準備されたステートメントは、SQLインジェクション攻撃の防止にどのように役立ちますか?
ウィキペディアは言う:
後で別のプロトコルを使用して送信されるパラメーター値を正しくエスケープする必要がないため、準備されたステートメントはSQLインジェクションに対して回復力があります。元のステートメントテンプレートが外部入力から派生していない場合、SQLインジェクションは発生しません。
その理由がよくわかりません。簡単な英語での簡単な説明といくつかの例は何でしょうか?
準備されたステートメントは、SQLインジェクション攻撃の防止にどのように役立ちますか?
ウィキペディアは言う:
後で別のプロトコルを使用して送信されるパラメーター値を正しくエスケープする必要がないため、準備されたステートメントはSQLインジェクションに対して回復力があります。元のステートメントテンプレートが外部入力から派生していない場合、SQLインジェクションは発生しません。
その理由がよくわかりません。簡単な英語での簡単な説明といくつかの例は何でしょうか?
回答:
アイデアは非常に単純です。クエリとデータは別々にデータベースサーバーに送信されます。
それで全部です。
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);
したがって、プログラムを変更して害を及ぼすことはできません。
かなりシンプルですよね?
追加する必要がある唯一のことは、すべてのマニュアルで常に省略されています:
準備済みステートメントで保護できるのはデータリテラルのみですが、他のクエリ部分では使用できません。
したがって、たとえば動的識別子(フィールド名など)を追加する必要がある場合、準備されたステートメントは役に立ちません。私は最近その問題を説明したので、繰り返しはしません。
$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);
。彼らは同じことをしませんか?
例を設定するための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=?
PREPARE
ながら、既に解析されている(つまり、入力に関係なくステートメントが変更されない)固定の名前付きステートメントを作成EXECUTE
します。以来PREPARE
のみがセッションの持続時間を有することがないpsqlのスクリプトを経由して注入を防止するために、パフォーマンス上の理由のために意味されるように、それは本当に見えます。psqlアクセスの場合、ストアドプロシージャに権限を付与し、プロシージャ内のパラメータをバインドできます。
基本的に、準備されたステートメントでは、潜在的なハッカーから入ってくるデータはデータとして扱われます-そして、それをアプリケーションSQLと混合したり、SQLとして解釈したりすることはできません(渡されたデータが直接アプリケーションSQL)。
これは、準備済みステートメントが最初にSQLクエリを "準備"して効率的なクエリプランを見つけ、後でフォームから取得されたと思われる実際の値を送信するためです(クエリが実際に実行されるときに)。
ここにさらに詳しい情報:
私は答えを読みましたが、それでも準備されたステートメントの本質を明らかにする重要なポイントを強調する必要性を感じました。ユーザー入力が関係するデータベースを照会する2つの方法を検討してください。
素朴なアプローチ
1つは、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"
上記のケースでは決して実行されません。
一言で言えば、準備されたステートメントで、ユーザー入力を介して導入された悪意のあるコードは実行されません!
では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インジェクションを回避できます...
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");
あなたがそれをあなたが正しいサーブレットに持っていると仮定しましょう。悪意のある人が 'filter'に悪い値を渡した場合、データベースをハッキングする可能性があります。
根本的な原因#1-区切り文字の問題
引用符を使用して文字列を区切り、文字列の一部にすることでSQLインジェクションが可能になり、文字列を解釈できない場合があります。文字列データで使用できない区切り文字がある場合、SQLインジェクションは発生しませんでした。区切り文字の問題を解決すると、SQLインジェクションの問題が解消されます。構造化クエリはそれを行います。
根本的な原因#2-人間の本性、人々は狡猾であり、一部の巧妙な人々は悪意があり 、すべての人々が間違いを犯します
SQLインジェクションのもう1つの根本的な原因は人間の性質です。プログラマーを含む人々は間違いを犯します。構造化クエリを間違えても、システムがSQLインジェクションに対して脆弱になることはありません。構造化クエリを使用していない場合、間違いがSQLインジェクションの脆弱性を引き起こす可能性があります。
構造化クエリがSQLインジェクションの根本原因を解決する方法
構造化クエリは、SQLコマンドを1つのステートメントに入れ、データを別のプログラミングステートメントに入れることで、区切り文字の問題を解決します。プログラミングステートメントは、必要な分離を作成します。
構造化クエリは、人為的ミスによる重大なセキュリティホールの発生を防ぐのに役立ちます。 人間がミスをすることに関して、構造クエリが使用されている場合、SQLインジェクションは発生しません。構造化クエリを使用しないSQLインジェクションを防ぐ方法はいくつかありますが、そのアプローチでの通常の人為的エラーは、通常、SQLインジェクションに少なくともある程度の影響を及ぼします。構造化クエリは、SQLインジェクションからフェイルセーフです。構造化クエリを使用すると、他のプログラミングと同じように、世界中のすべての間違いを犯す可能性がありますが、SQLインジェクションによって引き継がれるシステムに変えることはできません。これが、SQLインジェクションを防ぐ正しい方法であると人々が言いたがる理由です。
したがって、SQLインジェクションの原因と、使用時にそれらを不可能にする性質の構造化クエリがあることになります。