根据红日安全写的文章,学习PHP代码审计的第四节内容,题目均来自PHP SECURITY CALENDAR 2017,讲完题目会用一个实例来加深巩固,这是之前写的,有兴趣可以去看看:
PHP代码审计01之in_array()函数缺陷
PHP代码审计02之filter_var()函数缺陷
PHP代码审计03之实例化任意对象漏洞
2|0漏洞分析
现在咱们看第一题,代码如下:
class Login {
public function __construct($user, $pass) {
$this->loginViaXml($user, $pass);
}
public function loginViaXml($user, $pass) {
if (
(!strpos($user, '<') || !strpos($user, '>')) &&
(!strpos($pass, '<') || !strpos($pass, '>'))
) {
$format = '' .
' ';
$xml = sprintf($format, $user, $pass);
$xmlElement = new SimpleXMLElement($xml);
// Perform the actual login.
$this->login($xmlElement);
}
}
}
new Login($_POST['username'], $_POST['password']);
?>
2|1题目分析
我们来看上面的代码,看第1214行,这里通过格式话字符串的方式,使用XML结构来存储用户的登陆信息,这样很容易造成注入,再看上面的代码,最后一行实例化了这个类,17行调用了login来进行登陆操作。下面到重点了,看代码810行,这里用到了strpos()函数来进行过滤<>符号,这个函数的用法如下:
如下:
var_dump(strpos("abcd","a"));
var_dump(strpos("abcd","y"));
?>
我们发现,找到子字符串的话就会返回对应的下标,没找到会返回false。而在PHP中,0和false的取反都是true,这点需要我们注意,这道题目就是开发者在使用这个函数时,只考虑了返回false的情况,而没有考虑当首字符匹配时返回0的情况。导致了过滤被绕过从而实施XML攻击。
现在知道了如何绕过,那么构造payload:user=<">
为了更好的理解,来看一下构造这个payload时,strpos()函数的返回结果如下:
$user='<">
是不是理解了一些呢,现在看上面的代码,思考一下如果我们使用这个payload会返回什么呢。
var_dump((!strpos($user, '<') || !strpos($user, '>')) &&
(!strpos($pass, '<') || !strpos($pass, '>')));
返回了true,其实就是var_dump((true || false) &&(true || false))
成功绕过,可以进行XML注入。
通过上面的学习讲解是不是对strpos()函数了解更深一些了呢?下面咱们看一个实例,这个实例也是设计者没有考虑周全,导致任意用户密码重置。
3|0实例分析
这次的实例是DeDecms V5.7SP2正式版,源码可以从网上下载搭建,这个很容易找到,就不详细说了,上面说了,这个漏洞是任意用户密码重置,下面我们来分析代码,漏洞的触发点在 member/resetpassword.php 文件中,是因为对接收的参数safeanswer没有进行严格的类型判断导致被绕过。下面看代码:
现在来对上面的代码做一个分析,当$dopost==safequestion时,通过$mid对应的id值来查询当前用户的safequestion,safeanswer,userid,email等值,现在来看第84行,这里的意思是当我们传入的安全问题和安全答案等于之前设置的值时,就传入sn()函数,重点来了,注意看,这里用的是双等于来验证,而没有用三等于,所以,这里是可以被绕过的。当用户没有设置安全问题时,那么默认情况安全问题值为0,安全答案值为null,这里指的是数据库中的值,而我们如果传入空值时,那么就是空字符串,84行语句也就变成了if('0' == '' && null == ''),也就是if(false&&true),所以我们只需要让前半部分转为true就可以了,通过测试如下图,都可以和0比较等于true。
if("0"=="0.0"){echo "成功1"."n";}
if("0"=="0e1"){echo "成功2"."n";}
if("0"=="0e12"){echo "成功3"."n";}
if("0"=="0e123"){echo "成功4"."n";}
if("0"=="0."){echo "成功5"."n";}
上面的几种payload均能使得 safequestion 为 true,成功进入sn()函数
这里用empty()函数来判断$id和$row是否为空,如果不为空的话,就继续向下走,进入if(empty($setp))中,先判断是否超时,如果没超时的话进入修改页面,我圈起来了,现在跟过去看一下具体代码如下:
分析上面代码,我们发现如果传入的$key和数据库中的$row['pwd']相同时,则完成密码的重置,也完成了攻击的分析过程。
3|1漏洞验证
现在注册两个账号,分别是test123和test456,查看mid的值如下,test456的mid为3。test123的mid为2。
我们发现到了修改密码的页面,直接可以修改密码。我们将密码修改为abcdef,然后登陆test123,发现登陆成功,密码成功被修改。