SQLインジェクション攻撃とは
データベース(DB)を利用するWebアプリケーションの多くは、利用者からの入力情報からSQL文を構築し、DBを操作する。このSQL文に問題がある場合、DBの不正利用につながる恐れがある。
上記の問題を『SQLインジェクションの脆弱性』といい、問題を悪用した攻撃を『SQLインジェクション攻撃』という。
発生しうる脅威
SQLインジェクション攻撃により、発生しうる脅威は次のもの。
データベースに蓄積された非公開情報の閲覧
個人情報の漏えい 等
データベースに蓄積された情報の改ざん、消去
ウェブページの改ざん、パスワード変更、システム停止 等
認証回避による不正ログイン
ログインした利用者に許可されている全ての操作を不正に行われる
ストアドプロシージャ等を利用した OS コマンドの実行
システムの乗っ取り、他への攻撃の踏み台としての悪用 等
注意すべきページの特徴
次のようなサイトは特に注意が必要。
DBを利用するウェブアプリケーションを設置しているウェブサイト。個人情報等の重要情報をDBに格納するWeサイトでは、特に注意が必要。
根本的な解決方法
「脆弱性を作り込まない実装」を実現する手法として、次のものがある。
SQL文の組み立ては全てプレースホルダで実装する
SQLインジェクションの対応の分類として、次のものがある。
上記の通り、最も安全なのは静的プレースホルダでプレースホルダは『パラメータ部分を何かしらの記号(リテラル)で示しておき、後で、実際の値を機械的な処理で割り当てる方法』のこという。
中でも静的プレースホルダは、プレースホルダのままのSQL文をデータベースエンジン側にあらかじめ送信して、実行前に、SQL文の構文解析などの準備をしておく方式のこという。そして、SQL実行の段階で、実際のパラメータの値をデータベースエンジン側に送信し、データベースエンジン側にてバインド処理を実施する。イメージ図は次の通り。
ウェブアプリケーションで直接、文字列連結処理によってSQL文を組み立てる方法に比べて、プレースホルダでは、機械的な処理でSQL文が組み立てられるため、SQLインジェクションの脆弱性を解消できる。
なぜ『文字列連結処理』が安全でないのか
rubyで「pg
」gemを用いて『DBから特定のIDの情報を取得する』操作を、次のように記述し、#{id}
は利用者からの入力から決定されるとする。
conn.exec("SELECT * FROM memos WHERE memo_id = '#{id}'").first
この状況において、#{id}
を次のように入力する。
';DELETE FROM memos--
すると、SQLの中身は次のようになり、memos
テーブルのデータが全て削除されてしまう。
SELECT * FROM memos WHERE memo_id = '';DELETE FROM memos--'
『動的プレースホルダ』の使用時には注意が必要
動的プレースホルダは、SQL呼び出しごとに、パラメータをバインドした後のSQL文をデータベースエンジンに送る。
このバインド処理時に、バインド処理を実現するドライバやライブラリによっては、文字エンコーディングの処理が原因で、SQLインジェクションの脆弱性が生じる可能性があるため、動的プレースホルダの使用には注意が必要。
SQL文のリテラルを正しく構成する
SQL文の組み立てを文字列連結により行う場合は、エスケープ処理等を行うデータベースエンジンのAPIを用いて、SQL文のリテラルを正しく構成する。
正しく構成するためには、次の要件を満たす必要がある。
ただ、文字列リテラル生成時にエスケープが必要な文字は、データベースエンジンの種類や、データベースの設定によっても異なる場合がある。そうした違いに対して適切にエスケープ処理を実装しないと脆弱性を産んでしまう。
そこで、アプリケーションのプログラミング言語やデータベースエンジンが提供する、SQLにおけるリテラルを文字列として生成する専用のメソッドや関数を用いると良い。
rubyで対応する例の1つとして、gemで提供されているメソッドを使うという方法がある。pg
gemを用いる場合には、exec_params
メソッドが使える。
exec_params
では、SQLクエリ要求を、パラメータにプレースホルダを使用して、PostgreSQLに送信する。下記のように、パラメータ値をコマンド文字列から分離し、面倒でエラーを起こしやすい引用符付けやエスケープを不要にしてくれる。
require 'pg' conn = PG::Connection.open(:dbname => 'test') res = conn.exec_params('SELECT $1 AS a, $2 AS b, $3 AS c', [1, 2, nil]) # Equivalent to: # res = conn.exec('SELECT 1 AS a, 2 AS b, NULL AS c')
ウェブアプリケーションに渡されるパラメータにSQL文を直接指定しない
『論外』の実装として、hiddenパラメータ等にSQL文をそのまま指定するというものがある。 ウェブアプリケーションに渡されるパラメータにSQL文を直接指定する実装は、そのパラメータ値の改変により、データベースの不正利用につながる可能性がある。
保険的な解決方法
『攻撃による影響を軽減する』のことで、次の影響を軽減する。
項目 | 影響を軽減する具体例 |
---|---|
攻撃される可能性を低減する | 攻撃につながるヒントを与えない |
攻撃された場合に、脆弱性を突かれる可能性を低減する | 入力から攻撃に使われるデータをサニタイズ(無効化)する |
脆弱性を突かれた場合に、被害範囲を最小化する | アクセス制御 |
被害が生じた場合に、早期に知る | 事後通知 |
保険的な解決方法には、次のものがある。
エラーメッセージをそのままブラウザに表示しない
エラーメッセージの内容に、データベースの種類やエラーの原因、実行エラーを起こしたSQL文等 の情報が含まれる場合、これらはSQLインジェクション攻撃につながる有用な情報となってしまう可能性がある。
理由としては、どんなSQLが実行されているかがわかることで、SQLインジェクションを起こさせるSQLクエリを作るヒントを与えてしまうため。
また、 エラーメッセージは、攻撃の手がかりを与えるだけでなく、実際に攻撃された結果を表示する情報源としても悪用される場合があります。
よって、データベースに関連するエラーメッセージは、利用者のブラウザ上に表示させないことが推奨されている。
データベースアカウントに適切な権限を与える
ウェブアプリケーションがデータベースに接続する際に使用するアカウントの権限が必要以上に高 い場合、攻撃による被害が深刻化する恐れがある。
ウェブアプリケーションからデータベースに渡す命令文を洗い出し、その命令文の実行に必要な最小限の権限をデータベースアカウントに与えることが推奨されている。