web-dev-qa-db-ja.com

準備済みステートメントは、SQLインジェクション攻撃からどのように保護できますか?

準備済みステートメント どうすれば SQLインジェクション 攻撃を防ぐことができますか?

ウィキペディアによると:

後で別のプロトコルを使用して送信されるパラメーター値を正しくエスケープする必要がないため、準備済みステートメントはSQLインジェクションに対して回復力があります。元のステートメントテンプレートが外部入力から派生していない場合、SQLインジェクションは発生しません。

理由がよくわかりません。簡単な英語の簡単な説明といくつかの例は何でしょうか?

147
Aan

アイデアは非常に単純です-クエリとデータはデータベースサーバーに別々に送信されます
それで全部です。

SQLインジェクション問題の根本は、コードとデータの混合です

実際、SQLクエリは正当なプログラムです。そして、オンザフライでデータを追加することで、このようなプログラムを動的に作成しています。したがって、このデータはプログラムコードに干渉する可能性があり、すべてのSQLインジェクションの例で示されているように(PHP/Mysqlのすべての例)

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";

通常のクエリを生成します

SELECT * FROM users where id=1

このコード

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";

悪意のあるシーケンスを生成します

SELECT * FROM users where id=1; DROP TABLE users;

データがプログラム本体に直接追加され、プログラムの一部になるため、データがプログラムを変更し、渡されたデータに応じて、通常の出力またはテーブルusersが削除されるため、機能します。

準備されたステートメントの場合、プログラムを変更しませんが、そのままです
それがポイントです。

最初にprogramをサーバーに送信しています

$db->prepare("SELECT * FROM users where id=?");

データは、パラメーターまたはプレースホルダーと呼ばれる変数で置き換えられます。

データがまったくない状態でサーバーに送信されるのとまったく同じクエリに注意してください!そして、secondリクエストでデータを送信し、クエリ自体とは本質的に分離しています:

$db->execute($data);

したがって、プログラムを変更して害を及ぼすことはできません。
非常にシンプルですよね?

ただし、毎回ではなく、プレースホルダーを使用していることに注意してください準備されたステートメント

プレースホルダは、将来の処理のために実際のデータを変数で置き換えるための一般的なアイデアです(たとえば、printf()を参照)が、準備されたステートメントはその唯一のサブセットです。

準備されたステートメントをエミュレートでき、実際にクエリがデータとともに構成され、1つのリクエストでサーバーに送信される場合があります(PHPのPDOができます)。ただし、このアプローチは同等に安全であることを理解することが重要です。なぜなら、データのすべてのビットは、タイプであるため、問題は発生しません。

私が追加しなければならない唯一のものは、すべてのマニュアルで常に省略されています:

準備されたステートメントはdataのみを保護できますが、はプログラム自体を保護できません
したがって、たとえば、動的なidentifierを追加する必要がある場合-フィールド名、たとえば準備済みステートメントは役に立たない私たち。 最近問題を説明した なので、繰り返しはしません。

266

以下に例を設定するためのSQLを示します。

CREATE TABLE employee(name varchar, paymentType varchar, amount bigint);

INSERT INTO employee VALUES('Aaron', 'salary', 100);
INSERT INTO employee VALUES('Aaron', 'bonus', 50);
INSERT INTO employee VALUES('Bob', 'salary', 50);
INSERT INTO employee VALUES('Bob', 'bonus', 0);

InjectクラスはSQLインジェクションに対して脆弱です。クエリは、ユーザー入力とともに動的に貼り付けられます。クエリの目的は、ボブに関する情報を表示することでした。ユーザーの入力に基づいて、給与またはボーナスのいずれか。しかし、悪意のあるユーザーは、where句に「またはtrue」に相当するものを追加することでクエリを破損する入力を操作し、隠されているはずのアーロンに関する情報を含むすべてが返されるようにします。

import Java.sql.*;

public class Inject {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd";
        Connection conn = DriverManager.getConnection(url);

        Statement stmt = conn.createStatement();
        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'";
        System.out.println(sql);
        ResultSet rs = stmt.executeQuery(sql);

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

これを実行すると、最初のケースは通常の使用法であり、2番目のケースは悪意のある注入です。

c:\temp>Java Inject salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary'
salary 50

c:\temp>Java Inject "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b'
salary 100
bonus 50
salary 50
bonus 0

ユーザー入力の文字列連結を使用してSQLステートメントを作成しないでください。インジェクションに対して脆弱であるだけでなく、サーバーでのキャッシュへの影響もあります(ステートメントが変更されるため、バインド例では常に同じステートメントが実行されるのに対し、SQLステートメントキャッシュヒットを取得する可能性は低くなります)。

この種の注入を回避するためのバインディングの例を次に示します。

import Java.sql.*;

public class Bind {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres";
        Connection conn = DriverManager.getConnection(url);

        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?";
        System.out.println(sql);

        PreparedStatement stmt = conn.prepareStatement(sql);
        stmt.setString(1, args[0]);

        ResultSet rs = stmt.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

前の例と同じ入力でこれを実行すると、その文字列に一致するpaymentTypeがないため、悪意のあるコードは機能しません。

c:\temp>Java Bind salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
salary 50

c:\temp>Java Bind "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
21
Glenn

基本的に、準備されたステートメントでは、潜在的なハッカーからのデータはデータとして扱われます-そして、アプリケーションのSQLと混合したり、SQLとして解釈したりする方法はありません(渡されたデータが直接アプリケーションSQL)。

これは、準備済みステートメントが最初にSQLクエリを「準備」して効率的なクエリプランを見つけ、後でフォームから入力されると思われる実際の値を送信するためです。

詳細情報はこちら:

準備済みステートメントとSQLインジェクション

13
Jose

準備済みステートメントを作成してDBMSに送信すると、実行用のSQLクエリとして保存されます。

後でデータをクエリにバインドし、DBMSがそのデータをクエリパラメータとして実行(パラメータ化)として使用するようにします。 DBMSは、既にコンパイルされたSQLクエリの補足としてバインドするデータを使用しません。それは単なるデータです。

つまり、準備済みステートメントを使用してSQLインジェクションを実行することは基本的に不可能です。プリペアドステートメントの性質とDBMSとの関係により、これを防ぐことができます。

5
wulfgarpro

私は答えを読みましたが、それでも準備された声明の本質を明らかにする重要なポイントを強調する必要性を感じました。ユーザー入力が関係するデータベースを照会するには、次の2つの方法を検討してください。

素朴なアプローチ

ユーザー入力と一部のSQL文字列を連結して、SQLステートメントを生成します。この場合、ユーザーは悪意のあるSQLコマンドを埋め込むことができ、実行するためにデータベースに送信されます。

String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'"

たとえば、悪意のあるユーザー入力により、SQLString"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'と等しくなる可能性があります

悪意のあるユーザーが原因で、SQLStringには2つのステートメントが含まれていますが、2番目のステートメント("DROP TABLE CUSTOMERS")は害を及ぼします。

準備済みステートメント

この場合、クエリとデータが分離されているため、ユーザー入力がSQLステートメントとして処理されることはありませんしたがって実行されることはありません。このため、注入された悪意のあるSQLコードが害を及ぼすことはありません。したがって、上記の場合、"DROP TABLE CUSTOMERS"は実行されません。

簡単に言えば、準備されたステートメントでは、ユーザー入力を介して導入された悪意のあるコードは実行されません!

5
N.Vegeta

SQL Server では、準備されたステートメントを使用することは、入力パラメーターがクエリを形成しないため、間違いなく注入防止です。これは、実行されたクエリが動的クエリではないことを意味します。 SQLインジェクションの脆弱なステートメントの例。

string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'";

Inoutusername変数の値がa 'または1 = 1のようなものである場合、このクエリは次のようになります。

select * from table where username='a' or 1=1 -- and password=asda

そして、残りは--の後にコメントされるので、以下のように準備されたステートメントの例を使用して実行およびバイパスされることはありません。

Sqlcommand command = new sqlcommand("select * from table where username = @userinput and password=@pass");
command.Parameters.Add(new SqlParameter("@userinput", 100));
command.Parameters.Add(new SqlParameter("@pass", 100));
command.prepare();

したがって、実際には別のパラメータを送信することはできません。したがって、SQLインジェクションを回避します...

4
lloydom

キーフレーズはneed not be correctly escapedです。つまり、ダッシュ、アポストロフィ、引用符などを挿入しようとする人を心配する必要はありません。

すべて処理されます。

3
Feisty Mango
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");

あなたが正しいサーブレットに持っていると仮定しましょう。悪意のある人が「フィルター」に悪い値を渡した場合、データベースをハッキングする可能性があります。

2
MeBigFatGuy

根本原因#1-区切り文字の問題

SQLインジェクションは、引用符を使用して文字列を区切ったり、文字列の一部にしたりすることで可能になり、時々解釈できないようになっています。文字列データで使用できない区切り文字がある場合、SQLインジェクションは発生しませんでした。区切り文字の問題を解決すると、SQLインジェクションの問題がなくなります。構造クエリはそれを行います。

根本原因#2-人間性、人々はCraftなものであり、一部のCraftな人々は悪意があるそして、すべての人が間違いを犯す

SQLインジェクションのもう1つの根本的な原因は人間の性質です。プログラマを含む人々は間違いを犯します。構造化クエリを間違えても、システムがSQLインジェクションに対して脆弱になることはありません。構造化クエリを使用していない場合、間違いによりSQLインジェクションの脆弱性が発生する可能性があります。

構造化クエリがSQLインジェクションの根本原因を解決する方法

構造化クエリは、SQLコマンドを1つのステートメントに入れ、データを別のプログラミングステートメントに入れることにより、区切り文字の問題を解決します。プログラミングステートメントは、必要な分離を作成します。

構造化されたクエリは、ヒューマンエラーによる重大なセキュリティホールの作成を防ぎます。人間がミスを犯した場合、構造クエリを使用すると、SQLインジェクションは発生しません。構造化クエリを含まないSQLインジェクションを防止する方法がありますが、そのアプローチにおける通常の人為的エラーは通常、SQLインジェクションへの少なくともある程度の露出につながります。構造化クエリは、SQLインジェクションに対してフェールセーフです。他のプログラミングと同じように、構造化されたクエリを使用して、世界中のすべての間違いを犯すことができますが、SQLインジェクションによって引き継がれるシステムに変えることはできません。そのため、人々はこれがSQLインジェクションを防ぐ正しい方法だと言いたいのです。

したがって、SQLインジェクションの原因と、使用時にそれらを不可能にする自然構造化クエリがあります。

0
DanAllen