月度归档:2009年10月

php.ini的open_basedir参数设置与PHP文件操作存在的安全隐患

【php.ini中的open_basedir参数】
如果设置了open_basedir参数为一组目录列表,则PHP只能操作此组目录列表下的所有文件(包括文件自身)。 当一个脚本试图打开一个指定目录树之外的文件时,将遭到拒绝。所有的符号连接都会被解析,所以不可能通过符号连接来避开此限制。

特殊值’.’指定了存放该脚本的目录将被当做基准目录,但这有些危险,因为脚本的工作目录可以轻易被chdir()改变。

对于共享服务器,在httpd.conf中针对不同的虚拟主机或目录灵活设置该指令将变得非常有用。
在Windows中用分号分隔目录,UNIX系统中用冒号分隔目录。

作为Apache模块时,父目录中的open_basedir路径将自动被继承。
指定的限制实际上是一个前缀,而非一个目录名,也就是说”/dir/incl”将允许访问”/dir/include”和”/dir/incls”,如果您希望将访问控制在一个指定的目录,那么请在结尾加上一个斜线。
默认是允许打开所有文件。
另外,在PHP6中将使用基于open_basedir的安全防护。
另外,dl()函数可以绕过open_basedir指令的限制。

【不对此进行设置可能存在的问题】
将如下代码放到所在服务器,如果没有设置此参数,则可能会列出服务器的所有文件目录,让人看到自己服务器的所有信息貌似并不是一件很爽的事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
/**
 * 遍历所在服务器的所有文件
 * 基于php.ini配置中的open_basedir设置,如果此设置设置了相关路径,则只能浏览此目录下的文件
 * @param 当前目录的上level级目录,显示此目录下的所有文件 ,如果有的话
 * @example http://localhost/index.php?level=1  当前目录的上一级目录下的所有文件
 */
header("Content-type: text/htmlcharset=utf-8")
 
$filepath = $_SERVER['DOCUMENT_ROOT'] . "/"
$dir_array = array()
while (true) {
    $dir_array[] = $filepath
    if (!is_dir($filepath)) {
        break
    }
    $filepath = realpath($filepath."../")
}
 
print_r($dir_array)
 
$level = $_GET['level']
 
 
if (isset($level)) {    //  遍历当前目录的上level级下的所有文件
    if (isset($dir_array[$level])) {
        $files = get_files($dir_array[$level])
        print_r($files)
    }else{
        echo '不存在此目录!', '<br />'
    }
 
}else {  //  所有文件
    foreach ($dir_array as $dir) {
        $files = get_files($dir)
        print_r($files)
    }
}
 
die()
 
 
function get_files($dir) {
    $dir = realpath($dir) . "/"
    $files  = array()
    if (!is_dir($dir)) {
        return $files
    }
 
    $pattern =  $dir . "*"
    $file_arr = glob($pattern)
    foreach ($file_arr as $file) {
        if (is_dir($file)) {
            $temp = get_files($file)
            if (is_array($temp)) {
                $files = array_merge($files, $temp)
            }
        }else {
            $files[] = $file
        }
    }
    return $files
}
?>

【严重问题】
如果用户有正常的FTP账号,使用FTP创建文件,并copy到服务器的其它目录,并执行该程序,也许后果不堪设想!谨记!

PHP的生命周期

PHP的生命周期

php本身的生命周期是在命令行执行php test.php程序的生命周期(也就是cli)

整个过程如下:

执行php test.php

调用每个扩展的模块初始化程序

    请求test.php程序

    调用每个扩展的请求初始化程序

        执行test.php程序

    调用每个扩展的请求关闭程序

    释放内存等清除工作

调用每个扩展的模块关闭程序

终止php

如果PHP运行在WEB服务器中,那么它的生命周期就会有些不同了,这里又要根据服务器的不同分为以下三种:

1、单进程

模块初始化

    请求初始化

        执行脚本

    关闭请求

    请求初始化

        执行脚本

    关闭请求
    请求初始化
        执行脚本
    关闭请求
    请求初始化
        执行脚本
    关闭请求
……

……
……
模块关闭

单进程的WEB服务器只对模块初始化一次,所有的页面请求都在其中

2、多进程

模块初始化                         模块初始化                    模块初始化                模块初始化

    请求初始化                         请求初始化                    请求初始化                请求初始化

        执行脚本                            执行脚本                      执行脚本                   执行脚本

    关闭请求                            关闭请求                      关闭请求                   关闭请求

    请求初始化                         请求初始化                    请求初始化                请求初始化

        执行脚本                            执行脚本                      执行脚本                   执行脚本

    关闭请求                            关闭请求                      关闭请求                   关闭请求

    请求初始化                         请求初始化                    请求初始化                请求初始化

        执行脚本                            执行脚本                      执行脚本                   执行脚本

    关闭请求                            关闭请求                      关闭请求                   关闭请求

……                                  ……                            ……                         ……

关闭模块                            关闭模块                       关闭模块                    关闭模块

多进程只是把单进程复制了多份,各个子进程间无法共享数据等。

3、多线程

                        模块初始化

请求初始化                         请求初始化                    请求初始化                请求初始化

    执行脚本                            执行脚本                      执行脚本                   执行脚本

关闭请求                            关闭请求                      关闭请求                   关闭请求

                    关闭模块

全局变化可以在初始化的时候建立,并且只建立一次。

PHP扩展之smart_str分析

php_smart_str_public.h
在php_smart_str_public.h文件中我们找到了如下定义:

1
2
3
4
5
typedef struct {
              char *c;
              size_t len;
              size_t a;
} smart_str;

其中,size_t的定义 为:typedef unsigned int size_t;
char *c 表示整个字符串
size_t len表示整个字符串的实际长度
size_t a 表示这个字符的总长度(smart_str的字符串总长度总是以128字节的倍数增加)

从php_smart_str.h文件所在的源码中我们可以看出它的空间分配具体有如下特点:
1、在初始化时,如果所给字符串的长度小于SMART_STR_START_SIZE(定义为78字节),则初始smart_str的总长度为78字节(即a值),并给它分配空间;如果所给字符串的长度大于SMART_STR_START_SIZE,则将此长度上加上SMART_STR_PREALLOC(定义为128字节)作为smart_str的总长度,并为之分配空间。
2、在已有字符串后添加时,如果添加后的总长度小于当前smart_str->a的值,则无需分配空间,否则,在smart_str新的长度的基础上再加上128字节的空间。
这样有些类似于操作系统中内存以页为单位分配,它的好处是对齐内存地址,提高访问速度。
在php_smart_str.h文件中,我们可以看到一些针对smart_str类型的宏定义的操作方法,方法列表如下:

smart_str_0 如果字符串存在,给字符串的最后添加’\0’;
smart_str_alloc 在初始化和添加字符串时进行空间的分配;
smart_str_appends_ex(dest, src, what) 在一个smart_str变量后面添加一个字符串,它分为两种空间分配方法,当what为真是,它使用__zend_realloc函数,此函数为永久分配内存,在无法分配内存时会报Out of memory的,当what为假时,它使用标准空间分配,即ZendMM所我有的分配函数erealloc(ptr, size)。
smart_str_appends(dest, src) 在一个smart_str变量后面添加一个原生的字符串
smart_str_appendc(dest, c) 在一个smart_str变量后面添加一个字符
smart_str_free(s) 消除smart_str变量,释放所占内存
smart_str_appendl(dest, src, len) 在一个smart_str变量后面添加一个长度为len的字符串
smart_str_append(dest, src) 在一个smart_str变量后面添加一个smart_str字符串
smart_str_append_long(dest, val) 将一个long的数字转化成字符串后添加到smart_str的后面smart_str_append_unsigned(dest, val) 将一个unsigned long的数字转化成字符串后添加到smart_str的后面
smart_str_appendc_ex(dest, ch, what) 与smart_str_appends_ex类似,所不同的是它添加的是一个字符。
smart_str_setl(dest, src, nlen) 将一个长为len的原生的字符串强制转化为一个smart_str
smart_str_sets(dest, src) 将一个原生的字符串强制转化为一个smart_str

【经典代码】
数字转字符串:

1
2
3
4
5
6
7
8
9
char __b[32];             
int __num = 28790;
char *__p;
__p = __b + sizeof(__b) - 1;
*__p= '\0';                                                                                                                                                                                     
do{                                                                                                                                                                                        
 *--__p = (char) (__num % 10)+ '0';                                                                                                 
__num /=10;                                                                                                                                                                       
} while (__num > 0);