安全宝「约宝妹」代码审计CTF题解

昨天安全宝搞了一个情人节活动,闯关模式的,最后一关有一个代码审计的CTF。题目如下:

安全宝CTF.png

从代码上可以看出来,用户的输入需要先经过三个正则判断,最后符合$password=="42",才能拿到FLAG。

三个正则比较简单,就不细说了,分别是:

  1. 可见字符超过12个
  2. 字符串中,把连续的大写,小写,数字,符号作为一段,至少分六段,例如a12SD+io8可以分成a 12 SD + io 8六段
  3. 大写,小写,数字,符号这四种类型至少要出现三种

符合这三个要求的字符串很容易构建,关键是最后还要使$password=="42"

要让一个字符串等于42,我首先想到的是16进制。

试一下

var_dump("42"=="0x2A");

输出为bool(true)。而且0x2A已经符合了正则3,要让它符合正则1也很容易,在前面补0就可以了。例如0x000000002A。但是正则2就不太好办了,在16进制前后加符号和小数点都会破坏在判断相等时进行的转换。


试了几次之后感觉这条路是走不通了,CTF不像开发中遇到的问题可以在Google上找答案,搜了几个关键词结果什么都没找到。只好找出PHP Manual,重新读一下里面的Comparison Operators一章,看看PHP在判断相等的时候会做什么转换。结果在第一个例子中就看到答案了。

var_dump("1" == "01"); // 1 == 1 -> true
var_dump("10" == "1e1"); // 10 == 10 -> true
var_dump(100 == "1e2"); // 100 == 100 -> true

在这题里既可以用4.2e1也可以用420e-1,不过第二种可以额外提供一个-符号,在第二个里加上小数点,420.0e-1,就满足正则2正则3了。为了满足正则1,在里面再加几个零,就是我最后的答案:

420.00000e-1

最后顺便一说,看到有朋友试图使用ASCII转义来构建字符串,写的是\x34\x32\x2E,在本地可以通过测试:

var_dump("\x34\x32\x2E"=="42");//bool(true)

但是放到服务器上可以通过三次正则判断,却无法通过最后一步的$password=="42",开始他以为是本地和服务器PHP版本不同导致的。但其实是因为通过POST提交的数据和在本地直接赋值的数据是不同的。

在PHP中,如果在双引号里放转义字符,在赋值的时候就会转义。例如

$a = "\x34\x32\x2E";
echo $a;//42.
echo strlen($a);//3
var_dump($a=="42");//bool(true)

而通过POST提交,浏览器会对转义字符做转换,服务器拿到的相当于在单引号里赋值的字符串(值为%5Cx34%5Cx32%5Cx2E):

$b = '\x34\x32\x2E';
echo $b;//\x34\x32\x2E
echo strlen($b);//12
var_dump($b=="42")//bool(false);

可以看出来这道题目考的是使用==运算符进行比较时发生的转换,而不是使用=运算符赋值时发生的转换。

最后推荐两个网站:3v4l.org,可以使用各种版本的PHP为你运行脚本,下次认为是PHP版本问题的时候上去跑一下就可以验证了。CTF训练营,IDF实验室做的CTF在线解题网站,喜欢CTF的同学可以多刷刷题,组个队去参加真正的CTF比赛。


附文字版代码:

<?php
$flag = "THIS IS FLAG";

if  ("POST" == $_SERVER['REQUEST_METHOD'])
{
    $password = $_POST['password'];
    if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password))
    {
        echo 'Wrong Format';
        exit;
    }

    while (TRUE)
    {
        $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
        if (6 > preg_match_all($reg, $password, $arr))
        break;

        $c = 0;
        $ps = array('punct', 'digit', 'upper', 'lower');
        foreach ($ps as $pt)
        {
            if (preg_match("/[[:$pt:]]+/", $password))
            $c += 1;
        }

        if ($c < 3) break;
        if ("42" == $password) echo $flag;
        else echo 'Wrong password';
        exit;
    }
}
?> 

标签: php, ctf

已有 7 条评论

  1. test test

    \x34\x32\x2E 好像无法通过第一个正则的

    1. 我试了一下应该没问题。http://3v4l.org/JhJHk

      1. test test

        http://3v4l.org/eLuX7

        1. 把代码中第3行的双引号换成单引号,才是POST过去的值

          1. gozhhu gozhhu

            这也能看到你 :)

          2. (⊙ˍ⊙) 龚老师好

  2. [...]http://bayescafe.com/php/yuebaomei-ctf.html[...]

添加新评论