【Hacker101 CTF】Micro-CMS v2解説 SQLインジェンクションを使った認証バイパス

【注意】本記事にはHacker101 CTF Micro-CMS v2の解答に係る要素が多分に含まれています。当該CTFを独力で解きたい方は、後ほど参照することをお勧めします。
Hacker101はセキュリティに関する無料のコースを提供しているサイトであり、バグバウンティのプラットフォームであるHackerOneにより運営されています。Hacker101 CTFはHacker101で提供されているコンテンツの一つです。本記事ではHacker101 CTFで出題されているMicro-CMS v2を解説します。

Micro-CMS v2の概要

Micro-CMS v2は、ごく単純なCMS(Content Management System)が導入されているウェブサイトを題材にしたCTFです。Micro-CMS v1では記事の作成と編集が誰でも可能でしたが、v2ではログインが必須となりログインページに飛ばされるようになっています。

FLAG0 ログインページのSQLインジェクションその1

ログインページはユーザー名とパスワードを要求されるありふれた形式です。試しにユーザー名にaaaと入力して送信してみると”Unknown user”と返ってきます。一見普通のログインページですが、Usernameのフィールドにシングルクォートを入力して送信すると、以下のようにエラーが発生してバックトレースが表示されます。

エラーとして出力されている”You have an error in your SQL syntax;”から、SQL文の実行が失敗したことが分かります。問題のSQL文自体は以下のように組み立てられており、エラーが発生した原因は、Usernameへの入力にシングルクォートが入ったことでSQLの構文が破綻したためです。

SELECT password FROM admins WHERE username='<Usernameへの入力>'

この場合、シングルクォートがエスケープされないことを利用して、正当なユーザー名が不明であっても認証の一部をバイパスすることができます。Usernameに”‘ or ‘1’ = ‘1″と入力すると、以下のようなSQL文が生成されることになります。末尾にシングルクォートが付かないことに注意してください。WHERE句が常に成立することで、実質的に通常のSELECT文と変わらなくなり、結果としてユーザー名の認証をバイパスできます。

SELECT password FROM admins WHERE username='' or '1' = '1'

返ってくるエラーが”Unknown user”から”Invalid password”に変化しました。しかしながら、この方法はあくまでユーザー名を無視して認証できるだけで、このままでは正当なパスワードを入力しなければログインはできません。あらためて先のSQL文を確認すると、SELECT文を使用してpasswordなるカラムの値を取得しようとしています。このことから、SELECT文の結果取得される値を使って、Passwordフィールドに入力されたパスワードが検証されるのであろうと推測が立ちます。これを実証するにはUNION句を使います。

SELECT password FROM admins WHERE username='a' UNION SELECT '1'

あえて存在しないユーザー名をWHERE句に与えることで、前半のSELECT文ではadminsテーブルから何も取得されないため、後半のSELECT文が返す値でパスワードの検証を行わせることができます。今回の場合、Usernameに”a’ union select ‘1″を、Passwordに1を入力すると認証をバイパスできます。

ログイン後、認証ユーザーのみ閲覧できる”Private Page”なるページでFLAGが確認できます。

FLAG1 編集スクリプトのHTTP Verb Tampering

サイト上の各ページは”Edit this page”リンクから編集画面に移動することができます。このとき、スクリプトに送信されているのは上記のようにHTTP GETリクエストです。これをBurpを使いPOSTリクエストに書き換えてForwardすると、FLAGが表示されます。受け取ったHTTPリクエストのVerbをちゃんと検証していないスクリプトの脆弱性が突かれるイメージでしょうか。

FLAG2 ログインページのSQLインジェクションその2

“You got logged in, congrats! Do you have the real username and password? If not, might want to do that!”。FLAG0の方法でログインする際、遷移時の中間ページのソースを見ると、コメントとしてこんなことが書かれています。「本来のユーザー名とパスワード、知りたいよね?」。そういうことですね。

FLAG0の時はUNION句をうまく使い、本来のユーザー名・パスワードを知らずしてログインしました。さらに工夫すると、ユーザー名とパスワードを1字1句違わず特定することができます。今回の場合、まず以下のSQL文でadminsテーブルに登録されているユーザー数を特定できます。

SELECT password FROM admins WHERE username='a' or (SELECT count(*) FROM admins)<2#'

肝となるのはWHERE句のor以降です。adminsテーブルの行数をcountで取得して比較演算を行っており、行数が2より小さい場合はWHERE句が成立します。末尾の#はmySQL、MariaDBのコメント句であり、余計なシングルクォートをコメントアウトしています。WHERE句の成否は、認証後に表示されるエラーメッセージから推測することができます。このSELECT文は、本来入力されたユーザーのパスワードをadminsテーブルから探すものであり、何も取得できなかったときは当該ユーザーが存在しないものと判断され、”Unknown user”というエラーメッセージが出力されます。逆にWHERE句が成立した場合、つまりSELECT文が何かしらを取得して、且つパスワードが一致しなかったときは”Invalid password”というエラーメッセージが出力されます。要するに、上のSQL文を実行して”Unknown user”が表示されたらユーザー数は2以上、”Invalid password”が表示されたらユーザー数は1です。今回はユーザー数は1でした。

肝心のユーザー名・パスワードの特定は次のSQL文で行います。以下はユーザー名を特定する場合のSQL文です。

SELECT password FROM admins WHERE username='a' or exists(SELECT username FROM admins WHERE username LIKE '______')#'

先のユーザー数を特定するSQL文と同様にエラーメッセージの変化を利用してWHERE句の成否を確認し、ユーザー名の長さと文字を特定することができます。LIKE句で使われているアンダーバーはワイルドカードであり、任意の1文字にマッチします。アンダーバーが6つなら、文字を問わず6文字の文字列にマッチします。ユーザー名の文字列長を特定した後は、順番にアンダーバーを他の文字に置き換えていくことで、1文字ずつユーザー名を特定することができます。ワイルドカードには%もありますが、先のバックトレースから%だけは%%にあらかじめ置換が行われていることが確認できるため、余計な制約を避けるためにここではアンダーバーを使います。

まず1人存在するユーザーのユーザー名が1文字がどうか調べます。エラーメッセージが”Unknown user”なので2文字以上はあると判断できます。

ユーザー名が6文字であるかどうかを確認したところで、エラーメッセージが”Invalid password”に変化しました。ユーザー名は6文字と特定できました。ここからはアンダーバーを1文字ずつ総当たりでアルファベットに置き換えていき、ユーザー名を特定します。手動でも検証可能なパターン数ですが、手間なのでBurp Intruderを使って調べます。

まずはBurp Proxyでユーザー名の文字数を調べるのに使ったのと同一のリクエストをInterceptします。その後右クリックしてメニューから”Send to Intruder”でリクエストをIntruderに飛ばします。

Intruderに飛ばしたリクエストのタブに移動し、さらにPositionsタブでクエリ文字列内のアンダーバーの最初の1文字を選択します。

Payload typeにBrute forcerを選び、Character setにアルファベットすべて、Min lengthに1、Max lengthに1を指定して、Start attackで総当たりを開始します。

Start attackの後に開いたウィンドウで試行結果を確認します。エラーメッセージの長さが異なるのでLengthの値でアタリを付けられます。ユーザー名の最初の1文字はpだと確認できました。この手順を残りの5文字で繰り返し、ユーザー名を特定できます。パスワードも同様です。パスワードを特定するためのSQL文は以下の通りです。

SELECT password FROM admins WHERE username='a' or exists(SELECT password FROM admins WHERE password LIKE '______')#'

試行の結果、ユーザー名はprince、パスワードはraquelでした。特定した認証情報を使ってログインすると、最後のFLAGが表示されます。

おまけ

CTF自体とは関係ないようですが、各ページの”Edit this page”リンクから飛べる編集画面で、2回以上内容を変更せずにSaveすると404になるバグがあります。

関連記事

他のHacker101 CTFの記事も併せてどうぞ。

参考リンク

Share - この記事をシェアする

「【Hacker101 CTF】Micro-CMS v2解説 SQLインジェンクションを使った認証バイパス」への1件のフィードバック

コメントは受け付けていません。