【AWS】WAF で SQL インジェクション(SQLi)防御をする

AWS WAF で SQLi 攻撃を防ぐ方法について解説します。

SQL インジェクション攻撃(SQLi)とは

SQL インジェクション攻撃とは、Webサイトやアプリの入力欄に、攻撃者が SQL 文の一部を混ぜ込んで、データベースを不正に操作しようとする攻撃です。

SQL とは?

SQL は、データベースに問い合わせるための言語です。例えば、ログイン処理ではアプリが裏側でこんな SQL を実行していることがあります。

SELECT *
FROM users
WHERE email = '入力されたメールアドレス'
  AND password = '入力されたパスワード';

ユーザーがログイン画面に入力した値を使って、DB に「このメールアドレスとパスワードのユーザーはいますか?」と確認しています。

SQLi 攻撃のイメージ

問題は、アプリが入力値を安全に扱っていない場合です。

例えば、攻撃者がパスワード欄に以下のような文字列を入れたとします。

' OR '1'='1

すると、アプリの作りが悪い場合、SQL がこんな形に変わってしまう可能性があります。

SELECT *
FROM users
WHERE email = 'attacker@example.com'
  AND password = '' OR '1'='1';

'1'='1' は常に真です。

その結果、本来はパスワードが一致しないとログインできないはずなのに、条件式が崩れて、不正ログインできてしまう可能性があります。

これが SQLi 攻撃の代表的な考え方です。

何が危険なのか?

SQLi が成功すると、以下のような被害につながります。

被害内容
情報漏えいユーザー情報、メールアドレス、個人情報、注文情報などを盗まれる
不正ログイン認証を回避される
データ改ざんDB の内容を書き換えられる
データ削除テーブルやレコードを削除される
管理者権限奪取管理者ユーザーとしてログインされる
DB構造の把握テーブル名、カラム名などを調査される

よくある攻撃文字列

SQLi では、以下のような文字列が使われることがあります。

' OR '1'='1
' OR 1=1 --
admin'--
UNION SELECT
SLEEP(5)
DROP TABLE users

例えば UNION SELECT は、本来の検索結果に別のテーブルの情報を混ぜて取得しようとする攻撃で使われます。

SLEEP(5) は、SQL の応答をわざと遅らせて、SQLi が効いているか確認する攻撃で使われることがあります。

例:検索画面での SQLi

検索画面で、ユーザーが入力したキーワードを使って以下のような SQL を作っていたとします。

SELECT *
FROM jobs
WHERE title LIKE '%営業%';

通常は「営業」という求人を検索するだけです。

しかし、攻撃者が検索欄に以下のような値を入れます。

%' UNION SELECT email, password FROM users --

アプリが入力値をそのまま SQL に埋め込んでいると、求人検索のはずが、ユーザーテーブルの情報を抜き出すような SQL に変えられてしまう可能性があります。

なぜ発生するのか?

SQLi の主な原因は、ユーザー入力をそのまま SQL 文に連結してしまうことです。

悪い例です。

$sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'";

このように文字列結合で SQL を作ると、入力値に SQL の構文を混ぜ込まれたときに危険です。

防ぐには?

根本対策は、プリペアドステートメント / パラメータ化クエリを使うことです。

イメージとしては、SQL の形と入力値を分けて扱います。

SELECT *
FROM users
WHERE email = ?

この場合、ユーザーがどんな文字列を入れても、それは SQL の命令ではなく、単なる値として扱われます。

つまり、攻撃者が以下を入れても、

' OR '1'='1

DB はそれを SQL 文として解釈せず、ただの文字列として扱います。

WAF との関係

WAF は、SQLi っぽいリクエストを入口で検知してブロックします。

例えば、リクエストの URI、クエリ文字列、ヘッダー、Cookie、Body などに以下のような怪しい文字列が含まれていないか見ます。

UNION SELECT
OR 1=1
--
/*
SLEEP(
DROP TABLE

ただし、WAF はあくまで入口対策です。

WAF = 攻撃リクエストを入口で止める
アプリ修正 = SQLi 脆弱性そのものをなくす

なので、理想は両方です。

AWS WAF で SQLi 攻撃を防ぐ基本方針

AWS WAF で SQLi 攻撃を防ぐ基本方針は、「AWS managed rule で広く検知・遮断しつつ、誤検知をログで確認して例外調整する」です。

ただし重要なのは、WAF は SQLi の根本対策ではなく、入口での緩和策という点です。根本対策はアプリ側の プリペアドステートメント / パラメータ化クエリ です。

OWASP でも SQLi の主要な防御策としてパラメータ化クエリを推奨しています。

OWASP(Open Web Application Security Project)とは、ソフトウェアと Web アプリケーションのセキュリティ向上を目的とした非営利の国際的なオープンソースコミュニティです。

OWASP Top 10」をはじめ、脆弱性診断ツールや安全な開発のためのガイドラインなどを無償で公開しています。

最初に有効化すべきルール

AWS WAF では、まず以下を有効化するのが基本です。

AWSManagedRulesSQLiRuleSet

これは SQL Database managed rule group(SQL Database ルールグループ)で、SQL インジェクション攻撃など SQL 悪用パターンをブロックするための AWS 管理ルールです。WCU は 200 です。

SQL Database ルールグループには、SQL インジェクション攻撃などの SQL データベースの悪用に関連するリクエストパターンをブロックするルールが含まれています。これにより、不正なクエリのリモートインジェクションを防ぐことができます。アプリケーションが SQL データベースと連結している場合は、このルールグループを評価します。

このマネージドルールグループは、評価対象のウェブリクエストに保護パック (ウェブ ACL) 内のこのルールグループの後に実行されるルールでも使用できるラベルを追加します。 AWS WAF は、Amazon CloudWatch メトリクスにもラベルを記録します。

このルールグループでは、主に以下を検査します。

検査対象代表ルール
クエリパラメータSQLi_QUERYARGUMENTS, SQLiExtendedPatterns_QUERYARGUMENTS
リクエストボディSQLi_BODY, SQLiExtendedPatterns_BODY
ヘッダーSQLiExtendedPatterns_HEADER
URI パスSQLi_URIPATH, SQLiExtendedPatterns_URIPATH
CookieSQLi_COOKIE

AWS の SQLi managed rule は、これらのリクエスト要素に対して SQLi らしい文字列・構文・パターンを検査し、デフォルトでは Block アクションを持ちます。

実運用での進め方

いきなり本番で Block にするより、まずは Count モードで観察するのが安全です。

流れとしては以下がよいです。

  1. AWSManagedRulesSQLiRuleSet を Web ACL に追加
  2. まず各ルールを Count に override
  3. WAF ログで labels / terminatingRuleId / terminatingRuleMatchDetails を確認
  4. 正常アクセスの誤検知がないか確認
  5. 問題なければ Block に戻す
  6. 誤検知がある URI / パラメータだけ scope-down や例外ルールで調整

AWS WAF はルールグループ内の個別ルールアクションを override できます。Count はリクエストをブロックせず、後続ルール評価も続けるため、テストや誤検知確認に使えます。

ログで見るべきポイント

SQLi っぽいアクセスを見つける場合、WAF ログでは特に以下を見ます。

action
terminatingRuleId
terminatingRuleType
terminatingRuleMatchDetails
labels
httpRequest.uri
httpRequest.args
httpRequest.headers
httpRequest.clientIp
httpRequest.httpMethod

特に managed rule の場合、以下のような label が付きます。

awswaf:managed:aws:sql-database:SQLi_QueryArguments
awswaf:managed:aws:sql-database:SQLi_Body
awswaf:managed:aws:sql-database:SQLiExtendedPatterns_Header
awswaf:managed:aws:sql-database:SQLiExtendedPatterns_UriPath

AWS WAF の Managed Rule Group は評価したリクエストに label を追加し、その label は CloudWatch メトリクスやログでも確認できます。

追加でカスタム SQLi ルールを作る場合

Managed Rule で足りない場合は、カスタムルールで SQL injection match statement を作れます。

例えば、重要な検索 API やログイン API に対して以下を検査します。

URI path
Query string
All query parameters
Headers
Cookies
Body
JSON body

AWS WAF の SQL injection match statement は、Web リクエスト内の悪意ある SQL コードを検査するルールです。感度は LOW / HIGH を指定でき、HIGH はより多く検知できますが誤検知も増えやすいです。

例えば、以下のような考え方です。

/login
/search
/api/*
/entry/*

など、DB に接続する重要なエンドポイントに対して、QueryArguments / Body / JSON body を重点的に検査します。

Text transformation も重要

SQLi は以下のようにエンコードや難読化されることがあります。

UNION SELECT
%55%4e%49%4f%4e%20%53%45%4c%45%43%54
un/**/ion sel/**/ect

そのため、カスタム SQLi ルールでは text transformation を使い、URL decode や HTML entity decode などを行ってから検査します。AWS WAF の text transformation は、攻撃者が WAF 回避のために使う変則的な表現を検査前に整形する仕組みです。

Body 検査のサイズ制限に注意

SQLi が POST body に入る場合、WAF が body をどこまで検査できるかが重要です。

AWS WAF は ALB / AppSync では request body の先頭 8 KB、CloudFront / API Gateway / Cognito / App Runner / Verified Access ではデフォルト 16 KB を検査できます。CloudFront など一部リソースでは 64 KB まで増やせます。

つまり、巨大な POST body の後半に SQLi ペイロードを入れられると、設定によっては WAF だけでは見えない可能性があります。

そのため、重要 API では以下も検討します。

・body inspection limit を増やす
・大きすぎる body を SizeConstraint で制限する
・oversize handling をどう扱うか設計する
・アプリ側でも入力長制限を入れる

誤検知対応の考え方

SQLi ルールは誤検知が起きやすいです。

例えば、検索欄や記事本文、求人原稿、管理画面の入力欄などに以下のような文字が入ると、SQLi っぽく見えることがあります。

select
union
from
where
'
--
/*

この場合、ルール全体を無効化するのではなく、できるだけ限定的に例外化します。

よい例外化の単位は以下です。

特定 URI のみ
特定 HTTP method のみ
特定パラメータのみ
社内 IP のみ
管理画面のみ

悪い例は、SQLi ルール全体を無効化することです。これをやると SQLi 対策の意味がかなり薄くなります。

実務上のおすすめ構成

本番向けには、まずこの構成が現実的です。

Web ACL
├─ AWSManagedRulesCommonRuleSet
├─ AWSManagedRulesKnownBadInputsRuleSet
├─ AWSManagedRulesSQLiRuleSet
├─ 必要に応じて IP reputation 系
├─ 必要に応じて Rate-based rule
└─ 誤検知除外用の Allow / Count / Scope-down rule

SQLi 単体では AWSManagedRulesSQLiRuleSet が中心ですが、実際の攻撃は SQLi だけでなく、LFI / RFI / XSS / bot / scanning と混ざるため、CommonRuleSet や KnownBadInputs も合わせて使うのがよいです。AWS Managed Rules は一般的な Web 脅威に対する追加の防御層ですが、AWS 側も「利用者のセキュリティ責任の代替ではない」と説明しています。

結論

WAF で SQLi を防ぐなら、まずは以下です。

1. AWSManagedRulesSQLiRuleSet を入れる
2. 最初は Count でログ確認
3. labels で SQLi_QUERYARGUMENTS / SQLi_BODY / SQLi_URIPATH などを確認
4. 誤検知を URI / パラメータ単位で調整
5. 問題なければ Block 化
6. 重要 API は body size / oversize handling / custom SQLi rule も設計
7. 根本対策としてアプリ側は必ずパラメータ化クエリにする

一番大事なのは、WAF は「SQLi 攻撃っぽいリクエストを入口で落とすもの」であって、アプリの SQLi 脆弱性を直すものではないという点です。WAF でブロックしつつ、アプリ側ではプリペアドステートメント、入力バリデーション、DB 権限最小化までセットで対応するのが安全です。