LINQ to SQLによるSQLインジェクションの排除
論点をシンプルにするため、サンプルWebアプリケーションをコンソールアプリケーションに書き換えたものがリスト2です。このコンソールアプリケーションは、SQL文に埋め込まれる悪意あるSQLを単純化して表現しています(話を簡単にするために、どうやって埋め込むかは重要視しません。前のセクションで、値が直接埋め込まれるSQLにはインジェクションのおそれがあることを説明したので、不備のあるSQLが最終的に好ましくない結果となることが分かれば十分です)。
Imports System.Data.SqlClient Module Module1 Sub Main() SqlInjectionToDiscoverOtherDatabase() End Sub Sub SqlInjectionToDiscoverOtherDatabase() Dim connectionString As String = _ "Data Source=.\SQLExpress;Initial _ Catalog=northwind;Integrated Security=True" Dim sql As String = _ "SELECT CustomerID, CompanyName FROM Customers _ WHERE CustomerID = '{0}'" _ Dim customerID = _ "ALFKI' UNION SELECT 'database name' as dummy, " + _ "name FROM master.sys.databases --" Using connection As SqlConnection = _ New SqlConnection(connectionString) connection.Open() Dim command As SqlCommand = _ New SqlCommand(String.Format(sql, customerID), connection) Dim reader As SqlDataReader = command.ExecuteReader While (reader.Read()) Dim i As Integer For i = 0 To reader.VisibleFieldCount - 1 Console.WriteLine(reader(i).ToString()) Next End While End Using Console.ReadLine() End Sub End Module
リスト2では、悪意のあるSQL(ユーザーによる入力)を表すcustomerIDローカル変数を宣言しています。LINQ to SQLによってSQLインジェクションがどのように排除されるかを解説する前に、ここでLINQ to SQLについての一般的な説明をしたいと思います。LINQ to SQLの基本がわかっている場合は、次のセクションを読み飛ばしてください。
LINQ to SQLとは
LINQ(Language Integrated Query)とは、VBまたはC#のコードで記述されたSQLライクな言語のことです。LINQ to SQLは、基本的にLINQ to SQLテクノロジによってSQLクエリに変換されるLINQコードを指します。
大まかに言って、LINQ to SQLの作成手順は次のようになります。SQLデータベースのテーブルに一致する、カスタムエンティティクラスを定義します。クラスにはTableAttribute
属性を指定し、列を表すプロパティにはColumnAttribute
属性を指定します。SqlMetalを使用して、これらのクラスを生成することもできます。これはまさに、定義した(またはSqlMetal.exeで生成した)カスタムクラスのプロパティと、ColumnAttribute
属性でテーブルの列に関連付けられているプロパティのみがSQLから返されることを意味しています。次に、DataContext
クラスを継承した簡単なクラスを定義します。このDataContext
クラスは、データベースへの接続を表しています。作成はこれで完了です。ADO.NETを直接使用することはありません。SQLを直接記述することもありません。周辺の処理はLINQ to SQLの仕事です。
最終的な結果として、エンティティに一致するデータのみが返されます。ユーザーは、動的SQL文字列ではなくコンパイル済みのコードを使用することになります。SQLに直接渡す値はなく、SQLはLINQ to SQLエンジンによって生成されます。
LINQ to SQLがSQLインジェクションを防止する仕組み
動的SQLでは、SQLエンジンにテキスト文字列が渡されることが問題の種となります。LINQ to SQLの場合、コードは既にコンパイルされていて、SQLはLINQ to SQLテクノロジによって生成されます。
次に重要なのは、LINQがサポートするのはSelectクエリのみであるという点です。Update、Delete、Insertはサポートしていません。このため、悪意あるユーザーにできるのはデータを読み込むことだけです。これは少々不便に思われるかもしれませんが、そんなことはありません(詳しくは後述)。
LINQは、コンパイル時にメソッド呼び出しに変換されます。つまり、実行コードはコンパイルが完了しています。そのため、ユーザーが実行時に新たなLINQキーワードを入力して、事前に定義されたクエリを改ざんすることはできません。実行時にSelectをJoinやUnionに変えることはできないのです。
CodeDOMを使用して、LINQクエリを実行時にコンパイルするようにアプリケーションを設計すると、実行時にユーザーが動的LINQを入力できる場合があります。ただし、当然ながらこれには高度な技術を要します。方法が分かる人はあまりいませんし、ビジネスアプリケーションで動的コンパイルを使用する理由はほとんどありません。