SQLインジェクション
SQLインジェクション(英: SQL Injection)とは、アプリケーションのセキュリティ上の不備を意図的に利用し、アプリケーションが想定しないSQL文を実行させることにより、データベースシステムを不正に操作する攻撃方法のこと。また、その攻撃を可能とする脆弱性のことである。
SQLインジェクション - wikipedia
基本情報処理技術者試験の問題に出てくるから用語と意味は覚えたけど、具体的にどんな風に攻撃するの?どんな風に対策するの?という疑問があったので、実際に試してみました。
仕組み
ブラウザ上からパスワードを入力してログインするシステムを考えます。サーバー側ではパスワードが一致するユーザーをデータベース上から探します。SQL文はこんな感じ。
SELECT * FROM userdata WHERE password = '入力されたパスワード';
SQLインジェクションの攻撃を受けてしまう原因は「入力されたパスワード」を__そのまま__SQL文に代入してしまうからです。
攻撃者がhoge' OR 'a' = 'a
というパスワードを入力すると、上記のSQLは
SELECT * FROM userdata WHERE password = 'hoge' OR 'a' = 'a';
となります。
本来であればhoge' OR 'a' = 'a
という値をパスワードに持つレコードを検索させたいのですが、シングルクォーテーション自体が特殊な意味を持ってしまっているために、システム設計者が意識したSQL文とは異なるSQL文が出来上がってしまったわけです。
条件に注目すると、password='hoge'
または 'a'='a'
のときのユーザー情報をDBから取得できます。後者の条件は常に真となるので、攻撃者は実際のパスワードを知らずとも、ログインに成功してしまうという訳です。
ポイントはシングルクォーテーションが攻撃者入力のパスワードに含まれている点です。シングルクォーテーションで囲まれた文字は値を表す、という性質からシングルクォーテーション自体を特殊な文字としてSQLは定義しています。
実際にやってみた
Grails使いましたが、中身はシンプルなので知らなくても雰囲気掴めると思います。
テーブル
UserInformation
カラム名 | 型 | 意味 |
---|---|---|
password | String | ログイン用パスワード |
テーブルにはパスワードが12345 のレコードを1件追加しておきました。つまりパスワード12345 を入力すればログイン成功,それ以外はログイン失敗となるという事です。 |
リクエストパラメータ
パラメータ名 | 意味 |
---|---|
password | ログイン用パスワード |
脆弱性のあるプログラム
class DbaccessController {
def login() {
//SQLクエリの実行
def user = UserInformation.find("SELECT * FROM UserInformation WHERE password = 'params.password'")
//検索結果にマッチするレコードがあれば、ログイン成功
if(null == user){
render 'ログイン失敗'
}else{
render 'ログイン成功'
}
}
}
login()はリクエストパラメータとして受け取ったパスワードにマッチするユーザーがいればログイン成功、そうでない場合はログイン失敗の表示を出力します。
で、問題のSQL文はこれです。
SELECT * FROM UserInformation WHERE u.password = 'params.password'
paramsという変数がリクエストパラメータを表しています。リクエストパラメータのpasswordがDBのpasswordとマッチするレコードがあるか検索しています。
アクセス事例
普通のアクセス例、攻撃者のアクセス例です。URLの?
以降がリクエストパラメータを表しています。
ログイン成功
http://localhost:8080/dbaccess/login?password=12345
リクエストパラメータに正しいpasswordが設定されています。
ログイン失敗
http://localhost:8080/dbaccess/login?password=56789
リクエストパラメータに誤ったpasswordが設定されています。
攻撃(ログイン成功)
http://localhost:8080/dbaccess/login?password=56789' or 'a'='a
SQLインジェクション対策
ポイントはシンプルで、シングルクォーテーションが意味のある文字だと認識されなければ良い訳です。エスケープ処理を行ったり、プリペアドステートメント、ORMという仕組みを利用する事で解決されます。
GrailsではDB検索の箇所を以下のように記述することでシングルクォーテーションを無意味な文字列にできます。
SQLインジェクション対策を行ったプログラム
class DbaccessController {
def login() {
//SQLクエリの実行
def user = UserInformation.find("SELECT * FROM UserInformation WHERE password = ?",[params.password])
//検索結果にマッチするレコードがあれば、ログイン成功
if(null == user){
render 'ログイン失敗'
}else{
render 'ログイン成功'
}
}
}
プログラム中の?
にparams.password
を代入してくれるのですが、代入過程で自動的にエスケープ処理をしてくれます。これにより、params.password
中のシングルクォーテーションがただの文字列だと認識されるようになり、SQLインジェクション対策となります。