というわけで、今日は改めて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 + "'"
攻撃例
例えばユーザーとして「nippondanji」、パスワードに「fundoshi」という文字列を代入したとしよう。すると、このコードが生成するSQL文(つまりデータベースに投げられるもの)は次のようなものになる。- SELECT user, pass FROM users WHERE user = 'nippondanji' AND pass = 'fundoshi'
- クォーテーションを文字列の中に混入させる。これにより、攻撃者は新たなSQLの文法を注入=インジェクトすることが可能になる。
- SQL文を途中でコメントアウトしてしまう。これにより、文法的に正しいSQL文を組み立てやすくなる。
- OR 1=1など恒常的に成り立つ条件を利用する。これにより、WHERE句の条件を無視してしまう。
- UNIONを用いて任意の情報を入手する。
- INFORMATION_SCHEMAへアクセスしてどのようなテーブルが存在するかといった情報を入手する。
- セミコロンを使って複数のSQL文を記述し、任意の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 + "')"
- SELECT user, password FROM users WHERE MATCH(first_name, last_name) AGAINST('Mikiya')
- SELECT user, pass FROM users WHERE MATCH(first_name, last_name) AGAINST('') LIMIT 0 UNION SELECT user, first_name, last_name, 1 FROM users ORDER BY RAND() LIMIT 10 #')
このように、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