プリペアドステートメントはSQLインジェクション攻撃からどのように保護できますか?

準備されたステートメントは、 SQLインジェクション攻撃?

ウィキペディアは言う:

プリペアドステートメントはSQLインジェクションに対して弾力性があります。   パラメータ値は、後に異なる   プロトコルは、正しくエスケープする必要はありません。元の声明   テンプレートは外部入力から派生したものではなく、SQLインジェクションはできません   起こる。

私はその理由をとてもうまく見ることができません。簡単な英語での簡単な説明といくつかの例は何ですか?

125

9 答え

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

SQLインジェクションの問題の根源はコードとデータのミキシングです。

実際、Googleの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);

だから、私たちのプログラムを変更して害を及ぼすことはできません。
かなり簡単ですね。

ただし、プレースホルダを使用している毎回ではありませんこと、準備済みの文として処理されることに注意してください。

プレースホルダは、実際のデータを将来の処理(例えば printf()を参照)に代入するための一般的な考え方ですが、プリペアドステートメントはその唯一のサブセットです。

準備された文をエミュレートすることができ、実際にはデータとともに作成され、1回のリクエストでサーバに送信される場合があります(特にPHPではPDOがそれを行うことができます)。しかし、データのすべてのビットがタイプに応じて適切にフォーマットされているためというこのアプローチは同等の安全性であることを理解することが重要です。

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

準備文はデータのみを保護できますが、プログラム自体を守ることはできません
たとえば、フィールド名のような動的な識別子を追加する必要があると、準備されたステートメントは私たちを助けません。 最近問題を説明した ので、私は繰り返しません。

226
追加された
$ spoiled_data = "1; DROP TABLE users;" - > $ query = "SELECT * FROM users from id = $ spoiled_data"; $ data-> 1; DROP TABLE users; - > $ db-> prepare( "SELECT * FROM users where id =?" $ db-> execute($ data); 彼らは同じことをしませんか?
追加された 著者 Juha Untinen,
すごく説明!ありがとう
追加された 著者 candh,
@AdamFは気づいてくれてありがとう、私はあまりPHPと結合した
追加された 著者 Your Common Sense,
@ zaq178miami:「PDOは、機能をサポートしていないドライバのためだけにプリペアドステートメントをエミュレートします」 - 正確ではありません。 MySQLは準備されたステートメントをかなりサポートしています。 PDOドライバも同様です。しかし、MySQLのクエリはデフォルトでPDOによって準備されていましたが、前回チェックしました。
追加された 著者 cHao,
たとえば、デフォルトではPDOはプリペアドステートメントを使用しません。PDOはそのような機能をサポートしていないドライバに対してのみプリペアドステートメントをエミュレートするため、正確ではありません。
追加された 著者 pinepain,
PDOやPHPについて何も言わなかった
追加された 著者 Adam F,
@Juha Untinenデータは何でもかまいません。それはデータを解析しません。それはコマンドではなくDATAです。したがって、$ dataにSQLコマンドが含まれていても、それは実行されません。また、idが数値の場合、文字列の内容はレポートまたは値ゼロを生成します。
追加された 著者 Soley,

例を設定するための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'と同等のものをタックして、隠されていたはずの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=?
20
追加された
データベースに接続しているプログラムからのプリペアドステートメントを使用すると、dbの一部であるプリペアドステートメントを使用するのと同じ効果がありますか?例えば、Postgresは独自の準備文を持っていて、それを使ってSQLインジェクションを防止しますか? postgresql.org/docs/9.2/static/sql-prepare.html
追加された 著者 Celeritas,
@Celeritas私はPostgresqlの決定的な答えはありません。ドキュメントを見ると、効果は同じです。 EXECUTE はパラメータを束縛する名前付きステートメントを実行しますが、 PREPARE はすでに解析された固定名付きステートメントを作成します(つまり、 。 PREPARE にはセッションの持続時間しかないので、psqlスクリプトによる注入を防ぐのではなく、パフォーマンス上の理由からのものです。 psqlアクセスの場合、ストアドプロシージャにアクセス権を与え、procs内のパラメータをバインドできます。
追加された 著者 Glenn,

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

これは、プリペアドステートメントが最初に効率的なクエリプランを見つけるためにSQLクエリを「準備」し、そのフォームが実際に実行されたときに実際に値が送られるからです。

もっと詳しい情報はこちら:

準備文とSQLインジェクション

13
追加された

In SQL Server, using a prepared statement is definitely injection-proof because the input parameters don't form the query. It means that the executed query is not a dynamic query. Example of an SQL injection vulnerable statement.

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 [email protected]");
command.Parameters.Add(new SqlParameter("@userinput", 100));
command.Parameters.Add(new SqlParameter("@pass", 100));
command.prepare();

実際には別のパラメータを送信することはできないので、SQLインジェクションを避けることができます...

4
追加された

プリペアドステートメントを作成してDBMSに送信すると、そのステートメントはSQLクエリとして格納されて実行されます。

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

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

3
追加された

キーフレーズは正しくエスケープする必要はありませんです。つまり、ダッシュ、アポストロフィ、引用符などを入れようとする人々を心配しないでください。

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

3
追加された
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");

サーブレットでそれが正しいと仮定しよう。悪意のある人物が「フィルタ」に不正な値を渡した場合、データベースをハックする可能性があります。

2
追加された

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

ナイーブアプローチ

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

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

For example, malicious user input can lead to SQLString being equal to "SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

悪意のあるユーザーのために、 SQLString には2つの文が含まれています.2番目の文( "DROP TABLE CUSTOMERS"

作成済みのステートメント

In this case, due to the separation of the query & data, the user input is never treated as a SQL statement, and thus is never executed. It is for this reason, that any malicious SQL code injected would cause no harm. So the "DROP TABLE CUSTOMERS" would never be executed in the case above.

手短かに言えば、準備されたステートメントでは、ユーザー入力によって導入された悪質なコードは実行されません!

1
追加された
本当に?受け入れられた答えはそれを正確に伝えていませんか?
追加された 著者 Your Common Sense,
答えには「実装の詳細」がありますが、そこには存在しません。
追加された 著者 Your Common Sense,
@あなたの常識知っている答えには貴重な情報がたくさんありますが、データとクエリの分離の実装の詳細が何を必要としているのでしょうか。一方、悪意を持って注入されたデータ(存在する場合)が実行されないということに焦点を当てると、頭に釘が打たれます。
追加された 著者 N.Vegeta,
私がどこから来ているのかを確認しようとすると、私の主張は次のようになります。実装の詳細を見たいという要望は、悪意のあるユーザーの入力が何も起こらない明白な理由を理解する必要性から生まれます害。あまり実装の詳細を見る必要はありません。これは、実装の詳細が、悪意を持って入力されたSQLが実行されるようなものではないことを認識し、メッセージを送信した理由です。あなたの回答は質問に答えます、どのように(要望どおり)?しかし、私は他の人々(私のような)がなぜ簡単な答えに満足するだろうと思いますか?
追加された 著者 N.Vegeta,
これは、暗黙の批判(受け入れられた応答の著者が誰であるかを理解すること)がではなく、要点を明らかにする豊かなものであると考えてください。
追加された 著者 N.Vegeta,
データがクエリとは別に送信され、プログラムがそのまま残っているとはいえ、害がいかに正確に防止されているかはまだ示されていません。クエリ部分が実行前にスクリーニングされるからですか?クエリは決して実行されないためですか?これはちょうどボンネットの下で正確に何が起こっているかを見たいという欲求を引き起こした思考プロセスです。私の回答はそれに答えてupvoteを受け取り、downvote(私はあなたから来ていると推測しています)が続き、それがなぜ他の人に役立つのかを知ることができれば幸いです。
追加された 著者 N.Vegeta,

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

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

Root Cause #2 - Human Nature, People are Crafty and Some Crafty People Are Malicious And All People Make Mistakes

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

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

構造化問合せデリミタ問題を解決するには、sqlコマンドを1つの文に入れて別のプログラミング文に入れます。プログラミングステートメントは、必要な分離を作成します。

Structured queries help prevent human error from creating critical security holes. With regard to humans making mistakes, sql injection cannot happen when structure queries are used. There are ways of preventing sql injection that don't involve structured queries, but normal human error in that approaches usually leads to at least some exposure to sql injection. Structured Queries are fail safe from sql injection. You can make all the mistakes in the world, almost, with structured queries, same as any other programming, but none that you can make can be turned into a ssstem taken over by sql injection. That is why people like to say this is the right way to prevent sql injection.

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

0
追加された