6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SQLインジェクションの対策

Posted at

##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インジェクションの脆弱性を作り込みます。

app/models/sample.rb
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文の実行を防ぎます。

app/models/sample.rb
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インジェクションを突く攻撃コードだとしても文字列として扱われるため安全になります。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?