web-dev-qa-db-ja.com

準備されたステートメントはSQLインジェクションに対して100%安全ですか?

準備されたステートメントは実際に SQLインジェクションに対して100%安全ですが、ユーザー指定のすべてのパラメーターがクエリバインドパラメーターとして渡されると想定していますか?

古いmysql_ St​​ackOverflowの関数(悲しいことに、頻度が高すぎる)準備したステートメントはSQLインジェクションセキュリティ対策のチャックノリス(またはジョンスキート)であると一般に言っています。

しかし、実際に"this is 100%safe"と断定的に述べているドキュメントは実際には見たことがありません。それらについての私の理解は、クエリ言語とパラメーターをサーバーの正面玄関までずっと分離し、サーバーの正面玄関からそれらを別個のエンティティーとして扱うことです。

この仮定は正しいですか?

80
Polynomial

SQLインジェクションから100%安全であることを保証しますか? (私から)それを取得するつもりはありません。

原則として、データベース(またはdbと対話する言語のライブラリ)は、バッファオーバーフローの悪用やユーザーのnull終了文字の使用など、何らかの高度な攻撃を受けやすい安全でない方法で、バインドされたパラメータを使用して準備済みステートメントを実装する可能性があります文字列などを提供します(これらのタイプの攻撃は、根本的に異なるため、SQLインジェクションと呼ばれるべきではないと主張できますが、それは単なる意味論です)。

実際のデータベースで準備されたステートメントに対するこれらの攻撃について聞いたことがなく、バインドされたパラメーターを使用してSQLインジェクションを防ぐことを強くお勧めします。バインドされたパラメーターや入力サニテーションがなければ、SQLインジェクションを行うのは簡単です。入力衛生だけでは、衛生の周りにあいまいな抜け穴を見つけることがかなり可能です。

バインドされたパラメーターを使用すると、SQLクエリの実行計画は、ユーザー入力に依存せずに事前に把握されるため、SQLインジェクションは不可能になります(挿入された引用符、コメント記号などは、コンパイル済みのSQLステートメント内にのみ挿入されるため)。

準備されたステートメントの使用に対する唯一の議論は、実際のクエリに応じてデータベースで実行プランを最適化することです。ほとんどのデータベースは、完全なクエリが与えられた場合、最適な実行計画を実行するのに十分スマートです。たとえば、クエリがテーブルの大部分を返す場合、テーブル全体を調べて一致を見つける必要があります。一方、数個のレコードを取得するだけの場合は、インデックスベースの検索 [1] を実行できます。

EDIT:2つの批判への対応(コメントには長すぎる):

最初に、他の人が「はい」と述べたように、準備されたステートメントとバインドされたパラメーターをサポートするすべてのリレーショナルデータベースは、バインドされたパラメーターの値を見ないで準備されたステートメントを必ずしもプリコンパイルするわけではありません。多くのデータベースは慣習的にこれを行いますが、実行計画を理解するときにデータベースがバインドされたパラメーターの値を調べることも可能です。これは問題ではありません。バインドされたパラメーターが分離された準備済みステートメントの構造により、データベースがSQLステートメント(SQLキーワードを含む)をバインドされたパラメーターのデータ(バインドされたパラメーターには何もない)から簡単に明確に区別できるためです。 SQLキーワードとして解釈されます)。これは、変数とSQLキーワードが混在する文字列連結からSQLステートメントを構築する場合は不可能です。

次に、 other answer が指摘しているように、プログラムのある時点でSQLステートメントを呼び出すときにバインドされたパラメーターを使用すると、そのトップレベルの呼び出しを行うときにSQLインジェクションを安全に防ぐことができます。ただし、アプリケーションの他の場所にSQLインジェクションの脆弱性がある場合(たとえば、文字列連結によってSQLクエリを構築するために安全でない方法で作成したデータベースに保存および実行したユーザー定義関数)。

たとえば、アプリケーションで次のような疑似コードを記述したとします。

sql_stmt = "SELECT create_new_user(?, ?)"
params = (email_str, hashed_pw_str)
db_conn.execute_with_params(sql_stmt, params)

このSQLステートメントをアプリケーションレベルで実行する場合、SQLインジェクションはありません。ただし、ユーザー定義のデータベース関数が安全に作成されていない場合(PL/pgSQL構文を使用):

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES (' || email_str || ', ' || hashed_pw_str || ');'
     EXECUTE sql_str;
END;
$$
LANGUAGE plpgsql;

この場合、SQLインジェクション攻撃に対して脆弱になります。SQLインジェクション攻撃は、SQLステートメントをユーザー定義変数の値を含む文字列と混合する文字列連結によって構築されたSQLステートメントを実行するためです。

つまり、安全でない(文字列連結によるSQLステートメントの構築)ことを試みているのでない限り、次のような安全な方法でユーザー定義を記述するほうが自然です。

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
BEGIN
     INSERT INTO users VALUES (email_str, hashed_pw_str);
END;
$$
LANGUAGE plpgsql;

さらに、ユーザー定義関数の文字列からSQLステートメントを作成する必要性を本当に感じた場合でも、ユーザー定義関数内であっても、stored_procedures/boundパラメーターと同じ方法でSQLステートメントからデータ変数を分離できます。たとえば PL/pgSQL の場合:

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES($1, $2)'
     EXECUTE sql_str USING email_str, hashed_pw_str;
END;
$$
LANGUAGE plpgsql;

したがって、準備されたステートメントを使用すると、安全でないこと(文字列の連結によってSQLステートメントを構築すること)を行わない限り、SQLインジェクションから安全です。

54
dr jimbob

100%安全ですか?程遠い。バインドされたパラメーター(ステートメントごとに準備されたかどうかにかかわらず)は、SQLインジェクションの脆弱性の1つクラスを効果的に防止できます(dbバグがなく、正気な実装を想定) )。他のクラスを妨げることは決してありません。 PostgreSQL(選択した私のデータベース)には、パラメーターをアドホックステートメントにバインドするオプションがあり、準備されたステートメントに関する特定の機能が必要ない場合に、ラウンドトリップを節約できることに注意してください。

多くの大規模で複雑なデータベースは、それ自体がプログラムであることを理解する必要があります。これらのプログラムの複雑さはかなり異なり、SQLインジェクションはプログラミングルーチンの内部で注意する必要があるものです。このようなルーチンには、トリガー、ユーザー定義関数、ストアドプロシージャなどが含まれます。多くの優れたdbaは、アプリケーションのアクセスレベルとストレージレベルの間のある程度の抽象化を提供するため、これらのものがアプリケーションレベルからどのように相互作用するかは必ずしも明らかではありません。

バインドされたパラメーターを使用すると、クエリツリーが解析され、少なくともPostgreSQLでは、計画のためにデータが調べられます。計画が実行されます。準備されたステートメントを使用すると、プランが保存されるため、異なるデータを使用して同じプランを繰り返し再実行できます(これは必要な場合とそうでない場合があります)。しかし重要なのは、バインドされたパラメーターでは、パラメーターが解析ツリーに何も注入できないことです。したがって、このクラスのSQLインジェクションの問題は適切に処理されます。

しかし、今度は、テーブルに誰が何を書き込んだかをログに記録する必要があるため、トリガーとユーザー定義関数を追加して、これらのトリガーのロジックをカプセル化します。これらは新しい問題を引き起こします。これらに動的SQLがある場合は、SQLインジェクションを心配する必要があります。それらが書き込むテーブルには、独自のトリガーなどがある場合があります。同様に、関数呼び出しは、別の関数呼び出しなどを呼び出す別のクエリを呼び出す場合があります。これらのそれぞれは、メインツリーから独立して計画されます。

つまり、foo'; drop user postgres; --のようなバインドされたパラメーターを使用してクエリを実行すると、トップレベルのクエリツリーを直接関係付けることができず、postgresユーザーを削除する別のコマンドを追加できません。ただし、このクエリが別の関数を直接呼び出すかどうかに関係なく、行のどこかで関数が脆弱になり、postgresユーザーが削除される可能性があります。バインドされたパラメーターは、セカンダリクエリにno保護を提供しました。これらのセカンダリクエリは、バインドされたパラメータも可能な限り使用することを確認する必要があり、使用できない場合は、適切な引用ルーチンを使用する必要があります。

ウサギの穴は深くなります。

ところで、この問題が明らかなスタックオーバーフローに関する質問については、 https://stackoverflow.com/questions/37878426/conditional-where-expression-in-dynamic-query/37878574#37878574 を参照してください。

https://stackoverflow.com/questions/38016764/perform-create-index-in-plpgsql-doesnt-run/38021245#38021245 の(ユーティリティステートメントの制限により)問題の多いバージョン

25
Chris Travers