つれづれなる備忘録

CTF関連の事やその他諸々

ブラインドSQLインジェクション

今日は久しぶりにセキュリティ関係について書き残そうと思います!

 

皆さん、世の中のサイトの何割位に脆弱性があると思いますか?

正解はなんと、9割方のページに何らかの欠陥があるという調査結果が出ているそうです、、、

今日はその脆弱性をつく手法の中で、『ブラインドSQLインジェクション』というものについてちょっとだけ説明しようと思います。

 

 

皆さんはSQLインジェクションについては知っていると思いますが、よく見かける文献にある実行例はどれもテーブル名等々が分かっている状態で実行するのがほとんど。ぺネトレーションテストを行う場合、当たり前ですが攻撃者はそんな中の情報は知らない前提で行うものです。

まずはSQLインジェクションについて少し。

これはアプリケーションのセキュリティーホールを利用して、任意のSQL文を注入(inject)し実行させ、データベースを不正に操作することを指します。

例えば、以下のようなSQL文があるとします。以下実行環境をMicrosoft社のSQLServerとします。

SELECT * FROM user WHERE id = '(会員ID)' AND pass ='(パスワード)';

ウェブのSQLインジェクションに対して未対策のログインフォームで、ID「testid」は分かっているがパスワードが不明な場合、ID欄に『testid'; --』と入力すると以下のような文ができます。

SELECT * FROM user WHERE id = 'testid'; --' AND pass ='(パスワード)';

『--』以降はコメントアウトとして処理されるため、パスワードの如何に関わらず「testid」さんとして問題なくログインできます。仮にパスワードの入力判定をしている場合でも、適当な値を入力さえしていれば通ってしまいます。

たとえIDが分かっていなくても、『' OR 1=1; --』と入力すれば、

SELECT * FROM user WHERE id = '' OR 1=1; --' AND pass ='(パスワード)';

となり、idカラムにどんな値が入力されようと『1=1』は常に真なのでuserテーブル内の全てのレコードが選択されます。

 

ここまで原理が分かってしまえば後は何でもし放題ですよね?w

試しに『'; UPDATE user SET pass=''; --』と入力したとすると、、、とんでもないですね。

SELECT * FROM user WHERE id = ''; UPDATE user SET pass=''; --

ログインは出来ず、一瞬失敗したかな?と思いますが、その裏では全てのパスワードが削除されているのです。あぁおそろしや・・・・・

 

 

ここで疑問が生じますよね?

『おい、なんでテーブル名がuserで、カラム名がpassということが分かってんだよ!』

・・・その通りです、ゴメンナサイ。ここで登場するのがブラインドSQLインジェクションです。

原理はとっても簡単で、『最初のテーブル名は1文字目がuか?2文字目はsか?』なんて事を延々と続けていけばいいんです。条件文にサブクエリを含ませて、その真偽によって判断します。

 

テーブル名を取得するためには

SELECT name FROM sysobjects WHERE xtype = 'U';

とすれば良いです。

試しに『' OR (SELECT count(name) FROM sysobjects WHERE xtype = 'U') > 0 --』を入力します。これは(テーブル数)>0は真かどうかを見ているので、テーブルがひとつでもあればログインできます。これを使えばサーバー内にあるテーブル数は確定できますね?(別に使うこと無いけどww)

 

では続いて先頭のテーブル名を得てみましょう。

' OR lower(substring*1 >= 'n' --

まず『SELECT min(name) FROM sysobjects WHERE xtype = 'U'』で先頭のテーブル名を取得し、これを以下'user'とします。substring('user',x,y)でx文字目からy文字取得し、ここでは'u'となります。lower()によって小文字へ変換されるため、決定が容易になります。

'u'は'n'以降なのでここでログインは成功します。次に'n'を't'として真、'x'として偽、'v'として偽、'u'として真なので1文字目が'u'と定まります。

それでも心配なら、『>= 'u' --』を『= 'u' --』として確実に確認してみるのもいいでしょう。

次は2文字目の確認です。substring()の引数を(x,y)=(1,2)として'un','ut'・・・としても間違ってはいませんが、毎回毎回'u'を打つのは非効率的です。なので、(x,y)=(2,1)とすれば'n'として真、't'として偽、'q'として真、's'として真より2文字目が's'と決定します。

これを繰り返して'user'まで求まったとします。しかし、ここで決定したぞ!と喜んではいけません。まだ4文字目も以降名前が続く可能性は十分あるからです。

' OR (SELECT count(name) FROM sysobjects WHERE xtype = 'U' AND name = 'user') > 0 --』として真なら'user'テーブルは存在し、晴れて先頭のテーブル名を決定することができました。

 

'user'の次のテーブル名を取得するには、

' OR lower(substring*2 >= 'n' 』として同じ事を繰り返せばOKです!ww

今回はSQL Serverを対象としましたが、それ以外は以下のようにすれば良いです。

Oracle select * from all_objects where object_type='TABLE';

PostgreSQL select * from pg_tables where not tablename like 'pg%' order by tablename;

H2 select * from INFORMATION_SCHEMA.tables;

では、今日はここまで~

*1:SELECT min(name) FROM sysobjects WHERE xtype = 'U'),1,1

*2:SELECT min(name) FROM sysobjects WHERE xtype = 'U' AND name > 'user'),1,1