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


172

準備されたステートメントはSQLインジェクション攻撃の防止にどのように役立ちますか?

ウィキペディアは言う:

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

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

回答:


290

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

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削除されるため、これは機能します。

一方で準備された文の場合には、我々は我々のプログラムを変更しない、それがそのまま残る
ポイントだという。

最初にサーバーにプログラムを送信します

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

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

まったく同じクエリがデータなしでサーバーに送信されることに注意してください!次に、2番目のリクエストでデータを送信します。基本的にはクエリ自体から分離されています。

$db->execute($data);

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

追加する必要がある唯一のことは、すべてのマニュアルで常に省略されています:

準備済みステートメントで保護できるのはデータリテラルのみですが、他のクエリ部分では使用できません。
したがって、たとえば動的識別子(フィールド名など)を追加する必要がある場合、準備されたステートメントは役に立ちません。私は最近その問題説明したので、繰り返しはしません。


2
「たとえば、デフォルトではPDOは準備済みステートメントを使用しない」-PDOはそのような機能をサポートしないドライバーに対してのみ準備済みステートメントをエミュレートするため、正確には当てはまりません。
pinepain 2013年

3
@ zaq178miami:「PDOは、この機能をサポートしていないドライバーに対してのみ、準備されたステートメントをエミュレートします」-正確には正しくありません。MySQLはかなり長い間、準備されたステートメントをサポートしてきました。PDOドライバーも同様です。しかし、それでも、MySQLクエリはデフォルトでPDOによってまだ準備されていました。
cHao 2013

9
異なる何か$spoiled_data = "1; DROP TABLE users;"- > $query = "SELECT * FROM users where id=$spoiled_data";、に比べて:$db->prepare("SELECT * FROM users where id=?");- > $data = "1; DROP TABLE users;"- > $db->execute($data);。彼らは同じことをしませんか?
Juha Untinen 2014

14
@Juha Untinenデータは何でもかまいません。データは解析されません。コマンドではなくDATAです。したがって、$ dataにsqlコマンドが含まれていても、実行されません。また、IDが数値の場合、文字列コンテンツはレポートまたは値ゼロを生成します。
Soley、2015

21

例を設定するための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句の 'or true'に相当するものを追加することでクエリを破壊する入力を操作し、隠されているはずのAaronに関する情報を含むすべてが返されるようにします。

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=?

データベースに接続するプログラムの準備済みステートメントを使用すると、dbの一部である準備済みステートメントを使用した場合と同じ効果がありますか?たとえば、Postgresには独自の準備済みステートメントがあり、それを使用するとSQLインジェクションを防ぐことができますか?postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas 2015年

@Celeritas Postgresqlの決定的な答えはありません。ドキュメントを見ると、効果は同じであるように見えます。パラメータをバインドする名前付きステートメントを実行しPREPAREながら、既に解析されている(つまり、入力に関係なくステートメントが変更されない)固定の名前付きステートメントを作成EXECUTEします。以来PREPAREのみがセッションの持続時間を有することがないpsqlのスクリプトを経由して注入を防止するために、パフォーマンス上の理由のために意味されるように、それは本当に見えます。psqlアクセスの場合、ストアドプロシージャに権限を付与し、プロシージャ内のパラメータをバインドできます。
Glenn、

@Celeritas x86_64でPostgreSQL 11.1を使用して上記のコードを試したところ、上記のSQLiの例が機能しました。
クリシュナパン

15

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

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

ここにさらに詳しい情報:

準備されたステートメントとSQLインジェクション


6

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

素朴なアプローチ

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

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

たとえば、悪意のあるユーザー入力はSQLString"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

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

準備されたステートメント

この場合、クエリとデータが分離されているため、ユーザー入力はSQLステートメントとして扱われず、実行されません。このため、悪意のあるSQLコードが挿入されても害はありません。したがって、"DROP TABLE CUSTOMERS"上記のケースでは決して実行されません。

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


本当に?受け入れられた答えはそれを正確に伝えていませんか?
あなたの常識

@あなたの常識受け入れられた答えは多くの貴重な情報で満たされていますが、データとクエリの分離の実装の詳細には何が伴うのか不思議に思いました。一方、悪意を持って挿入されたデータ(存在する場合)が実行されないという点に焦点を合わせると、頭の釘が当たります。
N.Vegeta

また、回答に記載されていない「実装の詳細」はどれですか。
常識

私がどこから来たのかを確認しようとすると、私のポイントは次のとおりであることがわかります。実装の詳細を確認したいという短い要望は、悪意のあるユーザーの入力が原因とならない明確な理由を理解する必要から生じたものです。害。実装の詳細を確認する必要はそれほどありません。これが、実装の詳細が悪意を持って入力されたSQLが実行されることは決してなく、メッセージをホームに送信するようなものであることに気付いた理由です。あなたの応答はどのように(要求されたとおりに)質問に答えますか?しかし、他の人々(私のような)がその理由に対する簡潔な応答で満足すると思いますか?
N.Vegeta

これは、暗黙の批評ではなく、核心を解明する富化だと考えてください(容認は、受け入れられた応答の著者が誰であるかを理解しました)。
N.Vegeta

5

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

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

つまり、準備されたステートメントを使用してSQLインジェクションを実行することは基本的に不可能です。準備されたステートメントの本質とDBMSとの関係がこれを防ぎます。


4

では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インジェクションを回避できます...


3

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

それはすべてあなたのために処理されます。


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

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


0

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

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

根本的な原因#2-人間の本性、人々は狡猾であり、一部の巧妙な人々は悪意があり 、すべての人々が間違いを犯します

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

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

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

構造化クエリは、人為的ミスによる重大なセキュリティホールの発生を防ぐのに役立ちます。 人間がミスをすることに関して、構造クエリが使用されている場合、SQLインジェクションは発生しません。構造化クエリを使用しないSQLインジェクションを防ぐ方法はいくつかありますが、そのアプローチでの通常の人為的エラーは、通常、SQLインジェクションに少なくともある程度の影響を及ぼします。構造化クエリは、SQLインジェクションからフェイルセーフです。構造化クエリを使用すると、他のプログラミングと同じように、世界中のすべての間違いを犯す可能性がありますが、SQLインジェクションによって引き継がれるシステムに変えることはできません。これが、SQLインジェクションを防ぐ正しい方法であると人々が言いたがる理由です。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.