hiyoko-programingの日記

プログラミングを勉強したてのひよっ子。   エンジニア目指して勉強中。

SQLインジェクション

SQLインジェクションとは

SQLインジェクションとは、アプリケーションのSQLの呼び出し方において、セキュリティ上の不備を意図的に利用し、データベースシステムを不正に操作する攻撃方法のこと。
入力フォームから送信した値によりアプリケーションが想定しないSQL文を実行させ、攻撃を行う。

アプリケーション上にSQLインジェクション脆弱性があることによって、以下のような被害を受ける可能性がある。

  • データベース内のすべての情報が外部から盗まれる
  • データベースの内容が書き換えられる
  • IDとパスワードを用いずにログインされる(不正ログイン)
  • その他、データベースサーバー上のファイルの読み出し、書き込み、プログラムの実行などを行われる

https://tech-master.s3.amazonaws.com/uploads/curriculums//9d35e2feb4b4a5e84f2a84e085f6e090.jpeg

SQLインジェクションの攻撃例・対策

SQLインジェクション攻撃によってパスワードを用いずに認証を回避し不正アクセスするという攻撃を例にとり、対策例を説明。

一般的な対策方法

SQLインジェクションの原因は、ユーザからの入力がアプリケーション内部でSQL文として解釈されてしまい、実行されてしまうことが挙げられる。
そのため、SQLインジェクション脆弱性を解消するためには、SQL文を組み立て実行される際にSQL文を変更されることを防ぐことである。
SQL文の変更を防ぐには以下の対策方法がある。

SQL文中で意味を持つ特殊文字エスケープする

SQL文中では「'」が文字列の終端、「/」がエスケープ文字を意味している。例えばデータベース内の格納されたuser_idとpasswordが一致したら、認証を認めるアプリケーションを考えてみる。
ここで、認証に用いられるSQL文は以下になる。

1
2
# $uidと$pwdはユーザのフォームの入力値
SELECT * FROM user WHERE user_id='$uid' AND password='$pwd' 

ここに、以下のように入力。

1
2
$uid  tarou
$pwd  ' OR 'A'='A

するとSQL文は以下のようになる。

1
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の値として「?」が使われているが、これをプレースホルダーという。プレースホルダーはユーザの値が入力されるまでの一時的な値のようなものとなる。

1
SELECT * FROM user WHERE user_id=? AND password=?

こうすることによって、「ユーザテーブルにおいてuser_idとpasswordの組み合わせが一致するかどうかを判定できる」という構文が確定する。
つまり、この後ユーザからどんな値が入力されようとも上記の構文の中でSQLが実行されることになり、SQLインジェクションを防ぐことが可能になる。

https://tech-master.s3.amazonaws.com/uploads/curriculums//be530471fd8b30e3243a2d7cd0664a2d.jpeg

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文を記述する。

1
User.find_by_sql('select * from users')

実際にfind_by_sqlメソッドにSQLインジェクション脆弱性が潜んでいることを検索機能付きのPictweetを例に見ていく。ツイートの検索機能にSQLインジェクション脆弱性を作り込んでみる。

 ディレクトリを移動

まずは、検索機能付きのPictweetのディレクトリに移動する。

 ビジネスロジックを編集

tweet.rbのsearchメソッドを以下のように編集。

app/models/tweet.rb
1
2
3
4
def self.search(search)
  return Tweet.all unless search
  Tweet.find_by_sql("select * from tweets where text like '%#{search}%' ")
end

編集が終ったら、 localhost:3000 にアクセス。もしデータが存在していないようであれば、適当に複数データを用意しておく。

 SQLインジェクションを行う

次に、検索のテキストボックスに以下のように入力して実行。

1
' OR 'A'='A

すると以下のように、存在するtweetがすべて検索されている。

https://tech-master.s3.amazonaws.com/uploads/curriculums//751eb14b6e9fe6bf06992e1b69ef64f4.gif

この記述は最初のシングルクォーテーションでプログラム側のSQL文を終了させ、OR 'A'='Aの部分でtrueにすることによって、処理を行う。よってすべてのtweetが表示されているということになる。

このように外部からの入力でSQL文を実行されては非常に危険である。そこで、プレースホルダーを使い外部からのSQL文の実行を防ぐ。

 SQLインジェクションの対策を行う

tweet.rbのsearchメソッドを以下のように編集。

app/models/tweet.rb
1
2
3
4
5
 def self.search(search)
    return Tweet.all unless search
    search = "%#{search}%"
    Tweet.find_by_sql(["select * from tweets where text like ? ", search])
  end

searchという変数に入力値の仮引数searchを展開し、それをLIKE句で検索するために%で囲っている。
find_by_sql内ではプレースホルダー ? をいれることによって、ユーザの入力を適用する前にSQL文を確定する。[]プレースホルダーを利用するために用いる。

先ほど入力したようにテキストボックスに以下を入力。

1
' OR 'A'='A

すると今度は以下のように検索結果になにも表示されていないはず。

https://tech-master.s3.amazonaws.com/uploads/curriculums//562f0717cd930fd834c03848be5fbc8f.gif

これは入力値である「' OR 'A'='A」が単純な文字列として扱われているので、tweetの中に一致するものはないからである。
このようにプレースホルダーを使うことによって、SQL文を入力値を適用する前に確定するので、その後入力されるものがSQLインジェクションを突く攻撃コードだとしても文字列として扱われるため安全になる。

まとめ

  • SQLインジェクションとは外部からの入力により、アプリケーションが想定しないSQL文を実行することによって、認証を突破したり情報を盗んだりする攻撃手法のことである。
  • SQLインジェクションを防ぐには、SQL文中で意味をもつ「`」や「/」のエスケープやバインド機構を用いることが必要である。
  • Railsアプリケーションでfind_by_sqlを用いる際は正しい対策を講じることが重要である。