La Vie en Lorse

敗者色の人生

X-MAS CTF 2019 - Execute No Evil

X-MAS CTF 2019のWriteupです.問題の内容は典型的なSQLiですが,SQLコメントアウトの仕様を新たに知ったので,その共有も兼ねて投稿.

Question

f:id:Lorse:20191218025057p:plain

Solution

http://challs.xmas.htsp.ro:11002は次のようなサイトです. f:id:Lorse:20191218022843p:plain

試しに適当な入力を入れると1個のレコードが表示されました. f:id:Lorse:20191218023848p:plain

ソースを見ると<!-- ?source=1 -->というコメントがあるので.http://challs.xmas.htsp.ro:11002/?source=1にアクセスするとサイトのソースが表示されました.

<?php
if (isset ($_GET['source'])) {
    show_source ("index.php");
    die ();
}
?>

<head>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<form class="center">
    <h2>Cobalt Inc. employee database search</h2>
    <label>Name:</label>
    <input type="text" name="name" autocomplete="off">
    <input type="submit" value="Search">
</form>
<br>
<!-- ?source=1 -->

<?php
include ("config.php");
$conn = new mysqli ($servername, $username, $password, $dbname);

if (isset ($_GET['name'])) {
    $name = $_GET['name'];
    $name = str_replace ("*", "", $name);
    $records = mysqli_query ($conn, "SELECT * FROM users WHERE name=/*" . $name . "*/ 'Geronimo'", MYSQLI_USE_RESULT); // Don't tell boss

    if ($records === false) {
        die ("<p>Our servers have run into a query error. Please try again later.</p>");
    }

    echo '<table>';
    echo '
    <tr>
        <th>Name</th>
        <th>Description</th>
    </tr>';

    while ($row = mysqli_fetch_array ($records, MYSQLI_ASSOC)) {
        echo '<tr>
            <td>',$row["name"],'</td>
            <td>',$row["description"],'</td>
        </tr>';
    }

    echo '</table>';
}
?>

</body>

$records = mysqli_query ($conn, "SELECT * FROM users WHERE name=/*" . $name . "*/ 'Geronimo'", MYSQLI_USE_RESULT);において,SQL文に変数がそのまま結合されているため,ここに任意の処理を追加することが出来そうです.

しかし,前後の/*,*/コメントアウトされてしまいます.このコメントアウトを回避する方法として真っ先に思いつくのは~~ */ ~~のように入力内でコメントを終わらせる方法ですが,これは$name = str_replace ("*", "", $name);によって防がれています.このコメントアウトを回避することは出来ないでしょうか?

MySQL :: MySQL 5.6 リファレンスマニュアル :: 9.6 コメントの構文を確認すると,/*! ~~~ */と書いたときはmysqlではコメントアウトされずに実行されるようです.これを利用することでSQLiが出来ます.

まずはusersテーブルの全レコードを表示してみます.! '' or 1 !=を入力することで全レコードを取得できますが,usersテーブルにはGeronimoのレコードしかないようです.

ではusers以外のテーブルを探してみます.mysqlが使われているのでINFORMATION_SCHEMA.COLUMNSテーブルを調べることでDB内のテーブル一覧を取得できます.! '' union select * from INFORMATION_SCHEMA.COLUMNS where 1 !=で取得できそうな気がしますが,エラーが出ます. f:id:Lorse:20191218030754p:plain

これはunionの前後で取得しているカラム数が異なるからです.まずはSELECT * FROM usersで取得されるカラム数を知る必要があります. 調べる方法はかなり泥臭く,! '' union select 0, 0 from users where 1 !=の0の個数を増やしていって,エラーが無くなるかで判断します.今回は! '' union select 0, 0, 0 from users where 1 !=のときエラーが出なかったので,カラム数は3つです.

カラム数が分かったので,改めてテーブル一覧を取得しましょう.INFORMATION_SCHEMA.COLUMNSテーブルのTABLE_NAMEにはテーブル名,COLUMN_NAMEにはカラム名が入っているので,これらを表示させます.! '' union select 0, TABLE_NAME, COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where 1 !=を入力するとテーブル一覧とそのテーブルのカラム一覧が出ました.(画像は問題に関係のあるテーブルのみ抜粋) f:id:Lorse:20191218032312p:plain

! '' union select 0, whatsthis, 0 from flag where 1 !=flagテーブルの中身を取得します. f:id:Lorse:20191218032531p:plain

フラグを取得できました.

Flag:X-MAS{What?__But_1_Th0ught_Comments_dont_3x3cvt3:(}