##SQLインジェクション
アプリケーションのSQLの呼び出し方においてセキュリティ上の不備を意図的に利用し、データベースシステムを不正に操作する攻撃方法のことです。
入力フォームから送信した値によりアプリケーションが想定しないSQL文を実行させ、攻撃を行います。
アプリケーション上にSQLインジェクションの脆弱性があることによって、以下のような被害を受ける可能性があります。
①データベース内のすべての情報が外部から盗まれる
②データベースの内容が書き換えられる
③IDとパスワードを用いずにログインされる(不正ログイン)
④その他、データベースサーバー上のファイルの読み出し、書き込み、プログラムの実行などを行われる
##SQLインジェクションの攻撃例と対策
SQLインジェクションの原因は、ユーザからの入力がアプリケーション内部でSQL文として解釈されてしまい、実行されてしまうことです。
そのため、SQLインジェクション脆弱性を解消するためには、SQL文を組み立て実行される際にSQL文を変更されることを防ぐことです。
SQL文の変更を防ぐには以下の対策方法があります。
①SQL文中で意味を持つ特殊文字をエスケープする
②バインド機構を利用する
###SQL文中で意味を持つ特殊文字をエスケープする
SQL文中では「'」が文字列の終端、「/」がエスケープ文字を意味しています。例えばデータベース内の格納されたuser_idとpasswordが一致したら、認証を認めるアプリケーションとします。
ここで、認証に用いられるSQL文は以下になります。
# $uidと$pwdはユーザのフォームの入力値
SELECT * FROM user WHERE user_id='$uid' AND password='$pwd'
ここに、以下のように入力します。
$uid tarou
$pwd ' OR 'A'='A
すると前述のSQL文は以下のようになります。
SELECT * FROM user WHERE user_id='tarou' AND password='' OR 'A'='A'
もし、特殊文字である「'」をエスケープしていなかった場合、WHERE句の中ではuser_id='tarou' AND password=''と'A'='A'の二つの条件を判定することになります。この二つの文はORでつながっているため、どちらかがtrueの場合は認証が認められてしまいます。'A'='A'は確実にtrueのため、user_idとpasswordが一致していなくても認証が通ってしまいます。
しかし、特殊文字をきちんとエスケープした場合はユーザからの入力の「'」は単なる文字列として扱われるため、password=' ' OR 'A'='A(文字列) 'となり、SQL文は実行されません。
###バインド機構を利用する
バインド機構とは、ユーザからの値を割り振る前に、そのままデータベースを処理するところに送られ、SQL文の構造を確定する仕組みです。
SQL文確定後にユーザからの値を入れて実行するためユーザの入力値によってSQL文が変更されることはありません。
先ほどの例を用いると、以下のSQL文を処理を行う前にデータベースエンジンに送信します。
ここで、user_idとpasswordの値として「?」が使われていますが、これをプレースホルダーといいます。プレースホルダーはユーザの値が入力されるまでの一時的な値のようなものとなります。
SELECT * FROM user WHERE user_id=? AND password=?
こうすることによって、「ユーザテーブルにおいてuser_idとpasswordの組み合わせが一致するかどうかを判定できる」という構文が確定します。
つまり、この後ユーザからどんな値が入力されようとも上記の構文の中でSQLが実行されることになり、SQLインジェクションを防ぐことが可能になります。
##RailsでのSQLインジェクション対策
Railsではヘルパーメソッドが優秀なため、Railsアプリケーションで使用したActiveRecordを使用する場合はRailsアプリケーションにおいてSQLインジェクションが問題になることはほとんどありません。
しかしActiveRecordの発行するSQL文では、アプリケーションの機能として要件を満たせない場合があります。そういった場合は直接SQL文を記述しなければなりません。
この時に使われるのがfind_by_sqlです。そして、このfind_by_sqlメソッドにSQLインジェクションの脆弱性が潜んでいるので、使用する際は注意が必要です。
###find_by_sql
SQL文を直接書いて、レコードを取得するメソッドです。
以下はusersテーブルから全てのレコードを取得する処理をfind_by_sqlメソッドを使った例です。ActiveRecordとは異なり直接SQL文を記述します。
User.find_by_sql('select * from users')
例として今回は、検索機能にSQLインジェクションの脆弱性を作り込みます。
def self.search(search)
return Sample.all unless search
Sample.find_by_sql("select * from samples where text like '%#{search}%' ")
end
次に、検索のテキストボックスに以下のように入力して実行します。
' OR 'A'='A
すると存在するテキストがすべて検索されてしまいます。
先ほどの記述は最初のシングルクォーテーションでプログラム側のSQL文を終了させ、OR 'A'='Aの部分でtrueにすることによって、処理を行います。よってすべてのテキストが表示されます。
このように外部からの入力でSQL文を実行されては非常に危険です。そこで、プレースホルダーを使い外部からのSQL文の実行を防ぎます。
def self.search(search)
return Sample.all unless search
search = "%#{search}%"
Sample.find_by_sql(["select * from samples where text like ? ", search])
end
searchという変数に入力値の仮引数searchを展開し、それをLIKE句で検索するために%で囲っています。
find_by_sql内ではプレースホルダー ? をいれることによって、ユーザの入力を適用する前にSQL文を確定します。[]はプレースホルダーを利用するために用いています。
先ほど入力したようにテキストボックスに以下を入力します。
' OR 'A'='A
すると今度は検索結果になにも表示されなくなっています。
これは入力値である「' OR 'A'='A」が単純な文字列として扱われているので、sampleの中に一致するものはないからです。
このようにプレースホルダーを使うことによって、SQL文を入力値を適用する前に確定するので、その後入力されるものがSQLインジェクションを突く攻撃コードだとしても文字列として扱われるため安全になります。