1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

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クエリが発行されるかなど注意が必要です。
また、本番環境では詳細なエラーメッセージを表示しないことなど、攻撃者に情報を与えないようにするのも重要です。

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?