题目
打开页面显示为
没有其他信息,查看源代码也是空的
用dirsearch扫一下
可能是git源码泄露,可以用githack获取源码
python Githack.py http://5063c85b-a33d-4b6f-ae67-262231a4582e.node4.buuoj.cn:81/.git/
去工具所在的目录找到index.php文件
打开文件显示如下,需要代码审计
代码为简单的php脚本,接受名为‘exp’的get参数。
data://
协议用于访问数据(如base64编码的数据)。filter://
协议用于过滤数据。php://
协议用于访问各种内置的PHP流(如输入、输出、文件等)。phar://
协议用于访问PHAR(PHP归档文件)。/i
标志表示不区分大小写。
首先使用了正则表达式匹配来检查exp参数中是否包含“data://”,"fliter://","php://","phar://"等协议,日若包含其中任何一个协议,会输出
后使用正则表达式替换的方式检查参数中是否存在类似函数调用的语法,即以字母和下划线开头,后跟括号内可以由递归调用。若检查结果为“;”,代码会执行“eval($_GET['exp'])”,即执行exp中的代码
最后代码会检查参数中是否包含特定字符串,如"et", "na", "info", "dec", "bin", "hex", "oct", "pi", "log"等。如果存在这些字符串中的任意一个,代码将输出"还差一点哦!"并终止执行。
总结上述代码审计可知,被过滤掉了data://、filter://、php://、phar://、et、na、info、dec
bin、hex、oct、pi、log
对第二个if
(?R)是引用当前表达式,(?R)? 这里多一个?表示可以有引用,也可以没有。引用一次正则则变成了[a-z,_]+\([a-z,_]+\((?R)?\)\),可以迭代下去,那么它所匹配的就是print(echo(1))、a(b(c()));类似这种可以括号和字符组成的,这其实是无参数RCE比较典型的例子
if(';' === preg_replace('/[a-z,_]+(?R)?(?�)?/', NULL, $_GET['exp']))可以看出这是典型的无参数rce
因为不能传参,所以只能利用函数回显套娃来代替目标参数
解法一:
scandir() :将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称(glob()可替换)
localeconv() :返回一包含本地数字及货币格式信息的二维数组。(但是这里数组第一项就是‘.’,这个.的用处很大)
current() :返回数组中的单元,默认取第一个值。pos()和current()是同一个东西
逐步解析构造payload
var_dump(localeconv());能发现string[1]就是一个“.”,这个点是由localeconv()产生的
var_dump(localeconv()):
是一个PHP函数调用,用于打印当前的本地化信息。它返回一个关联数组,包含了与当前地区相关的数字格式、货币格式、日期格式等信息。
array(19) {
["decimal_point"]=>
string(1) "."
["thousands_sep"]=>
string(1) ","
["int_curr_symbol"]=>
string(3) "USD"
// ...
}
利用current()
函数将这个点取出来的,点代表的是当前目录,那接下来就很好理解了,我们可以利用这个点完成遍历目录的操作,相当于就是linux
中的ls
指令
current()取第一个值,那么current(localeconv())就能构造一个‘.’,而
'.'
表示当前目录,scandir('.')
将返回当前目录中的文件和子目录,从代码审计得知flag所在的文件名就是flag.php
flag的文件名在比较后端我们可以通过array_reverse()将数组内容反转,让它从倒数第二的位置变成正数第二
移动指针读取第二个数组,参照下列数组移动操作可知我们应选用next()函数
end() : 将内部指针指向数组中的最后一个元素,并输出
next() :将内部指针指向数组中的下一个元素,并输出
prev() :将内部指针指向数组中的上一个元素,并输出
reset() : 将内部指针指向数组中的第一个元素,并输出
each() : 返回当前元素的键名和键值,并将内部指针向前移动
highlight_file()返回文件内容
所以最终的payload为
?exp=highligth_file(next(array_reverse(scandir(current(localeconv())))));
解法二:
在已知文件名flag.php的情况下直接读文件
已知文件名,改包手动添加cookie头把文件名写在PHPSESSIONID后
构造payload为
readfile(session_id(session_start()));
session_start()
是PHP的一个函数,用于启动一个新的会话或者恢复一个已存在的会话。session_id()
函数返回当前会话的ID。
readfile()
是PHP的另一个函数,用于读取文件内容并将其输出到浏览器。
参考文章链接:
BuuCTF [GXYCTF2019]禁止套娃详解(两种方法)-CSDN博客
BuuCTF [GXYCTF2019]禁止套娃详解(两种方法)-CSDN博客