MeWrite Docs

SQL Injection: WAF blocked request

SQLインジェクション攻撃パターンが検出された場合のエラー

概要

Webアプリケーションファイアウォール(WAF)がSQLインジェクション攻撃パターンを検出してリクエストをブロックした場合のエラーです。正当なリクエストが誤検出される場合もあります。

エラーメッセージ

403 Forbidden - Request blocked by WAF

または

AWS WAF: Request blocked - SQL injection detected

原因

  1. 実際の攻撃: 悪意あるSQLインジェクションの試み
  2. 誤検出: 正当な入力がSQLパターンに一致
  3. ルールが厳しすぎる: WAFルールの設定が過剰
  4. エスケープ不足: アプリケーション側の対策不備

解決策

1. プリペアドステートメントを使用

1
2
3
4
5
6
7
8
9
// Node.js (mysql2)
// 悪い例
const query = `SELECT * FROM users WHERE id = ${userId}`;

// 良い例:プリペアドステートメント
const [rows] = await connection.execute(
  'SELECT * FROM users WHERE id = ?',
  [userId]
);

2. ORMを使用

1
2
3
4
5
6
7
8
9
// Prisma
const user = await prisma.user.findUnique({
  where: { id: userId }
});

// Sequelize
const user = await User.findOne({
  where: { id: userId }
});

3. Python での対策

1
2
3
4
5
6
7
8
# 悪い例
query = f"SELECT * FROM users WHERE id = {user_id}"

# 良い例:パラメータ化クエリ
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

# SQLAlchemy ORM
user = session.query(User).filter(User.id == user_id).first()

4. PHP での対策

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 悪い例
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];

// 良い例:PDO プリペアドステートメント
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]);
$user = $stmt->fetch();

// Laravel Eloquent
$user = User::find($id);
$users = User::where('status', $status)->get();

5. 入力バリデーション

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 型と形式の検証
function validateUserId(id) {
  // 数値のみ許可
  if (!/^\d+$/.test(id)) {
    throw new Error('Invalid user ID');
  }
  return parseInt(id, 10);
}

// express-validator
const { param, validationResult } = require('express-validator');

app.get('/users/:id',
  param('id').isInt({ min: 1 }),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // 安全に処理
  }
);

6. WAFルールの調整

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// AWS WAF ルール
{
  "Name": "SQLiRule",
  "Priority": 1,
  "Statement": {
    "SqliMatchStatement": {
      "FieldToMatch": {
        "Body": {}
      },
      "TextTransformations": [
        {
          "Priority": 0,
          "Type": "URL_DECODE"
        },
        {
          "Priority": 1,
          "Type": "HTML_ENTITY_DECODE"
        }
      ]
    }
  },
  "Action": {
    "Block": {}
  }
}

7. ホワイトリスト方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 許可されたカラム名のみ
const allowedColumns = ['name', 'email', 'created_at'];

function buildOrderByClause(column, direction) {
  if (!allowedColumns.includes(column)) {
    throw new Error('Invalid column');
  }
  if (!['ASC', 'DESC'].includes(direction.toUpperCase())) {
    throw new Error('Invalid direction');
  }
  return `ORDER BY ${column} ${direction}`;
}

8. エスケープ(最後の手段)

1
2
3
4
5
6
7
// mysql2 のエスケープ
const mysql = require('mysql2');
const escaped = mysql.escape(userInput);

// PostgreSQL
const { escape } = require('pg-format');
const query = format('SELECT * FROM %I WHERE name = %L', tableName, userName);

9. エラーメッセージの非公開

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 悪い例:詳細なエラーを露出
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message, stack: err.stack });
});

// 良い例:汎用的なエラーメッセージ
app.use((err, req, res, next) => {
  console.error(err);  // ログには記録
  res.status(500).json({ error: 'Internal server error' });
});

10. セキュリティテスト

1
2
3
4
5
# sqlmap でテスト(許可された環境のみ)
sqlmap -u "http://localhost/api/users?id=1" --batch

# OWASP ZAP でスキャン
zap-cli quick-scan http://localhost

よくある間違い

  • 動的クエリでテーブル名やカラム名を直接連結
  • エラーメッセージでSQLエラーの詳細を露出
  • クライアント側のみでバリデーション
  • 「特殊文字をエスケープすれば安全」と過信

Security の他のエラー

最終更新: 2025-12-09