More info...

2010-01-14

SQLインジェクションとは何か?その正体とクラッキング対策。

世間では、今Gumblar祭りが勃発中であり、SQLインジェクションがニュースに出てくることは少なくなったが、だからと言ってSQLインジェクションの脅威がなくなったわけではない。SQLインジェクションはGumblarを仕掛ける手段としても利用されることがあり、Webアプリケーションを提供する全ての人にとって、対策を講じなければいけない驚異であることに変わりはない。SQLインジェクションという攻撃手法が認識され、大いに悪用されているにも係わらず、その本質に迫って解説している記事は少ないように思う。従来のWeb屋だけでなく、今やアプリケーション開発の主戦場はWebであると言っても過言ではなく、そういう意味ではSQLインジェクションについて理解することは、全てのプログラマにとっての嗜みであると言えるだろう。

というわけで、今日は改めてSQLインジェクションについて語ってみようと思う。

SQLインジェクションとは?

SQLインジェクションは、一言で言うと不正にSQLを書き換えてしまう攻撃手法である。名前からするとデータベース上のセキュリティ脆弱性のように思ってしまうかも知れないが、実はそうではなく、Webアプリケーション側の問題なのである。データベースは、安全なSQLであるか不正に書き換えられたSQLであるかを見分けることは出来ない。文法さえ正しければSQLを実行して結果をしまうのである。それはデータベースにとって期待通りの動作であり、文法的に正しいSQLを実行するのは欠陥でも何もない。(むしろ実行出来ないのが欠陥であると言える。)つまり、Webアプリケーション側でSQLが書き換えられた時点で、時はもう既に遅し!なのである。

従って、SQLインジェクション攻撃は特定のデータベースだけの問題ではない。MySQLはもちろんのこと、ポスグレだってオラクルだってDB2だってMS SQL ServerだってSQLインジェクションの餌食となりえるのである。なぜなら、Webアプリケーションの問題だから。おっと、もちろんFirebirdもだ。

では、何故Webアプリケーション側でSQLインジェクション攻撃が可能なのか?というと、それは、WebアプリケーションがSQLを動的に組み立てるからである。Webアプリケーションでは、まあ一番最悪な例では次のように、文字列を連結してSQL文を組み立てようとすることがある。
$sql = "SELECT user, pass FROM users
WHERE user = '" + $user + "' AND pass = '" + $password + "'"
$userや$passwordはHTMLの入力フォームから受け取った文字列であるとしよう。これは何らかのユーザー認証を仮定したものであるが、「これの何が悪いの?」と思った人はアウト〜〜〜ッ!!である。これはまさにSQLインジェクションが可能なコードだからだ。

攻撃例

例えばユーザーとして「nippondanji」、パスワードに「fundoshi」という文字列を代入したとしよう。すると、このコードが生成するSQL文(つまりデータベースに投げられるもの)は次のようなものになる。
SELECT user, pass FROM users WHERE user = 'nippondanji' AND pass = 'fundoshi'
これのSQL文なら安全である。攻撃者は、$userや$passwordに相当する部分について様々なテクニックを使って攻撃をし、不正にログインしたり、不正にクレジットカードなどの情報を入手したり、不正にデータを書き換えたりする。そして結果的に、文法は正しいが全く意味の異なるSQL文へと書き換えてしまうことにより、攻撃を成立させるのである。SQLインジェクションに利用される書き換えのテクニックには様々なものが存在するが、例えば次のようなものが挙げられる。
  • クォーテーションを文字列の中に混入させる。これにより、攻撃者は新たなSQLの文法を注入=インジェクトすることが可能になる。
  • SQL文を途中でコメントアウトしてしまう。これにより、文法的に正しいSQL文を組み立てやすくなる。
  • OR 1=1など恒常的に成り立つ条件を利用する。これにより、WHERE句の条件を無視してしまう。
  • UNIONを用いて任意の情報を入手する。
  • INFORMATION_SCHEMAへアクセスしてどのようなテーブルが存在するかといった情報を入手する。
  • セミコロンを使って複数のSQL文を記述し、任意のSQL文を実行する。これにより、不正にデータを改竄してしまう。
攻撃方法は数えあげればキリがないが、実に様々なバリエーションが存在する。(攻撃方法が分かったとしても、絶対に悪用してはいけないよ!)例えば、前述のSQL文に対してパスワードの確認をすっ飛ばしたいと思ったなら、$userに「nippondanji'#」というような値を混入させてしまうことである。そうすると、SQL文は次のように違った意味のものになってしまうだろう。
SELECT user, pass FROM users WHERE user = 'nippondanji'#' AND pass = 'hoge'
この例では、クォーテーションによって文字列が意図しないところで閉じており、なおかつハッシュ記号(#)以降はコメントアウトされてしまっているので、パスワードの値が何であってもユーザーが存在すればログイン出来てしまうのである!

もう一つ例を紹介しよう。次は、氏名からユーザーを検索するコードだと仮定して欲しい。
$sql = "SELECT user, first_name, last_name, prof FROM users
WHERE MATCH(first_name, last_name) AGAINST ('" + $search + "')"
例えばこのコードの$searchに対して「Mikiya」と入力すると次のようなSQL文になる。
SELECT user, first_name, last_name, prof FROM users WHERE MATCH(first_name, last_name) AGAINST('Mikiya')
ああ、可愛そうなSQL・・・。このSELECT文を書き換えると、最悪の場合任意の情報をデータベースから抜き出すことが可能になる。例えば、検索語のところに「') LIMIT 0 UNION SELECT user, pass, 1, 1 FROM users ORDER BY RAND() LIMIT 10 #」という文字列を入れたとしよう。すると、このSQL文は次のようになってしまう。
SELECT user, firt_name, last_name, prof FROM users WHERE MATCH(first_name, last_name) AGAINST('') LIMIT 0 UNION SELECT user, pass, 1, 1 FROM users ORDER BY RAND() LIMIT 10 #')
おわかりだろうか?全く意味の異なるSQL文であるが、これは文法的には正しい。そして、意図しない情報(この場合は任意のユーザーのパスワード10個)を抜き出しているのである。UNIONを利用すれば任意のテーブルから任意の情報を取り出すことが可能であり、それはそれは恐ろしい攻撃が成立してしまうのである。しかも、UNIONを使ってINFORMATION_SCHEMAにアクセスすればどのようなテーブルが存在するかが把握出来るので、攻撃者は思いのままに情報を入手出来てしまうだろう。

このように、SQLインジェクションは本当にヤバい攻撃なのである。

対策編

SQLインジェクションの最も確実で根本的な対策は、プリペアードステートメントを利用することである。問題は、SQLを組み立てる過程にあるので、動的にSQLを組み立てなければ原理的にはSQLインジェクションは発生しない。Webのフォームから受け取った値は、パラメータとしてプリペアードステートメントに渡せばいいわけである。ただし、プリペアードステートメント自身を動的に組み立ててしまうと元の木阿弥なので注意しよう!また、せっかくプリペアードステートメントを利用しても、その中でストアドプロシージャを呼び出し、ストアドプロシージャ内で動的なSQLの組み立てをやっていた・・・などということにもならないように注意したい。

何らかの事情によってプリペアードステートメントが利用出来ない場合は動的にSQLを組み立てるしかないが、その場合はWebのフォームから受け取った値をチェックしたりエスケープしたりしてフィルタするしかない。例えば入力される値が数字であると分かっていれば、正規表現で不正な入力を簡単にはじくことができるだろう。文字列の場合にはしっかりとエスケープする必要があるが、攻撃者はフィルタをすり抜けて不正なシングルクォーテーションを混入させる手法の発見に血道をあげており、フィルタの開発と攻撃手法の確立はいたちごっこであると言える。(今は完全に見えてもどこかに穴があるかも知れない。穴がひとつでも存在していれば一巻の終わりなのだ!)Webアプリケーションファイアウォールを使ってフィルタを強化するのもアリだろう。

ただし、フィルタはWebから受け取った値だけを対象にするだけでは不十分である。いったんデータベースへ不正な値を格納させておいてから、それを攻撃に利用する「セカンドオーダーインジェクション」という手法が存在するので、フィルタはSQLを組み立てるのに利用する値全てに適用する必要があるので注意しよう。

UNIONなどを使った攻撃では、そのSQL文を実行するユーザーが入手出来る情報なら、ありとあらゆるものが入手出来てしまう。従って、「このサービスはあんまりアクセスが多くないから・・・」などと思って油断してはならない。どれだけアクセスが少なかろうとも、SQLインジェクション攻撃が成立してしまった時の被害の大きさに違いは無いからである!油断しないこと!それが一番大事なSQLインジェクション対策であると言える。

その他の対策としては、データベースのセキュリティを徹底したり、大事な情報(パスワードやクレジットカード番号など)をアプリケーション側で暗号化してからデータベースに突っ込んだり、ハニーポットを仕掛けておいたり、といった基本的なことが考えられる。O/Rマッパーを使うのも有効である。(フィルタが備わっていたり、自動的にプリペアードステートメントを使ってくれたりすることがあるため。)

まとめと宣伝

SQLインジェクションはとても恐ろしい攻撃であり、どのようなデータベース製品であろうとも、ひとたびSQLが書き換えられてしまうとそれを防ぐことは出来ない。そのためにはWebアプリケーション側でしっかりと対策をしておく必要がある。本稿ではSQLインジェクションの原理と対策について説明したが、何よりも重要なことは皆がSQLインジェクションの驚異を認識し、危機感を持つことである。そして、皆で協力し、対策を講じるためのノウハウがWebに蓄積していくことが重要だと思う。この投稿も皆さんのお役に立てれば幸いである。

現在執筆中の書籍にも、SQLインジェクションの話が出てくるのだが、この記事よりもうちょっと(かなり?)詳しく説明している。春に発売する予定なので乞うご期待!!皆さん買って下さいw

0 件のコメント:

コメントを投稿