SQLインジェクション ってなに?
SQLインジェクション(SQL Injection)とは、ウェブアプリケーションのセキュリティ脆弱性を利用して、悪意のあるユーザーがSQLクエリを操作し、データベースに対して不正な操作を行う攻撃手法です。
この攻撃により、攻撃者はデータベース内の機密情報を取得したり、データを削除・改ざんしたり、さらにはデータベース自体を操作したりすることができます。
例え話
図書館を想像してみてください。
図書館には大きなカードボックスがあって、みんなの名前と借りた本の情報が書いてあります。
-
通常の使い方:
図書館の人に「自分の今まで借りた本を教えてください」とお願いすると、図書館の人はカードを探して「あなたは『ドラえもん』を借りています」と教えてくれます。 -
SQLインジェクションの使い方:
悪い人は「自分」または「みんなの情報を全部見せて」とお願いします。図書館の人は混乱して、あなたの情報だけじゃなくて、みんなの秘密の情報まで全部見せてしまうみたいなイメージです。
コンピュータでの例
-
お店のウェブサイトで:
- 通常は「商品ID: 42」で検索すると、その商品だけが表示されます
- 悪い人は「商品ID: 42 または全ての商品とお客さんの情報も見せて」と入力します
- 最悪のケース、全ての秘密の情報を見せてしまいます
-
ログインするとき:
- 通常は「ユーザー名とパスワード」を入れます
- 悪い人は「どんなユーザー名でも、パスワードはなんでもOK」というような特別な言葉を入れます
- すると、パスワードを知らなくても入れてしまうことがあります
はい、こんな感じになりますが 詳しく説明↓↓↓
詳細解説
基本的なSQL文の例
データベースへの一般的な問い合わせは以下のような形です:
SELECT * FROM users WHERE username = 'tanaka' AND password = 'secret123';
この問い合わせは「usersテーブルからユーザー名が'tanaka'でパスワードが'secret123'のデータを取得する」という意味です。
悪用の例
攻撃者は入力フィールドに以下のような値を入力します:
' OR '1'='1' --
すると、最終的なSQL文はこうなります:
SELECT * FROM users WHERE username = '' OR '1'='1' -- ' AND password = '何か';
この文は:
-
username = ''
(空のユーザー名) OR -
'1'='1'
(これは常に真) -
--
以降はコメントアウトされるので実行されない
つまり「全てのユーザー」を返すという結果になり、認証がバイパスされる。
SQLインジェクションの種類
1. クラシカルSQLインジェクション(Classic SQL Injection)
概要
ユーザー入力をそのままSQL文に組み込むことで、不正なSQLコードを実行させる基本的な手法です。
例
SELECT * FROM users WHERE username = 'admin' AND password = 'password';
ユーザーが admin' --
という入力をすると、以下のようにSQL文が変わり、パスワードチェックが回避されます。
SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'password';
※ --
はSQLのコメント記号なので、それ以降の条件(パスワードチェック)が無視される。
2. ブラインドSQLインジェクション(Blind SQL Injection)
ブラインドSQLインジェクションは、エラーメッセージやデータの直接的な表示がない場合に、システムの動作や応答時間の違いを利用してデータベースの情報を推測する攻撃手法 です。
通常のSQLインジェクションは、エラーメッセージや取得したデータを見て攻撃を行いますが、ブラインドSQLインジェクションでは、サーバーのレスポンス(成功・失敗の違い、応答時間の変化)から間接的に情報を得るのが特徴です。
ブラインドSQLインジェクションの種類
ブラインドSQLインジェクションには、ブールベース(Boolean-based) と 時間ベース(Time-based) の2種類があります。
1. ブールベースSQLインジェクション(Boolean-based SQL Injection)
概要
データベースのクエリの結果が「真(TRUE)」か「偽(FALSE)」かによって、Webページの挙動が変化することを利用してデータを推測する手法。
例
SELECT * FROM users WHERE username = 'admin' AND password = 'password';
通常、正しい username
と password
を入力するとログインできます。
しかし、次のように username
に admin' AND 1=1 --
を入力すると…
SELECT * FROM users WHERE username = 'admin' AND 1=1 -- ' AND password = 'password';
これは常に TRUE
となるため、データが取得できる可能性があります。
逆に admin' AND 1=0 --
を入力すると FALSE
となるため、データは取得できません。
攻撃の流れ
-
admin' AND 1=1 --
を入力 → ログイン成功(TRUE)なら、admin
ユーザーは存在すると推測。 -
admin' AND 1=0 --
を入力 → ログイン失敗(FALSE)なら、条件式が機能していると確認。 -
admin' AND (SELECT LENGTH(password) FROM users WHERE username='admin') > 5 --
を入力 →
ページの変化を見てpassword
の長さを推測。 - さらに
admin' AND (ASCII(SUBSTRING(password,1,1)) > 100) --
などを使い、
パスワードの各文字のASCII値を推測し、パスワード全体を取得。
📌 ポイント
- エラーメッセージが表示されなくても、ページの挙動の違いを利用して情報を抜き取ることができる!
2. 時間ベースSQLインジェクション(Time-based SQL Injection)
概要
SQLの SLEEP()
や BENCHMARK()
関数を使い、意図的に処理時間を遅延させることで、条件の真偽を判定する手法。
例
以下のSQL文を実行すると、条件が TRUE
なら5秒遅延し、FALSE
ならすぐに応答します。
SELECT * FROM users WHERE username = 'admin' AND IF(1=1, SLEEP(5), 0);
✅ 1=1
(真)の場合 → 5秒遅延
❌ 1=0
(偽)の場合 → 遅延なし
攻撃の流れ
-
admin' AND IF(1=1, SLEEP(5), 0) --
を入力 → 5秒遅延なら条件がTRUE
と判断。 -
admin' AND IF(1=0, SLEEP(5), 0) --
を入力 → 遅延なしなら条件がFALSE
と判断。 -
admin' AND IF((SELECT LENGTH(password) FROM users WHERE username='admin') > 5, SLEEP(5), 0) --
→ 5秒遅延ならpassword
の長さは5文字より大きい。 -
admin' AND IF(ASCII(SUBSTRING(password,1,1)) > 100, SLEEP(5), 0) --
→ 1文字目のASCIIコードを判定し、1文字ずつパスワードを特定。
📌 ポイント
- エラーメッセージを一切表示しないWebアプリでも、レスポンス時間を測ることでデータを盗み出せる!
- ログにエラーメッセージが残らないため、攻撃が発見されにくい!
ブラインドSQLインジェクションの危険性
- データ漏洩:ユーザー名やパスワードなどの機密情報が推測される。
- 検出が難しい:エラーメッセージを出さず、通常のリクエストと区別しづらい。
- 時間がかかるが確実に攻撃できる:1文字ずつ推測するため、完全な情報を得るまでに時間がかかるが、確実に情報を得ることが可能。
3. エラーベースSQLインジェクション(Error-based SQL Injection)
概要
エラーメッセージの内容からデータベースの情報を取得する手法。
例
SELECT * FROM users WHERE id = 1 UNION SELECT 1, database(), 3, 4;
もしこのSQLが実行されると、エラーメッセージの中に database()
の結果(現在のデータベース名)が表示される可能性がある。
4. ユニオンベースSQLインジェクション(Union-based SQL Injection)
概要
UNION
を使用して、攻撃者が意図的に追加したデータを取得する手法。
例
SELECT id, username, password FROM users WHERE id = 1 UNION SELECT 1, 'hacker', 'password';
このSQLが実行されると、データベース内に本来存在しない ('hacker', 'password')
という行が結果として表示される可能性がある。
5. スタックドクエリSQLインジェクション(Stacked Query SQL Injection)
概要
複数のSQLクエリを ;
で区切って連続実行させることで、データの改ざんや削除を行う手法。
例
SELECT * FROM users WHERE username = 'admin'; DROP TABLE users;
もし admin'; DROP TABLE users; --
という入力を受け付けると、users
テーブルが削除される可能性がある。
このようにSQLインジェクションには多くの種類があり、それぞれ異なる手法でデータを不正に取得・改ざんします。防ぐためには、適切なプログラミング(プレースホルダーの使用)やWAFの導入が重要です。
SQLインジェクション攻撃の検出
1. WAF(Webアプリケーションファイアウォール)
WAFを使用して、SQLインジェクション攻撃のパターンを検出してブロックします。
2. ログの監視
不審なデータベースクエリや失敗したログイン試行をモニタリングします。
3. 自動テスト
ツールなど使用して、自分のアプリケーションの脆弱性を定期的にテストします。
SQLインジェクションはウェブアプリケーションに対する最も一般的で危険な攻撃の一つです。
適切な対策を講じることで、アプリケーションとユーザーデータを保護することができます。
SQLインジェクションの対策 防御方法
1. プレースホルダー(プリペアドステートメント)を使う
✅ 安全なコード
Ruby on Rails
User.where("email = ?", user_params[:email])
-
?
を使うことで、入力値が自動的にエスケープされるため、SQL インジェクションが防止されます。
❌ 危険なコード
User.where("email = '#{params[:email]}'")
- 文字列を直接埋め込むと、SQL インジェクションのリスクがあります。
Django の ORM を利用の場合
from myapp.models import User
# ユーザー入力
email = "user@example.com"
# ✅ 安全なクエリ
user = User.objects.get(email=email)
2. クエリのメソッドチェーンを使う
Rails では find_by
や where
などのメソッドチェーンを使うことで、安全にデータを取得できます。
✅ 安全なコード
User.find_by(email: user_params[:email])
-
find_by
は自動的にエスケープ処理を行うため、SQL インジェクションを防げます。
❌ 危険なコード
User.where("email = '#{params[:email]}'").first
- 直接 SQL を埋め込むのは危険。
3. sanitize 系メソッドを使う
Rails には、SQL のエスケープを手動で行うための sanitize_sql_for_conditions
、sanitize_sql_array
、sanitize
などのメソッドが用意されています。
これらは、手動で SQL クエリを作成する必要がある場合に便利です。
✅ 安全なコード
safe_sql = ActiveRecord::Base.sanitize_sql(["email = ?", user_params[:email]])
User.where(safe_sql)
-
sanitize_sql
を使うことで、ユーザー入力が安全に処理されます。
❌ 危険なコード
User.where("email = '#{params[:email]}'")
- 直接変数を埋め込むのは危険。
4. ストロングパラメーター(Strong Parameters)を活用
Rails では ストロングパラメーター を使うことで、不正なパラメーターの混入を防ぐことができます。
✅ 安全なコード
params.require(:user).permit(:email, :password)
- 許可されたパラメーターのみを受け取るため、不正なデータをブロックできます。
5. SQL の LIKE クエリを使うときの注意
LIKE
クエリを使う場合、%
や _
などのワイルドカードを適切に処理しないと、意図しない SQL が実行される可能性があります。
❌ 危険なコード
User.where("email LIKE '%#{params[:email]}%'")
-
params[:email]
に%
を含めると、すべてのデータが取得される可能性がある。
✅ 安全なコード
Rails には sanitize_sql_like
メソッドがあり、ワイルドカードを適切に処理できます。
User.where("email LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(user_params[:email])}%")
-
sanitize_sql_like
を使うことで、%
や_
を安全に処理できます。
6. order
などでの SQL インジェクション防止
order
メソッドにユーザー入力を直接渡すと、SQL インジェクションのリスクがあります。
❌ 危険なコード
User.order("created_at #{params[:order]}") # 'ASC' or 'DESC' のみ許可すべき
- ユーザーが
'; DROP TABLE users; --
などを入力すると、データベースを破壊できる可能性がある。
✅ 安全なコード
if %w[ASC DESC].include?(user_params[:order].upcase)
User.order("created_at #{user_params[:order].upcase}")
else
User.order("created_at DESC") # デフォルトの値
end
- 許可する値を限定することで、SQL インジェクションを防ぐ。
7. エラーメッセージの制限
本番環境では 詳細なエラーメッセージを表示しない ようにすることで、攻撃者に情報を与えないようにします。
✅ 安全な設定
config/environments/production.rb
でエラーメッセージを制限する:
config.consider_all_requests_local = false
- これにより、デバッグ情報や SQL のエラーメッセージが本番環境で表示されなくなります。
まとめ
Rails(ActiveRecord)などのフレームワークを正しく使用すれば、SQL インジェクションのリスクを減らせますが、手動で SQL を組み立てる場合や LIKE
・order
を使う際など どのようなSQLクエリが発行されるかなど注意が必要です。
また、本番環境では詳細なエラーメッセージを表示しないことなど、攻撃者に情報を与えないようにするのも重要です。
安全なコーディングを心がけましょう! 🚀