小白学代码审计

14 篇文章 2 订阅

下面为github某代码审计项目前两道题的详细解答 challenge1 话不多说,直接上代码:

<?php
$users = array(
        "0:9b5c3d2b64b8f74e56edec71462bd97a" ,
        "1:4eb5fb1501102508a86971773849d266",
        "2:facabd94d57fc9f1e655ef9ce891e86e",
        "3:ce3924f011fe323df3a6a95222b0c909",
        "4:7f6618422e6a7ca2e939bd83abde402c",
        "5:06e2b745f3124f7d670f78eabaa94809",
        "6:8e39a6e40900bb0824a8e150c0d0d59f",
        "7:d035e1a80bbb377ce1edce42728849f2",
        "8:0927d64a71a9d0078c274fc5f4f10821",
        "9:e2e23d64a642ee82c7a270c6c76df142",
        "10:70298593dd7ada576aff61b6750b9118"
);

$valid_user = false;

$input = $_COOKIE['user'];
$input[1] = md5($input[1]);

foreach ($users as $user)
{
        $user = explode(":", $user);
        if ($input === $user) {
                $uid = $input[0] + 0;
                $valid_user = true;
        }
}

if (!$valid_user) {
        die("not a valid user\n");
}

if ($uid == 0) {

        echo "Hello Admin How can I serve you today?\n";
        echo "SECRETS ....\n";

} else {
        echo "Welcome back user\n";
}

拿到这道题,我们先要找问题的突破点

if ($uid == 0) {

        echo "Hello Admin How can I serve you today?\n";
        echo "SECRETS ....\n";

从这里,我们可以看到,如果我们要拿到flag,那么uid的值必须为0 那么这个uid是什么呢?

我们继续看前面一段代码

$input = $_COOKIE['user'];
$input[1] = md5($input[1]);

foreach ($users as $user)
{
        $user = explode(":", $user);
        if ($input === $user) {
                $uid = $input[0] + 0;
                $valid_user = true;
        }
}

我们先传入一个数组user赋值给input 然后将input的第二项进行md5加密 然后对users数组进行遍历,然后判断其是否和通过cookie传入的user数组相等 如果相等的话,就把user数组的第一个元素赋值给uid,并通过加0的方式来进行int型的强制转换 并将valid_user置为true

那么如果我们要得到flag,那么需要满足两个条件: 1.user[0] 的值为 0 2.valid_user = true

 

 

结合这两个条件,那么解出这道题的最直接的方法就是得到users数组中第一个用户的密码的明文。 通过前面的

$input[1] = md5($input[1]);

以及

if ($input === $user) {

我们可以得出前面的密文是经过md5加密后的密文 我们尝试通过在线解密的平台来获得该密文对应的明文

 

 

通过解密平台并不能得到我们想要的明文,那么我们该怎么办呢?

为了介绍下一种拿到flag的姿势,我们先要谈一个php的漏洞 在php5.6.11之前,php的数组在进行"=="和"==="比较的时候有一个截断漏洞: 就是当数组的键名的值大于2<sup>32</sup>的时候,会造成一种截断,此时,如果键名如果为2<sup>32</sup>那么其实就和0是等价的,如果为2<sup>32</sup>+1,那么其实就和1是等价的,以此类推........ 可能有些读者不是很懂我说的是什么意思 我们可以通过几个demo来更直观的解释 我们先计算2<sup>32</sup>的值:

 

 

得到2<sup>32</sup>的值为:4294967296 首先是"=="的比较的demo:

<?php
var_dump([0 => 0] == [4294967296 => 0]);

运行结果:

 

 

<?php
var_dump([1 => 0] == [4294967297 => 0]);

运行结果: bool(true)

然后下面是 "==="的demo:

<?php
var_dump([0 => 0] === [4294967296 => 0]);

运行结果: bool(true)

<?php
var_dump([1 => 0] === [4294967297 => 0]);

运行结果: bool(true)

但是我们需要注意的是,这个漏洞只针对键名,而数组键所对应的值则不存在。 demo如下:

<?php
var_dump([0 => 0] === [0 => 4294967296 ]);

 

 

我们可以看到,值的2<sup>32</sup>就不和0相等了

好了,这个漏洞就介绍到这里了,现在我们再回到我们面临的问题中来。 我们知道的怎样 才能拿到flag,可是users列表中第一个用户密码的明文我们拿不到。

这时我们就要用另外一种思路了,uid为0,不一定就是匹配到了users列表中的第一位用户。如果我们的user[0]没有值,又匹配到了users列表中的任意一个用户,不就可以达到uid为0,valid_user为true吗

 

 

但是问题又来了,user[0]要不存在值,那么又怎么能满足匹配到users列表中的用户呢? 这时就要利用我们前面提到的php在数组比较时产生的漏洞了 我们再看这段代码

foreach ($users as $user)
{
        $user = explode(":", $user);
        if ($input === $user) {
                $uid = $input[0] + 0;
                $valid_user = true;
        }
}

在比较的时候,input和user这两个数组时用的"=="在比较,那么我们可以不设置input[0]的值,而是用input[4294967296]来代替,然后比较的时候时用的input[4294967296]和input[1]来和列表中的用户比较,而uid又是通过input[0]+0来得到的,这样就完美绕过了它的限制。 通过对上面users列表中用户的哈希进行md5解密,可以发现5号用户的密码明文时可以得到的:

 

 

然后我们完整的思路就是令 user[1] = "hund" user[4294967296] = "5"就可以了

 

 


challenge2

<?php
show_source(__FILE__);
$flag = "xxxx";
if(isset($_GET['time'])){ 
        if(!is_numeric($_GET['time'])){ 
                echo 'The time must be number.'; 
        }else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){ 
                        echo 'This time is too short.'; 
        }else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){ 
                        echo 'This time is too long.'; 
        }else{ 
                sleep((int)$_GET['time']); 
                echo $flag; 
        } 
                echo '<hr>'; 
}

分析该题,传入一个get类型的变量time,然后判断time的大小,如果time的大小位于区间[5184000,7776000]的话就可以得到flag。 但是这时就出现一个问题了:

         }else{ 
                sleep((int)$_GET['time']); 
                echo $flag; 
        } 

在得到flag之前,会将time转换为int型,并作为sleep函数的参数,也就是会停顿我们输入的time那么多秒 那么我们要得到flag,岂不是要等待这么久吗,这样等下去,ctf比赛早就结束了。 所以我们想要得到flag,就的想办法绕过

sleep((int)$_GET['time']); 

那么该怎么绕过呢,这时我们就要从它将time强制转换为int型这里来做手脚了

1.首先,我们要知道,在php中,通过get和post传过来的参数的值都是字符串型的 2.php中,数与数之间可以用不同的进制来进行比较

我们可以做下demo来验证下: 我们知道这里的time要大于5184000且小于7776000。 那么我们就选择一个5184001来做实验吧 先将5184001转换为16进制

 

 

然后再来和5184000和7776000进行比较:

<?php
echo (5184000 < 0x4f1a01);
echo "<br><br>";
echo (7776000 > 0x4f1a01);

运行结果:

 

 

从返回结果来看,十六进制和十进制这些时可以直接比较的。

十进制的数字字符串可以直接强制转换为int型而不会出现错误什么的,那么如果是十六进制的来进行int型的强制转换呢?

<?php
$time = "5184001";
echo (int)$time;

echo "<br><br>";

$time_hex = "0x4f1a01";
echo (int)$time_hex;

返回结果:

 

 

我们可以看到,十进制正常转换,而十六进制在强制转换的时候,当识别到第一个0后后面的就无法识别的,最终转换的结果为0。 那么我们的解题思路就出来了: 利用十六进制可以与十进制进行比较,而在进行int强制转换时的结果为0就可以绕过sleep拿到flag了

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值