問題と範囲
マルチテナントの注文プラットフォームが内部検索エンドポイントを公開しています。認証済みユーザーのテナントはサーバーセッションから取得可能です。リクエストには、顧客メールアドレス、注文ステータス一覧、ソート項目、ソート順、および取得件数上限が含まれる場合があります。レビューにより、次のようなコードが見つかりました。
const sql = `
SELECT id, customer_email, status, total_cents, created_at
FROM orders
WHERE tenant_id = '${tenantId}'
AND customer_email = '${input.email}'
AND status IN (${input.statuses.join(",")})
ORDER BY ${input.sort} ${input.direction}
LIMIT ${input.limit}
`攻撃者が制御するテキストがSQLの構文構造を変更できないように、このクエリ境界をどのように再設計するか説明してください。値、オプションのフィルタ、リストパラメータ、ソート列などの識別子、ストアドプロシージャ、ORMの生クエリエスケープハッチ、データベース権限、ロギング、テスト、および後に動的SQLで再利用される以前に保存されたデータについて網羅してください。
中心となる原則は、SQLコードは信頼できるアプリケーションコードによって決定されなければならず、外部データはサーバー側のパラメータバインディングインターフェースを介してデータベースに到達しなければならないということです。入力バリデーションや最小権限の原則は有用な障壁を追加しますが、文字列連結を安全なクエリに変えるわけではありません。
決定的なスキルがクエリの構築、データベースドライバの動作、サービス境界での認可、データベース権限、および本番環境での検証であるため、これはバックエンドの質問です。実装言語は副次的な要素にすぎません。
面接官が評価していること
最初の評価ポイントは、候補者が単に「プリペアドステートメントを使用する」と言うだけでなく、すべての構文境界を特定しているかどうかです。テナントID、メールアドレス、ステータス、上限などの値はデータとしてバインドする必要があります。テーブル名、カラム名、SQLキーワード、ソート順は通常、通常の値プレースホルダーでは提供できないため、クエリの再設計、または小さな公開enumを固定のSQLトークンにマッピングするサーバー管理のホワイトリストが必要です。
2番目の評価ポイントは、インジェクション対策と認可の区別です。パラメータ化されたtenant_idであっても、それがリクエストボディから取得され、呼び出し元が別のテナントを選択できる場合、テナント分離の観点からは依然として安全ではありません。サービスは認証されたプリンシパルからテナントを導出し、関連するすべてのクエリにそれを含める必要があります。パラメータ化は値がクエリ構造を変更するのを防ぎますが、呼び出し元がその値へのアクセスを許可されていることを証明するわけではありません。
3番目の評価ポイントは、隠れた実行境界を認識することです。ORMが安全なのは、コードがそのパラメータ化されたAPIを正しく使用している間だけです。生クエリヘルパー、文字列で構築されたフィルタ、マイグレーションツール、レポーティングジョブ、動的SQLを実行するストアドプロシージャは、同じ脆弱性を再現する可能性があります。現在安全に保存されているデータも、別のジョブが後でSQLに連結すると、セカンドオーダーインジェクションの発生源になる可能性があります。
最後の評価ポイントは、多層的な検証です。優れた回答は、コードレビュー、サーバー側のバインディング、ホワイトリストに登録された識別子、最小権限のデータベースロール、安全なエラーハンドリング、敵対的文字列を使用した統合テスト、テナント分離のアサーション、およびデータベースエラーの監視を組み合わせます。Webアプリケーションファイアウォール(WAF)や手動エスケープを主要な修正策として提示することはありません。
回答前に確認すべき明確化のための質問
- どのデータベースとドライバがデプロイされていますか? プレースホルダーの構文、配列のバインディング、識別子のクォートユーティリティ、およびプリペアドステートメントの動作はそれぞれ異なります。設計原則は移植可能ですが、正確なAPIはデプロイされているドライバと一致している必要があります。
- リクエストは
tenantIdを提供しますか? 提供されている場合、認可においてそのフィールドを無視し、認証されたサーバーコンテキストからテナントを導出します。テナントをまたぐ管理者アクセスには、明示的に認可された別のパスが必要です。 - どのフィルタがオプションですか? オプションの述語は、値をパラメータとして保持したまま、固定のSQLフラグメントから追加する必要があります。汎用的な「任意のフィールドと演算子を追加する」APIは、構文の表面を大幅に拡大させます。
- 実際に必要なソート項目はどれですか? プロダクトが作成日時と合計金額のみを必要とする場合は、それら2つのパブリックキーを公開します。任意の列式、関数、照合順序(collation)、またはカンマ区切りのソート句を受け入れてはいけません。
- エンドポイントは多数のステータスまたはIDを検索できますか? データベースドライバの配列機能、型付き配列パラメータ、または生成されたプレースホルダーのセットを使用します。生の値をSQLテキストに直接結合してはいけません。
- パス内にORMの生クエリAPIは存在しますか? 明示的に危険なメソッドと「安全な」テンプレートヘルパーの両方を検査し、値がドライバによってバインドされているか、最初に文字列に補間されているかを確認します。
- ストアドプロシージャが動的SQLを構築していますか? プロシージャは自動的に安全になるわけではありません。プロシージャがSQLを呼び出すとき、そのパラメータはデータのままである必要があります。動的な
EXEC形式のパスも同様のレビューが必要です。 - アプリケーションロールにはどのような権限が付与されていますか? 読み取りエンドポイントは通常、スキーマ所有権、DDL、または無関係なテーブル権限で接続すべきではありません。運用およびマイグレーションの認証情報をランタイム認証情報から分離します。
- リリース前にどのような証拠が必要ですか? コードを変更する前に、敵対的入力コーパス、テナント分離チェック、生クエリのインベントリ、データベースロールの検証、および本番エラーシグナルを定義します。
30秒の回答フレームワーク
「認証されたサーバーコンテキストをtenantIdの取得元とし、テナント、メール、ステータス配列、上限のすべてのデータ値をデータベースドライバ経由でバインドします。ソート列とソート順は構文であるため、2つの公開enum値をサーバー管理の固定SQLトークンにマッピングし、それ以外はすべて拒否します。ORMの生クエリ呼び出しとストアドプロシージャは文字列構築によるSQLを再導入する可能性があるためインベントリを作成し、保存されたデータが後の実行境界に達した際にも再度パラメータ化します。その後、ランタイムデータベースロールの権限を最小限に抑え、クライアントには汎用エラーを返し、詳細な内部障害を監視し、敵対的文字列がリテラルのままであり、他のテナントの行を決して返さないことを証明する統合テストを実行します。」
ステップごとの詳細解説
ステップ1:コード、データ、および認可元の分類
元のクエリの各部分を分類します。
| クエリ部分 | タイプ | 安全なソース |
|---|---|---|
SELECT、テーブル、述語 | SQL構文 | 静的アプリケーションコード |
| テナントID | データおよび認可スコープ | 認証済みサーバーコンテキスト |
| メール、ステータス、上限 | データ | バインドされたドライバパラメータ |
| ソート列 | SQL識別子 | サーバーのホワイトリスト |
| ソート順 | SQLキーワード | サーバーのホワイトリスト |
安全でない例では、リクエストから派生した文字列が構文解析に関与することを許してしまいます。クォート、コメント、演算子、または追加の式によって、データベースがどの文字がデータであるかを認識する前にステートメントが変更される可能性があります。SQLには方言固有の構文、エンコーディング、コメント、関数、パーサーの動作があるため、いくつかの疑わしい部分文字列をチェックするだけでは境界を復元できません。
このエンドポイントだけでなく、SQLを実行するすべての場所から着手します。生クエリAPI、テンプレート文字列、クエリメソッド周辺の文字列連結、動的プロシージャ実行、レポーティングフィルタ、フィールド名や演算子名を受け取るクエリビルダを検索します。何がSQLテキストとしてドライバに到達し、何がパラメータコレクションとして到達するかを示せるようになるまでラッパーを追跡します。
ステップ2:サーバー上ですべての値をバインドする
PostgreSQLスタイルのTypeScript実装により、値を分離して保持できます。
const SORT_COLUMNS = {
createdAt: "o.created_at",
total: "o.total_cents",
} as const
const SORT_DIRECTIONS = {
asc: "ASC",
desc: "DESC",
} as const
interface OrderSearchInput {
email: string | null
statuses: string[] | null
sort: string
direction: string
limit: number
}
async function findOrders(authenticatedTenantId: string, input: OrderSearchInput) {
if (
!Object.hasOwn(SORT_COLUMNS, input.sort) ||
!Object.hasOwn(SORT_DIRECTIONS, input.direction)
) {
throw new Error("Unsupported sort option")
}
const sortColumn =
SORT_COLUMNS[input.sort as keyof typeof SORT_COLUMNS]
const sortDirection =
SORT_DIRECTIONS[input.direction as keyof typeof SORT_DIRECTIONS]
const sql = `
SELECT o.id, o.customer_email, o.status, o.total_cents, o.created_at
FROM orders AS o
WHERE o.tenant_id = $1
AND ($2::text IS NULL OR o.customer_email = $2)
AND ($3::text[] IS NULL OR o.status = ANY($3))
ORDER BY ${sortColumn} ${sortDirection}
LIMIT $4
`
return db.query(sql, [
authenticatedTenantId,
input.email,
input.statuses,
input.limit,
])
}残りの補間では、ランタイムのメンバーシップチェック後に定数マップから選択された値のみが使用されます。リクエスト文字列が識別子やキーワードになることはありません。テナント、メール、ステータス配列、および上限は、ドライバのパラメータコレクションに残ります。
実行前に、limitをプロダクトの許容範囲内の整数としてバリデーションします。これにより、リソース使用量とAPIセマンティクスが保護されます。ビジネスバリデーションとコード/データの分離は異なる目的を果たすため、これも依然としてパラメータとしてバインドされます。
便利な配列パラメータを持たないデータベースの場合は、配列の長さからプレースホルダーを生成し、各要素をバインドします。
status IN ($3, $4, $5)
values = [tenantId, email, status1, status2, status3]プレースホルダーテキストは信頼できるコードによって生成される場合がありますが、ステータス値自体をSQLテキストに結合してはなりません。空配列の動作を明示的に定義します。これは「ステータスフィルタなし」または「何も一致しない」を意味する可能性があり、これらの選択がクエリビルダの動作によって偶発的に変わるべきではありません。
ステップ3:動的構文を小さく抑え、サーバー所有にする
通常のプレースホルダーは値を表し、任意の識別子やキーワードを表すことはできません。"created_at"を$1として渡すと、通常は列を選択するのではなく文字列リテラルによってソートされ、信頼できない識別子を値APIで処理しても製品の要件は解決されません。
小さな公開語彙を公開し、それを固定の内部トークンにマッピングします。
createdAt -> o.created_at
total -> o.total_cents
asc -> ASC
desc -> DESC未知のキーは拒否します。クォートされた識別子、SQL関数、JSONパス、照合順序、NULL順序句、またはリクエストからの式をそのまま通過させてはなりません。製品要件によって後から計算ソートが追加された場合は、サーバーコードでその式を実装し、1つの新しいパブリックキーをそれにマッピングします。
信頼できる管理コードが真に動的な識別子を必要とする場合、識別子のクォートヘルパーが適切なこともありますが、任意のユーザー入力をクォートすると、エンドポイントが持つべき権限よりも広範な機能が維持されてしまうことがよくあります。通常のAPIでは、狭いマッピングの方がレビューやテストが容易です。
ステップ4:テナント分離を個別に維持する
検証済みのセッション、トークン、またはサービスアイデンティティからauthenticatedTenantIdを導出します。JSONボディ、クエリ文字列、またはブラウザの状態に含まれる重複したテナント値を信頼してはなりません。テナントが所有する行を対象とするすべての読み取りおよび書き込みには、認可されたスコープを含めるか、それを要求するリポジトリ抽象化を使用する必要があります。
テストマトリックスには、別のテナントに属する有効な注文ID、メール、またはステータスを含める必要があります。すべてのSQL値が構文的に無害である場合でも、クエリはそのような行を返してはなりません。これにより、インジェクションテスト単体では検出できない認可の不備を検出できます。
データベースの行レベルセキュリティ(RLS)は、信頼できるセッションアイデンティティを中心に構成され、接続プーリングを通じてテストされている場合、別の境界を追加できます。ただし、サービス認可の欠落、安全でないセッション変数処理、またはSQL連結の言い訳にはなりません。独自の設定とテストを持つ追加の強制レイヤーとして扱ってください。
ステップ5:ORM、ストアドプロシージャ、およびセカンドオーダーパスの監査
ORMの通常のフィルタAPIは一般的に値をバインドしますが、生実行メソッドは安全なパラメータ化された形式と明示的に安全でない文字列形式の両方を提供する場合があります。正確なメソッド、フレームワークのバージョン、およびドライバパスを確認してください。queryRawのようなメソッド名だけでは十分な証拠とは言えません。最終的なSQLと値が別々に送信されているかを証明してください。
ストアドプロシージャは、その入力がパラメータのままである場合にのみ安全です。パラメータを動的SQLに連結して実行するプロシージャは、脆弱な境界をデータベース内に移動させたにすぎません。プロシージャの本文で動的実行を検索し、アプリケーションロールに付与されている権限を確認してください。
セカンドオーダーインジェクションは、敵対的テキストが通常のデータとして保存され、後にSQL構文として再利用された場合に発生します。例えば、インポート処理でレポート名が安全に挿入されたとしても、夜間のレポーティングジョブが後でその名前をクエリに連結することがあります。最初の書き込みがパラメータ化されていても、後の実行が脆弱なままになる可能性があります。値がリクエスト、別のサービス、ファイル、または既存のデータベース行のいずれから直接来たかに関係なく、すべての実行境界でパラメータ化または固定マッピングを適用してください。
ステップ6:欠陥を隠すことなく多層防御を追加する
必要なテーブルと操作のみを持つランタイムデータベースロールを使用します。読み取り専用の検索サービスがスキーマを所有したり、テーブルの削除、拡張機能の作成、または無関係なデータの更新を行う権限を持つべきではありません。マイグレーションと管理には個別の認証情報を使用する必要があります。最小権限は、インジェクションやその他のアプリケーション侵害が発生した場合の被害を抑えますが、安全でないクエリを許容できるものにするわけではありません。
入力バリデーションでは、ビジネス上の型、長さ、enumメンバーシップ、および範囲を強制する必要があります。不正なリクエストを早期に拒否し、悪用を減らすことができます。ただし、バリデーションを通過した値でも不適切なSQLコンテキストでは依然として危険である可能性があり、名前などの自由形式のフィールドには正当に句読点が含まれるため、これはあくまで副次的な防御策です。
手動エスケープはデータベース固有であり、脆弱です。汎用的なescapeSqlヘルパーを作成してその出力を連結してはいけません。レガシーパスをすぐに置き換えることができない場合は、それを分離し、データベースベンダーが文書化している機能を使用し、権限を制限し、テストを追加して、削除を追跡します。Webアプリケーションファイアウォール(WAF)は、インシデント発生時に一時的な検知や仮想パッチを提供する場合がありますが、すべてのデータベース実行パスが安全であることを証明することはできません。
クライアントには汎用的な失敗を返し、ルート、操作、デプロイメント、およびデータベースエラークラスを含む構造化された内部イベントをログに記録します。SQLテキスト、スタックトレース、接続の詳細、または機密性の高いパラメータ値を呼び出し元に返してはなりません。調査に十分な識別子を保持しつつ、シークレットや完全な個人データのロギングは避けてください。
ステップ7:クエリ境界で修正を検証する
実際のドライバとデータベース方言に対して統合テストを構築します。クォート、コメントマーカー、セミコロン、Unicode、ワイルドカード文字、およびSQLに似たテキストを含む文字列を含めます。アサーションは単に「リクエストがクラッシュしなかった」ことだけではありません。値がリテラルとして扱われ、クエリの形状が固定されたままであり、エンドポイントが認可された行のみを返す必要があります。
少なくとも以下を網羅してください。
- クォートやコメントに似た文字を含むメールテキストが、リテラル比較のままであること。
- 許可されたすべてのソートキーが、期待通りの固定
ORDER BY句を生成すること。 - 未知のソートキー、ソート順、ステータス、および範囲外の上限が、クエリ実行前に拒否されること。
- 空、1要素、および多数の要素を持つ配列が定義された動作を持ち、バインドされた値を使用していること。
- 呼び出し元がリクエストフィールドのいずれかを変更しても、別のテナントの行を取得できないこと。
- ORMの生クエリパスおよびストアドプロシージャパスが、同じ敵対的コーパスを受け取ること。
- レポーティングまたはメンテナンスジョブによって後で使用される保存された文字列が、2番目の実行境界でデータのままであること。
- ランタイムデータベースロールがスキーマ変更を実行できず、無関係なテーブルにアクセスできないこと。
静的解析とコードレビューにより、新しい生文字列補間パスを防ぐことができます。安全でない生クエリメソッドやSQLに隣接する文字列構築に対するルールまたはレビューチェックポイントを設定しますが、ラッパー、生成されたコード、プロシージャ、およびドライバの動作は単純なテキスト検索では見えない場合があるため、統合テストは維持してください。
ステップ8:リリース、監視、および対応
データベースエラー率、エンドポイントのレイテンシ、拒否された入力数、およびクエリ形状の監視とともにロールアウトします。構文エラー、権限エラー、または拒否されたソートキーの急増は、見落とされたクライアントまたはアクティブなプロービングを示している可能性があります。単にカウントするためだけに完全な敵対的ペイロードをログに記録しないでください。
実際のインジェクションが疑われる場合は、脆弱なエンドポイントを無効化または制限し、関連するデプロイメントおよび監査証跡を保持し、漏洩した可能性のあるデータベース認証情報をローテーションして、ランタイムロールが読み取りまたは変更できた可能性のある範囲を特定します。データベースログと業務レコードを確認して不正アクセスがないか調査し、同等のすべての実行パスを修復し、通常のアクセスを復元する前に発見されたクラスをリグレッションコーパスに追加します。
完了とは、安全でない構文パスがなくなり、テナント認可が強制されたままであり、ランタイムロールが制限され、すべてのクエリ実行ツールのインベントリが作成され、敵対的入力が即時実行と後の実行の両方でデータのままであることがテストで証明されることを意味します。
高品質な回答例
「まず、ORMの生メソッドやストアドプロシージャを含め、エンドポイントで使用されるすべてのSQL実行パスのインベントリを作成します。元のクエリは5つの異なる関心事を混在させています。テナント、メール、ステータス値、上限はデータであり、ソート列とソート順はSQL構文であり、テナントスコープは認可の決定でもあります。
認証されたサーバーコンテキストからテナントを導出し、メール、型付きステータス配列、および制限付きの整数上限とともにドライバ経由でバインドします。ソートキーとしてはcreatedAtとtotalのみを、ソート順としてはascとdescのみを公開し、ランタイムのメンバーシップチェック後にそれらを定数SQLトークンにマッピングします。未知の値は拒否されます。ドライバに配列バインディング機能がない場合は、プレースホルダーリストのみを生成し、各要素を個別にバインドします。
すべてのORM呼び出しが安全であると思い込まず、ORMの正確な生クエリAPIを検証します。また、動的実行がないかストアドプロシージャを検査します。以前に保存された値も、レポートやバッチジョブが後でSQLを構築する際には依然として信頼できないため、その実行境界で再度パラメータ化する必要があります。
多層防御のために、検索サービスは必要な列またはビューのみを読み取ることができ、スキーマを変更できないデータベースロールを使用します。ビジネスバリデーションによって許可されたenum、長さ、上限を強制し、パラメータ化はインジェクション防御としての役割を維持します。クライアントへのレスポンスは汎用的なものにし、内部ログにはSQLテキストや機密パラメータを含めずに操作とエラークラスを記録します。
最後に、実際のドライバを使用して統合テストを実行します。クォート、コメント、SQLに似たテキスト、Unicode、配列要素はリテラル値のままでなければなりません。テストは、許可されたすべてのソートオプションと拒否されたソートオプション、空のリストと大きなリスト、ORMおよびプロシージャのパス、保存されたデータの再利用、およびテナント間識別子を網羅します。クエリ構造が固定され、いかなるリクエストも別のテナントの行を返すことができなくなって初めて、修正は完了します。」
よくある間違い
- メールアドレスのみをパラメータ化する → テナント、リスト要素、上限、または別のフィルタが依然としてSQLを変更できる可能性がある → すべての値をバインドし、実行パス全体を洗い出す。
- 要求されたカラム名を通常の値としてバインドする → 値のプレースホルダーは識別子を表さない → 小さなパブリックenumをサーバー所有の固定SQLトークンにマッピングする。
- 要求された任意の識別子をクォートする → エンドポイントが依然として呼び出し元に広範な構文制御を許可してしまう → プロダクトに必要なソートキーのみを公開し、残りは拒否する。
- バリデーション済みのステータスリストを
IN (...)に結合する → バリデーションの乖離が発生する可能性があり、各要素がSQLテキストに再侵入する → 型付き配列パラメータを使用するか、プレースホルダーを生成して各要素をバインドする。 - パラメータ化されているからといってリクエストから
tenantIdを取得する → コードとデータの分離は、選択されたテナントの認可を意味しない → 認証されたサーバーコンテキストからテナントスコープを導出する。 - ORMがすべてのインジェクションを防ぐと思い込む → 生メソッドや安全でないメソッドは通常のバインディングをバイパスする可能性がある → 正確なAPIと最終的なドライバ呼び出しを検証する。
- 文字列構築をストアドプロシージャに移行する → プロシージャ内の動的SQLはインジェクションに対して脆弱なままである → プロシージャの入力が最終実行までパラメータ化された状態を維持する。
- 最初の書き込み時のみサニタイズする → 保存された敵対的テキストが、後でレポーティングジョブでSQL構文になる可能性がある → セカンドオーダーインジェクションに対してすべての実行境界を保護する。
- カスタムヘルパーでクォートをエスケープする → 方言、エンコーディング、コンテキストの違いにより、手動エスケープは脆弱になる → ドライバのサーバー側パラメータバインディングインターフェースを使用する。
- クエリが修正されたからといってアプリケーションに所有者権限を付与する → 別の欠陥や認証情報の漏洩が発生した際に不要な被害範囲を生む → 最小権限のランタイムロールを使用し、マイグレーション認証情報を分離する。
- デバッグのためにデータベースエラーとSQLを返す → 呼び出し元にスキーマとクエリの詳細が漏洩し、ログに個人データが露出する可能性がある → 汎用エラーを返し、構造化されマスキングされた内部証拠を記録する。
- 有名なペイロードを1つテストして終了する → 認可、配列、ストアドプロシージャ、代替の生パス、セカンドオーダー実行が未テストのままになる → 多様なコーパスにわたってクエリの形状とテナントの結果を検証する。
フォローアップの質問
フォローアップ1:プリペアドステートメントで動的なテーブル名やカラム名を保護できますか?
通常のバインドパラメータは値を表します。一般的にテーブル名、列名、演算子、またはSQLキーワードを置き換えることはできません。呼び出し元が小さなenumから選択できるようにAPIを再設計し、許可された各キーをアプリケーションが所有する固定SQLフラグメントにマッピングします。信頼できる管理ツールが真に動的な識別子を必要とする場合は、データベースベンダーの識別子機能とより厳格な認可境界を使用し、ユーザー向けの通常の検索エンドポイントを通じてその機能を公開してはいけません。
フォローアップ2:SQLインジェクションを防ぐのにORMだけで十分ですか?
選択されたAPIが最終的なドライバ呼び出しまでパラメータバインディングを維持している場合に限られます。通常の等価比較やフィルタメソッドは多くの場合そうなっています。生の文字列メソッド、安全でないバリアント、動的なフィールド名、カスタム演算子、拡張機能はそうではない可能性があります。デプロイされているフレームワークのバージョンを確認し、安全なテスト環境で生成されたSQLと値を検査し、敵対的入力による統合テストを実行してください。ORMは間違いの機会を減らしますが、そのエスケープハッチを理解する必要性をなくすわけではありません。
フォローアップ3:セカンドオーダーSQLインジェクションとは何ですか?
攻撃者が制御する値が、元のステートメントを変更することなく最初にデータとして保存されます。その後のプロセスがその値を読み取って動的SQLに連結し、そこで新しいステートメントの構文を変更します。修正は後続の実行境界で行う必要があります。保存された値をデータとしてバインドするか、構文を選択するためのものであれば固定トークンにマッピングします。データベースの行、ファイル、キュー、内部サービスが実行可能SQLに影響を与える場合、それらを信頼できないソースとして扱ってください。
フォローアップ4:アプリケーションはすべてのクォート、セミコロン、またはSQLキーワードを拒否すべきですか?
いいえ。名前、検索テキスト、その他の正当なデータには、句読点やSQLに似た単語が含まれる場合があります。ビジネスバリデーションでは、実際のドメインの型、長さ、形式、enum、および範囲を強制する必要があります。パラメータ化は、値全体をリテラルデータにすることでセキュリティ特性を提供します。拒否リスト(denylist)は不完全であり、有効な入力を壊してしまう可能性もあります。
フォローアップ5:大規模な動的INリストはどのように処理しますか?
利用可能な場合はデータベースの型付き配列またはテーブル値パラメータを使用するか、要素ごとに1つのプレースホルダーを生成して各値をバインドします。リソース制御のために最大リストサイズを定義します。非常に大きなリストの場合は、一時テーブル、一括ロード(bulk-load)メカニズム、または別のAPIが正当化される場合がありますが、挿入される値は依然としてSQL文字列の連結ではなく、バインドデータまたは一括プロトコルデータを使用します。
フォローアップ6:本番環境でのインジェクションインシデントが確認された場合、どのように対応しますか?
脆弱なパスを制限し、デプロイメント、データベース、アプリケーションの監査証跡を保持し、漏洩した可能性のある認証情報をローテーションします。ランタイムロールの権限を使用して調査範囲を特定し、不正な読み取りや書き込みがないか確認し、同等のすべてのクエリビルダとプロシージャを修復します。観測されたパスをリグレッションテストに追加し、可能な限り権限を削減し、修正されたクエリとテナント境界が検証された後にのみトラフィックを復旧します。