👻

SQLインジェクション ってなに?

に公開

SQLインジェクション ってなに?

SQLインジェクション(SQL Injection)とは、ウェブアプリケーションのセキュリティ脆弱性を利用して、悪意のあるユーザーがSQLクエリを操作し、データベースに対して不正な操作を行う攻撃手法です。
この攻撃により、攻撃者はデータベース内の機密情報を取得したり、データを削除・改ざんしたり、さらにはデータベース自体を操作したりすることができます。

例え話

図書館を想像してみてください。
図書館には大きなカードボックスがあって、みんなの名前と借りた本の情報が書いてあります。

  1. 通常の使い方
    図書館の人に「自分の今まで借りた本を教えてください」とお願いすると、図書館の人はカードを探して「あなたは『ドラえもん』を借りています」と教えてくれます。

  2. SQLインジェクションの使い方
    悪い人は「自分」または「みんなの情報を全部見せて」とお願いします。

    図書館の人は混乱して、あなたの情報だけじゃなくて、みんなの秘密の情報まで全部見せてしまうみたいなイメージです。

コンピュータでの例

  1. お店のウェブサイトで

    • 通常は「商品ID: 42」で検索すると、その商品だけが表示されます
    • 悪い人は「商品ID: 42 または全ての商品とお客さんの情報も見せて」と入力します
    • 最悪のケース、全ての秘密の情報を見せてしまいます
  2. ログインするとき

    • 通常は「ユーザー名とパスワード」を入れます
    • 悪い人は「どんなユーザー名でも、パスワードはなんでも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 = '何か';

この文は:

  1. username = '' (空のユーザー名) OR
  2. '1'='1' (これは常に真)
  3. -- 以降はコメントアウトされるので実行されない

つまり「全てのユーザー」を返すという結果になり、認証がバイパスされる。

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';

通常、正しい usernamepassword を入力するとログインできます。
しかし、次のように usernameadmin' AND 1=1 -- を入力すると…

SELECT * FROM users WHERE username = 'admin' AND 1=1 -- ' AND password = 'password';

これは常に TRUE となるため、データが取得できる可能性があります。
逆に admin' AND 1=0 -- を入力すると FALSE となるため、データは取得できません。

攻撃の流れ

  1. admin' AND 1=1 -- を入力 → ログイン成功(TRUE)なら、admin ユーザーは存在すると推測。
  2. admin' AND 1=0 -- を入力 → ログイン失敗(FALSE)なら、条件式が機能していると確認。
  3. admin' AND (SELECT LENGTH(password) FROM users WHERE username='admin') > 5 -- を入力 →
    ページの変化を見て password の長さを推測。
  4. さらに 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(偽)の場合 → 遅延なし

攻撃の流れ

  1. admin' AND IF(1=1, SLEEP(5), 0) -- を入力 → 5秒遅延なら条件が TRUE と判断。
  2. admin' AND IF(1=0, SLEEP(5), 0) -- を入力 → 遅延なしなら条件が FALSE と判断。
  3. admin' AND IF((SELECT LENGTH(password) FROM users WHERE username='admin') > 5, SLEEP(5), 0) --
    → 5秒遅延なら password の長さは5文字より大きい。
  4. 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_bywhere などのメソッドチェーンを使うことで、安全にデータを取得できます。

安全なコード

User.find_by(email: user_params[:email])
  • find_by は自動的にエスケープ処理を行うため、SQL インジェクションを防げます。

危険なコード

User.where("email = '#{params[:email]}'").first
  • 直接 SQL を埋め込むのは危険。

3. sanitize 系メソッドを使う

Rails には、SQL のエスケープを手動で行うための sanitize_sql_for_conditionssanitize_sql_arraysanitize などのメソッドが用意されています。
これらは、手動で 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 を組み立てる場合や LIKEorder を使う際など どのようなSQLクエリが発行されるかなど注意が必要です。
また、本番環境では詳細なエラーメッセージを表示しないことなど、攻撃者に情報を与えないようにするのも重要です。

安全なコーディングを心がけましょう! 🚀

Discussion