Grails
基本情報処理技術者試験
SQLインジェクション

「SQLインジェクション」 ~ 基本情報処理技術者試験に出る攻撃方法の実演とその対策 ~

More than 1 year has passed since last update.

SQLインジェクション

SQLインジェクション(英: SQL Injection)とは、アプリケーションのセキュリティ上の不備を意図的に利用し、アプリケーションが想定しないSQL文を実行させることにより、データベースシステムを不正に操作する攻撃方法のこと。また、その攻撃を可能とする脆弱性のことである。
SQLインジェクション - wikipedia

基本情報処理技術者試験の問題に出てくるから用語と意味は覚えたけど、具体的にどんな風に攻撃するの?どんな風に対策するの?という疑問があったので、実際に試してみました。

仕組み

ブラウザ上からパスワードを入力してログインするシステムを考えます。サーバー側ではパスワードが一致するユーザーをデータベース上から探します。SQL文はこんな感じ。

SQL文
SELECT * FROM userdata WHERE password = '入力されたパスワード';

SQLインジェクションの攻撃を受けてしまう原因は「入力されたパスワード」をそのままSQL文に代入してしまうからです。
攻撃者がhoge' OR 'a' = 'aというパスワードを入力すると、上記のSQLは

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 ログイン用パスワード

脆弱性のあるプログラム

DbaccessController.groovy
class DbaccessController {

def login() {
        //SQLクエリの実行
        def user = UserInformation.find("SELECT * FROM UserInformation WHERE password = 'params.password'")
        //検索結果にマッチするレコードがあれば、ログイン成功
        if(null == user){
          render 'ログイン失敗'
        }else{
          render 'ログイン成功'
        }
     }
}

login()はリクエストパラメータとして受け取ったパスワードにマッチするユーザーがいればログイン成功、そうでない場合はログイン失敗の表示を出力します。
で、問題のSQL文はこれです。

SQL文
SELECT * FROM UserInformation WHERE u.password = 'params.password'

paramsという変数がリクエストパラメータを表しています。リクエストパラメータのpasswordDBの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インジェクション対策を行ったプログラム

DbaccessController.groovy
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インジェクション対策となります。