SQLインジェクションはどのように防ぐのですか?
Prepared Statementと最小権限で防御する
本ブログ作成文は、講義を目指して製作されました。 また、作成された文章の情報も間違っている可能性がありますので、ご注意ください。
SQLインジェクションは、入力から引用符をすべて消去する方法ではブロックしません。 SQL命令とユーザーデータを最初から分離して、データが命令として解釈されないようにする必要があります。
Prepared Statementを使用する
Prepared Statement(準備された文)またはParameterized Query(パラメータ化クエリ)は、SQL文に値が入る場所を別々に表示します。
const sql = 'SELECT id, name, password_hash FROM users WHERE name = ?';
const [rows] = await database.execute(sql, [userName]);
if (rows.length === 1)
{
const passwordMatches = await passwordHasher.verify(
rows[0].password_hash,
password
);
}
ここで?に入る値はSQL文法ではなくデータとして渡される。 ユーザーが引用符OR同じ文字列を入力してもクエリの構造を変更できません。
パスワードはSQLで平文を比較しません。 ユーザ情報をパラメータクエリにインポートし、検証されたパスワードハッシュライブラリに保存されたハッシュと入力値を比較します。
ORMを使用すると自動的にすべて安全になるわけではない。 ORMが提供するパラメータ機能を使用せずにRaw Queryを文字列にまとめると、同じ脆弱性が発生します。
テーブル名とソート基準はホワイトリストでチェックされます
パラメータは通常値に使用されます。 テーブル名、列名、ASC私DESC同じSQL文法桁はパラメータで処理できないことが多い。
これらの値は、ユーザーの文字列をそのまま付けるのではなく、サーバーがあらかじめ決めたAllowlist(許可リスト)から選択する。
const allowedSortColumns = {
newest: 'created_at',
name: 'name'
};
const sortColumn = allowedSortColumns[requestedSort] ?? 'created_at';
const sql = `SELECT id, name FROM users ORDER BY ${sortColumn} DESC`;
requestedSortそれ自体をSQLに入れず、サーバーが所有する安全な文字列に変換することが重要です。
データベースアカウントの権限を減らす
Webサーバーが使用するデータベースアカウントに管理者権限を与えると、脆弱性の1つの損傷がデータベース全体に広がる可能性があります。
- 照会のみする機能には読み取り権限だけを与える。
- 必要なテーブルにのみアクセスさせます。
- Webアプリケーションアカウントでデータベースを管理しないでください。
- パスワードは平文で保存せず、検証されたハッシュ方式で保存します。
エラーはサーバーに記録し、ユーザーに隠す
詳細なSQLエラーをそのまま応答すると、テーブル名や列名などの内部構造が公開される可能性があります。 ユーザーには一般的な失敗メッセージが表示され、詳細なエラーはアクセスが制御されたサーバーログに残されるべきです。
入力検証、ファイアウォール、検出ツールも役立ちます。 しかし、最初にすべきことはすべてのクエリをパラメータ化し、動的に入るSQL文法はホワイトリストに制限し、データベース権限を最小化することです。