DiceCTF 2021 - Missing Flavortext

2 min read読了の目安(約2100字

問題概要

Hmm, it looks like there's no flavortext here. Can you try and find it?
missing-flavortext.dicec.tf
Downloads
index.js

開くとログイン画面になっており、SQL Injectionの問題であることは容易に想像がつきます。使用しているのはSQLite3。username=admin、password=(ランダムな文字列)でログインすることが目的です。

解説

制約

index.jsを見ると分かるのですが、以下の制約があります。

  • usernamepasswordは空ではいけない

SQLite3の仕様で、''はエスケープされて'として扱われることを嫌っていそうです

  • usernamepassword'が含まれてはいけない
  • クエリは複数行使用しているので、コメントアウトするなら/*を使う

このことから、想定出来る解き方は大きく2通りです。

  • 制約の2つめを頑張って突破し、username=' or 1=1 /*等として解く
  • マルチバイトを利用してusernameの後ろにある'を別の文字として認識させる

2つ目はUTF-8とのことなので難しそうです。ということで1つ目の手法で、どこかに穴があるはず...!

body-parserにおける罠

さりげなくindex.jsに書かれているこの1文が今回の鍵です。

app.use(bodyParser.urlencoded({ extended: true }));

ここのextended: trueなんですが、公式ドキュメントを参照すると以下のような仕様が分かります。

A new body object containing the parsed data is populated on the request object after the middleware (i.e. req.body). This object will contain key-value pairs, where the value can be a string or array (when extended is false), or any type (when extended is true).

つまり、extended: trueによりusernamepasswordは配列としても送ることが出来ます。例えば、username=["admin?' or 1=1 /*]として送信すると、以下のような挙動をします。

  1. 普通に制約2つ目の'禁止は通過可能。usernameは配列なので、その1要素として"'"が含まれないため。
  2. クエリに関しては、1要素の配列なので普通の文字列と同等の扱いを受けてusername = 'admin?' or 1=1 /*' and password...といったクエリが作られる
  3. 結果、SQL Injectionが成功して唯一のユーザーであるadminとしてのログインに成功

Chrome拡張である「Advanced REST Client」を用いて以下のようにクエリを送るとレスポンスが来ます。

<!doctype html>
<html>
    <head>
        <link rel="stylesheet" href="/styles.css">
    </head>
    <body>
        <div>
            <p>Looks like there was no flavortext here either :(</p>
            <p>Here's your flag?</p>
            <p>dice{sq1i_d03sn7_3v3n_3x1s7_4nym0r3}</p>
        </div>
    </body>
</html>