PHP代码审计分段讲解(12)
28题
<!DOCTYPE html> <html> <head> <title>Web 350</title> <style type="text/css"> body { background:gray; text-align:center; } </style> </head> <body> <?php $auth = false; $role = "guest"; $salt = if (isset($_COOKIE["role"])) { $role = unserialize($_COOKIE["role"]); $hsh = $_COOKIE["hsh"]; if ($role==="admin" && $hsh === md5($salt.strrev($_COOKIE["role"]))) // strrev返回 string 反转后的字符串。 { $auth = true; } else { $auth = false; } } else { $s = serialize($role); setcookie('role',$s); $hsh = md5($salt.strrev($s)); setcookie('hsh',$hsh); } if ($auth) { echo "<h3>Welcome Admin. Your flag is } else { echo "<h3>Only Admin can see the flag!!</h3>"; } ?> </body>
这个题目出处应该是Jarvis OJ中的flag在管理员手里,题目地址在:http://web.jarvisoj.com:32778/
简单分析一下代码:
$auth = false; $role = "guest"; $salt =
首先给三个变量赋值,$auth和$role的值是我们可见的,$salt应该是后端赋值而我们不可见。
接着在COOKIE中传输role,并且将其反序列化的值赋给role
if (isset($_COOKIE["role"])) { $role = unserialize($_COOKIE["role"]);
接着是给hsh赋值
$hsh = $_COOKIE["hsh"];
然后是一个判断,进而给auth赋值
if ($role==="admin" && $hsh === md5($salt.strrev($_COOKIE["role"]))) // strrev返回 string 反转后的字符串。 { $auth = true; } else { $auth = false; }
我们看到最后想要获取flag需要 $auth为真
if ($auth) { echo "<h3>Welcome Admin. Your flag is xxx" } else { echo "<h3>Only Admin can see the flag!!</h3>"; }
即我们需要满足这个条件
$role==="admin" && $hsh === md5($salt.strrev($_COOKIE["role"]))
如果COOKIE中没有role的话,就进入else,使用代码最开始赋值的auth和role进行以下操作
else { $s = serialize($role); setcookie('role',$s); $hsh = md5($salt.strrev($s)); setcookie('hsh',$hsh); }
但else里面的操作正常来说是不能拿到flag的
所以我们需要突破的是:
$role==="admin" && $hsh === md5($salt.strrev($_COOKIE["role"]))
先看后面的一个比较:
$hsh === md5($salt.strrev($_COOKIE["role"]))
左边的$hsh是我们COOKIE中传递过去的$hsh 哈希值
右边是使用盐值和反转后的传过去的role值连接后,并进行md5加密的结果,需要与$hsh相等
这里看上去是无懈可击的,虽然我们可以传递hsh和role的值,但是因为我们不知道盐值,所以得到的md5很难凭猜或者爆破来令其与hsh相等。
但是如MD5,SHA1, SHA2等,都是基于Merkle–Damgård结构,而这类算法的问题在于:如果你知道加密前的原文,和加密后的密文,只需要再知道盐值的长度,就能在原文后面添加信息并计算出相应的密文。
在pcat的博文中介绍过应用场景为:
如果一个应用程序是这样操作的:
准备了一个密文和一些数据构造成一个字符串里,并且使用了MD5之类的哈希函数生成了一个哈希值(也就是所谓的signature/签名)
让攻击者可以提交数据以及哈希值,虽然攻击者不知道密文
服务器把提交的数据跟密文构造成字符串,并经过哈希后判断是否等同于提交上来的哈希值
这个时候,该应用程序就易受长度扩展攻击
另外,只有盐值在前,原文在后,才可以用hash长度扩展攻击。
预测得到的是:
md5(盐+原文+填充+恶意扩充)
算法原理在这篇文章中非常详细了,具体可移步:
https://www.freebuf.com/articles/web/31756.html
哈希长度扩展攻击我们通常使用HashPump进行利用
HashPump是一个借助于OpenSSL实现了针对多种散列函数的攻击的工具,支持针对MD5、CRC32、SHA1、SHA256和SHA512等长度扩展攻击。而MD2、SHA224和SHA384算法不受此攻击的影响,因其部分避免了对状态变量的输出,并不输出全部的状态变量。
其安装过程为:
git clone https://github.com/bwall/HashPump apt-get install g++ libssl-dev cd HashPump make make install
对于该题,我们先进行抓包,获取初始哈希值
role进行url解码后为:
s:5:"guest";
hsh为
3a4727d57463f122833d9e732f94e4e0
这里还利用了PHP反序列化unserialize的一个特性,即
unserialize()
会忽略多余的字符
举例代码为:
<?php $s = 's:5:"admin"; s:5:"guest";'; echo unserialize($s); ?>
输出结果为:
可以看出后面的
s:5:"guest";
并没有进行反序列化,而是被忽略了,这也是我们使用填充字符进行哈希长度扩展攻击的关键。
将获得的结果反序后重放
尝试得到盐值的长度为12,获得flag
这里其实可以写一个脚本进行尝试,这样可以快很多,减少体力劳动
参考链接:
https://www.freebuf.com/articles/web/31756.html
https://www.cnblogs.com/pcat/p/5478509.html
https://blog.csdn.net/zpy1998zpy/article/details/80858080