flawopen.com/SQL Injection/数値 ID
数値 ID でも文字列フォーマットが危険なのはなぜですか?
危険性は値の実際の意味ではなく、クエリを文字列の連結で作ることにあります。「常に数値」というのはデータへの想定であり、コードが強制する保証ではありません。
想定が崩れる場面
URL のクエリパラメーターやフォーム値は、DB 列が期待する型にかかわらず、通信上はテキストです。型を明示的に検証しない限り、コードに届く前に id=1 OR 1=1 のような値を拒否する仕組みはありません。
ID が常に信頼できる内部ソース由来なので今は安全なコードでも、新しい呼び出し元がユーザー値を同じ関数に渡せば危険になります。脆弱性はその変更を待って潜在していたのです。
特に動的型付け言語では、ログ上は数値に見える値でも追加文字を含む文字列の場合があります。明示的に検証しない限り、期待する型と実際の型は一致するとは限りません。
型を検証した後もクエリをパラメーター化する
アプリケーションが期待する形式に ID を検証してから、データベースドライバーのパラメーター API に渡してください。検証は入力契約を守り、パラメーター化は入力が SQL 構文として解釈されるのを防ぎます。プロトコル上のドライバーの動作は異なる場合があるため、すべてのドライバーが同じ方法で値を送るとは限りません。PostgreSQL のパラメーターはテーブル名や列名を表せません。動的なクエリ構造はアプリケーション内の固定許可リストから選んでください。
数値をバインドし、意味を検証する
アプリケーションがその型を必要とする場合は、ID が正の安全な整数であることを検証したうえで、クエリ値としてバインドしてください。検証はアプリケーションの入力契約を守り、パラメーター化は入力が SQL 構文になるのを防ぎます。PostgreSQL のパラメーターはテーブル名や列名の代わりには使えないため、動的なクエリ構造はアプリケーション内の固定許可リストから選択してください。
const rawId = new URL(request.url).searchParams.get("id");
const result = await pool.query(
`SELECT id FROM users WHERE id = ${rawId}`
);
const rawId = new URL(request.url).searchParams.get("id");
if (rawId === null || !/^[0-9]+$/.test(rawId)) {
return new Response("Invalid id", { status: 400 });
}
const id = Number(rawId);
if (!Number.isSafeInteger(id) || id < 1) {
return new Response("Invalid id", { status: 400 });
}
const result = await pool.query(
"SELECT id FROM users WHERE id = $1",
Array.of(id)
);