Entity Frameworkを使ってC#のSQLインジェクションを防ぐ
2024年7月30日
0 分で読めますSQLインジェクションを防ぐ重要性
SQLインジェクション(SQLi)は、Webアプリケーションにおける最も深刻なセキュリティ脆弱性の一つです。攻撃者がユーザー入力欄に悪意のあるSQLコードを注入し、アプリケーションが実行するSQLクエリを操作できる場合に発生します。SQLiによって、機密データへの不正アクセスやデータの破損、さらにはデータベースサーバーの完全な制御につながる可能性があります。データの完全性、機密性、可用性、そしてアプリケーション全体のセキュリティを維持するには、SQLiの防止が不可欠です。
開発者がよく犯す間違いの一つは、文字列連結でSQLクエリを組み立てることです。この方法では、ユーザーが入力したパラメーターがSQLクエリの一部となり、実行経路やクエリに影響を与える可能性があります。脆弱なC#コードの例を見てみましょう。
;や--のような文字を使うと、意図した位置より早くクエリを終了させ(;)、文字列の残りをコメントとして扱わせる(--)ことで、クエリの実行経路を操作できます。
名前がBrian'; DROP TABLE Users;--に設定されている場合、クエリは次のようになります。
最初のクエリは;の後で終了します。その後、Usersテーブルを削除する新しいクエリが実行され、元のクエリの残りの部分はコメントとして扱われます。同様に、このような方法でSQLクエリを実装すると、データベース内のデータを変更したり、新しいデータを挿入したりすることも可能です。
エスケープとプリペアドステートメントの比較
重要なのは、入力パラメーターをクエリ本体から分離し、ユーザー入力がクエリの実行経路に影響を与えないようにすることです。多くのプログラミング言語には、パラメーター化クエリを利用してユーザー入力をクエリ本体から分離する関数が用意されています。
では、内部ではどのように実装されているのでしょうか。大きく分けて、エスケープとプリペアドステートメントの2つの方法があります。
エスケープ
エスケープとは、引用符などの危険な文字の前にエスケープ文字を追加し、ユーザー入力を無害化する処理です。しかし、エスケープは間違いが起きやすく、完全に安全な方法ではありません。エスケープはクライアント側(アプリケーション)で行われ、エスケープ済みのクエリは単一のステートメントとしてデータベースに送信されます。さまざまな例外ケースに細心の注意を払って対応する必要があるため、SQLiに対する主要な防御策としては一般に推奨されません。
プリペアドステートメント
一方、SQLクエリをパラメーター化するプリペアドステートメントは、少し異なる仕組みで動作します。まずSQLの構造を定義し、データベースサーバーに送信します。パラメーターが渡される前に、データベースは実行経路を作成できます。次に、パラメーターをデータベースに送信します。実行計画はすでに作成されているため、パラメーターは常に実行可能なコードではなくデータとして扱われ、実行に影響を与えることはありません。
Entity FrameworkでSQLインジェクションを防ぐ
Entity Framework(EF)には、アプリケーションをSQLiの脆弱性にさらすことなく、安全にデータベースとやり取りするための方法がいくつか用意されています。LINQクエリ、FromSqlInterpolated、明示的なパラメーターを指定したFromSqlRawなどがその例です。
LINQを使う
Language Integrated Query(LINQ)は、データベースと安全にやり取りする方法です。ほとんどのクエリでは、EFでデータベースに問い合わせる際にLINQを使うことが推奨されます。LINQは高水準の抽象化を提供し、SQL構文を知らなくてもC#で直接クエリを記述できます。C#に統合されているため、コンパイル時の構文チェックも利用できます。
Entity FrameworkはLINQクエリをプリペアドステートメントとパラメーター化を用いたSQLに自動変換するため、SQLインジェクションを防止できます。
LINQは非常に便利で安全ですが、特に複雑なクエリでは、手書きのSQLクエリより効率の低いSQLが生成されることがあります。変換レイヤーによってパフォーマンスのオーバーヘッドが生じる可能性もあります。また、LINQで表現できる内容は、EFプロバイダーがC#コードをSQLに変換できる範囲に制限されます。特定の種類の結合、サブクエリ、ウィンドウ関数など、一部の複雑なSQL機能は、LINQで表現するのが難しい、あるいは不可能な場合があります。
FromSqlInterpolatedを使う
FromSqlInterpolatedも、SQLを直接実行するための安全な方法です。文字列補間を使い、プリペアドステートメントによるパラメーター化を自動的に処理します。
FromSqlInterpolatedを使うと、EFはname変数を安全にパラメーター化し、SQLインジェクションを防ぎます。Entity Framework Coreは補間文字列を解析し、補間された式を識別します。次に、SQLコマンド内でこれらの式をパラメータープレースホルダーに置き換えます。対応するSQLパラメーターが作成され、補間された式の値がそれぞれのパラメーターに割り当てられます。
内部では、FromSqlInterpolated関数はパラメーター化クエリを使ったプリペアドステートメントを使用します。そのため、SQLインジェクションに対して安全です。LINQ構文よりSQLで表現するほうが簡単な複雑なクエリが必要な場合、FromSqlInterpolatedは優れた代替手段となります。
明示的なパラメーターを指定してFromSqlRawを使う
最初の例では、クエリを先に作成し、その全体をFromSQLRawステートメントに渡しました。しかし、FromSqlRawも安全に使用できます。FromSqlRawで明示的なパラメーターを指定すると、EFはユーザー入力を適切にパラメーター化し、SQLインジェクションを防ぎます。この方法では、パラメータープレースホルダーを使ってSQLクエリを定義し、値を別途渡すことで、ユーザー入力を実行可能なコードではなくデータとして扱います。
この例では、SQLクエリ文字列で{0}というプレースホルダーを使い、パラメーター値を引数としてFromSqlRawに直接渡しています。これによりユーザー入力が安全にパラメーター化され、SQLインジェクションを防げます。
検出、修正、予防
FromSqlRawを安全に使う場合と安全でない方法で使う場合の違いはごくわずかで、見落としやすいものです。そのため、すべての開発者がSnyk Codeのようなツールを使い、C#で安全でないコードを構築している場合に警告を受けられるようにするべきです。

上の例では、Gitインテグレーションに基づいて同様のSQLインジェクションの問題が検出されています。セキュリティを重視しながら開発スピードを犠牲にしたくない場合、開発ライフサイクル中にこうした問題をチェックすることが重要です。
ただし、フレームワークに関する教育と知識も非常に重要です。EFを使う場合は、まずLINQ構文を使うのが基本です。極めて高いパフォーマンスが必要な場合や、非常に複雑なクエリを作成する場合を除けば、LINQで十分でしょう。非常に具体的で複雑なSQLクエリが必要な場合は、前述のFromSqlInterpolatedを使ってください。私からのアドバイスは、FromSqlRawを避けることです。安全に使うことはできますが、正しい方法と間違った方法の違いはわずかで、簡単に見落とされる可能性があります。
要点を簡潔にまとめると、Entity Frameworkを使う場合は次のとおりです。
データベースとのやり取りにはLINQ構文を使い、SQLを直接扱うのは避けましょう。
複雑なケースに限り、FromSQLInterpolatedを使いましょう。
Snyk Codeのようなコードスキャンツールを使い、ミスの検出に役立てましょう。
これで、C#のEntity Frameworkを使ってコードをSQLインジェクションの脆弱性から守る方法について理解が深まったはずです。すぐに始めるには、今すぐSnykの無料アカウントを作成するか、デモを予約して、Snykがコードの記述中にどのように保護するかをご確認ください。
Capture the Flagを始めよう
オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。



