「入力値バリデーションはブラックリストでいいや」が招く悲劇:ホワイトリスト方式による鉄壁の守り方
現場でコードレビューをしていると、未だに「この文字は危険だから弾こう」というブラックリスト方式でバリデーションを実装している例を見かける。正直に言おう。それは「穴の空いた網で水をすくう」ようなものだ。
攻撃者は常に、君たちが想定した「禁止リスト」の裏をかき、エンコーディングや特殊文字の組み合わせでそれを突破する。今回は、インジェクション攻撃を根絶するための「ホワイトリスト方式」の哲学と、明日から現場で使える実装パターンを叩き込む。
—
1. なぜ「ブラックリスト」は敗北するのか?
ブラックリスト方式(例:' や -- を除去する)が脆弱な理由は、「悪意ある入力パターンが無限に存在するから」だ。
例えば、SQLインジェクション対策として ' を置換するようなコードを書いたとしよう。しかし、攻撃者は以下のような手法でそれを回避する。
- マルチバイト文字の悪用: 一部のエンコーディング環境下では、特定文字を挿入することでエスケープ処理を無効化できる。
- 代替演算子:
'を使わずにOR 1=1を成立させる手法や、UNION SELECTを用いたデータ窃取。 - WAFの検知回避: 攻撃ペイロードをURLエンコードや二重エンコードして、単純な文字列マッチングをすり抜ける。
「許可するものだけを通す」ホワイトリスト方式なら、そもそも想定外の形式はすべて門前払いできる。これがセキュリティの鉄則だ。
—
2. ホワイトリスト方式の実装:PHP編
「型」と「形式(正規表現)」の二重チェックが基本だ。例えば、ユーザーIDが「半角英数字のみで10文字以内」という仕様なら、それ以外の入力は一切許してはならない。
/
function validateUserId(string $input): bool {
// 1. 文字数チェック(長さの制限はDDoSやバッファオーバーフロー対策の基本)
if (strlen($input) > 10) return false;
// 2. 正規表現によるホワイトリストチェック
// ^[a-zA-Z0-9]+$ : 先頭から末尾まで、半角英数字のみを許可
if (!preg_match(‘/^[a-zA-Z0-9]+$/’, $input)) {
return false;
}
return true;
}
$userInput = $_POST[‘user_id’] ?? ”;
if (validateUserId($userInput)) {
// ここで初めて処理に進む
echo “バリデーション成功: クエリ実行へ”;
} else {
// ログに記録し、攻撃の兆候として検知対象にする
error_log(“不正な入力が検出されました: ” . $userInput);
die(“不正なアクセスです。”);
}
—
3. ホワイトリスト方式の実装:Python (Flask) 編
Pythonではライブラリを活用し、バリデーションをモデル層に閉じ込めるのが美しい。
import re
from flask import request, abort
def validate_input(data):
# ホワイトリスト:許可するパターンのみを定義
# 郵便番号: 000-0000 形式のみ許可
pattern = re.compile(r’^\d{3}-\d{4}$’)
if not pattern.match(data):
# 不正な入力には400 Bad Requestを返す
abort(400, description=”Invalid input format”)
ルーティングでの利用例
@app.route(‘/zipcode’, methods=[‘POST’])
def handle_zipcode():
zip_code = request.form.get(‘zip’)
validate_input(zip_code)
# ここから先は安全なデータとして扱える
return “OK”
—
4. 現場で忘れてはならない「防御の多層化」
コードレベルでのバリデーションは必須だが、それだけで満足してはいけない。インフラ側でも攻撃の芽を摘む必要がある。
Nginxでのリクエスト制限(概念設定)
不自然なリクエストをアプリケーションに到達させないことも重要だ。
nginx.conf の設定例
不自然な特殊文字を含むリクエストを拒否(極端な例だが、静的サイトなら有効)
if ($request_uri ~ “(\%27)|(\’)|(\-\-)|(\%23)”) {
return 403;
}
データベース層の防壁(プリペアドステートメント)
どれほどバリデーションを完璧にしても、「SQL文への埋め込み」は絶対にやるな。 必ずプリペアドステートメント(PDOやSQLAlchemy等)を使用すること。バリデーションは「入力の適格性」、プリペアドステートメントは「実行の安全性」という役割分担を理解してほしい。
—
最後に:なぜ「泥臭いチェック」が必要なのか
セキュリティとは、エンジニアの「面倒くさい」という感情との戦いだ。
正規表現を書くのは面倒だし、テストケースを増やすのも手間がかかる。しかし、一度インシデントが起きれば、その数千倍のコストと信頼の失墜が待っている。
「データは信頼できないものとして扱う(Zero Trust)」という精神をコードの隅々に宿らせてくれ。今回紹介したホワイトリスト方式を実装のベースラインに据えるだけで、君たちのアプリケーションの堅牢性は劇的に向上する。
まずは、自分の担当しているプロジェクトのバリデーションロジックを開いてみてほしい。「禁止リスト」で守られている場所があれば、それが今日、君が修正すべき最初のチケットだ。
コメント