安全小游戏:寻找漏洞

来源:岁月联盟 编辑:猪蛋儿 时间:2020-01-29

0×1 前言
YouTube上的一个视频,分享给大家。
这里存在一个Bug,你能找到Ta吗?
if (empty($_POST['hmac']) || empty($_POST['host'])) {
 header('HTTP/1.0 400 Bad Request');
 exit;
}
$secret = getenv("SECRET");
if (isset($_POST['nonce']))
 $secret = hash_hmac('sha256', $_POST['host'], $secret);
$hmac = hash_hmac('sha256', $_POST['host'], $secret);
if ($hmac !== $_POST['hmac']) {
 header('HTTP/1.0 403 Forbidden');
 exit;
}
echo exec("host ".$_POST['host']);
?>
0×2 初步分析
首先大致游览一下,这很像一个CTF题目。最后的目标要执行:
echo exec("host ".$_POST['host']);
我们可以控制的输入有:
$_POST['hmac'] $_POST['host'] $_POST['nonce']
为了执行到目标需要跳过2个判断:
if (empty($_POST['hmac']) || empty($_POST['host'])) {
 header('HTTP/1.0 400 Bad Request');
 exit;
}
if ($hmac !== $_POST['hmac']) {
 header('HTTP/1.0 403 Forbidden');
 exit;
}
第一个判断只需要post参数hmac和host不为空就可以,第二个判断需要使得hmac和_POST['hmac']相等。
如果我们设置了nonce的值,那么中间会多执行一个步骤:
if (isset($_POST['nonce']))
$secret = hash_hmac(‘sha256′, $_POST['host'], $secret);
0×2 进一步分析
关键就在于第二个判断,能不能绕过他呢?hmac经过了sha256的哈希,而sha256是单向散列函数,我们不知道secret就不能倒推出散列值,如果穷举或采用字典查询这样并不符合题目的要求(穷举和字典攻击不是漏洞,任何系统都无法避免),这条路断了。
那么我们考虑一下PHP的类型自动转换:
在变量定义中不需要(不支持)明确的类型定义。变量类型是根据使用该变量的上下文所决定的。

为什么会相等呢?因为这里的上下文环境(context)自动将其转为了数字,而0e123(0*10^123) 和0e999(0*10^999)都是0,所以相等。

在md5判断的时候就会出错,因为他们都是0e开头的。
这时我们看一下PHP的比较运算符:
例子
名称
结果
$a == $b 
等于
TRUE,如果类型转换后 $a 等于 $b。 
$a === $b 
全等
TRUE,如果 $a 等于 $b,并且它们的类型也相同。 
$a != $b 
不等于
TRUE,如果类型转换后 $a 不等于 $b。 
$a !== $b 
不全等
TRUE,如果 $a 不等于 $b,或者它们的类型不同。 
这里采用里不全等,那么之前我们提到的漏洞就不能使用了。而且如果if (isset($_POST['nonce']))这步被跳过那么后面的
$hmac = hash_hmac('sha256', $_POST['host'], $secret);
那么hmac就根本无法预测。所以这步至关重要,这里我们可以控制的变量只有host和nonce。

在传入参数的时候,PHP不仅可以让你决定传入的值是什么,还可以让你决定传入的类型。所以可以是nonce=123,也可以传入一个数组nonce[]=123。那么我们试试:

可以看到,返回了NULL,有的PHP版本同时还会提示一个warning,但返回的也是NULL。那么我们如果nonce传入一个数组,接下来的hmac我们也会知道:

我们在host参数传入;id,最后会执行host ;id就会打印出当前的计算机用户名。
0×3 exploit
最终我们的需要post的数据为:
 hmac=58dedd736c5af324a198c6c663e569df59691854d1f53d704bdbce40f1d139c1&host=;id&nonce[]=1