パラメータ化されていないクエリにエラーを返させないのはなぜですか?


22

SQLインジェクションは非常に深刻なセキュリティの問題です。その大部分は間違いを犯しやすいためです。ユーザー入力を組み込んだクエリを作成するための明確で直感的な方法では脆弱性が残り、それを緩和する正しい方法ではパラメーター化について知る必要があります最初にクエリとSQLインジェクション。

これを修正する明白な方法は、明白な(しかし間違った)オプションをシャットダウンすることだと思われます:パラメータの代わりにハードコードされた値をWHERE句で使用する受信したクエリが素敵で説明的なものを返すようにデータベースエンジンを修正します代わりにパラメータを使用するよう指示するエラーメッセージ。これには、管理ツールからのアドホッククエリなどを簡単に実行できるように、オプトアウトオプションが必要になることは明らかですが、デフォルトで有効にする必要があります。

これを行うと、SQLインジェクションがほぼ一晩中停止しますが、私が知る限り、実際にこれを行うRDBMSはありません。そうでない理由はありますか?


22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'単一のクエリにハードコーディングされた値とパラメータ化された値の両方が含まれます。それをキャッチしてみてください!このような混合クエリには有効なユースケースがあると思います。
アモン

6
今日からレコードを選択する方法はどうですかSELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
-Jaydee

10
@MasonWheeler申し訳ありませんが、私は「それを許可してみてください」という意味でした。完全にパラメータ化されており、SQLインジェクションの影響を受けないことに注意してください。ただし、データベースドライバーは、リテラル"bad"が本当にリテラルなのか、文字列の連結によるものなのかを判断できません。私が見る2つの解決策は、SQLおよび他の文字列埋め込みDSLを取り除くことです(はい)、またはパラメータ化されたクエリを使用するよりも文字列の連結が煩わしい言語を促進することです(umm、no)。
アモン

4
そして、RDBMSはこれを行うかどうかをどのように検出しますか?対話型のSQLプロンプトを使用してRDBMSにアクセスすることは、一晩で不可能になります...任意のツールを使用してDDLまたはDMLコマンドを入力できなくなります。
-jwenting

8
ある意味では、これを行うことができます:実行時にSQLクエリをまったく構築せず、代わりにORMまたはSQLクエリを構築する必要のない他の抽象化レイヤーを使用します。ORMには必要な機能がありませんか?そして、SQLはSQLを書きたい人向けの言語です。だからこそ、全体としてSQLを書くことができるのです。基本的な問題は、動的にコードを生成することは見た目よりも難しいことですが、人々はとにかくそれをやりたいと思っており、それらを許可しない製品には満足しません。
スティーブジェソップ

回答:


45

リテラルを使用するのが正しいアプローチである場合が多すぎます。

パフォーマンスの観点から、クエリにリテラルが必要な場合があります。パフォーマンスを心配するほど大きくなったバグトラッカーがあるとします。システム内のバグの70%は「クローズ」、20%は「オープン」、5%は「アクティブ」、5 %は他のステータスになります。すべてのアクティブなバグを返すクエリを合理的にしたい場合があります

SELECT *
  FROM bug
 WHERE status = 'active'

statusバインド変数として渡すのではなく。に渡された値に応じて異なるクエリプランstatusが必要です-テーブルスキャンを実行して、閉じられたバグとインデックススキャンを返しますstatusアクティブなローンを返す列。現在、データベースとバージョンが異なると、バインド変数の値に応じて、同じクエリで異なるクエリプランを使用できるようにするためのアプローチが(多少なりとも成功します)異なります。ただし、クエリの再解析を煩わせるか、既存のプランを新しいバインド変数値に再利用するかの決定をバランスさせるために管理する必要のあるかなりの複雑さが導入される傾向があります。開発者にとって、この複雑さに対処することは理にかなっているかもしれません。または、オプティマイザーよりも自分のデータがどのように見えるかについて、より多くの情報がある場合、別のパスを強制することは理にかなっているかもしれません。

コードの複雑さの観点から、SQLステートメントにリテラルを含めることは完全に理にかなっている場合が多くあります。たとえば、zip_code郵便番号が5文字で、場合によっては4桁の数字が追加されている列がある場合、次のようなことを行うのが最適です。

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

数値の4つの個別のパラメーターを渡すのではなく。これらは決して変化するものではないため、変数をバインドすることは、コードを潜在的に読みにくくし、誰かが間違った順序でパラメーターをバインドしてバグになる可能性を作成するのに役立ちます。


12

SQLインジェクションは、信頼されていない未検証のソースからのテキストをクエリの他の部分と連結してクエリが作成されるときに発生します。このようなことは、ほとんどの場合文字列リテラルで発生しますが、それが発生する唯一の方法ではありません。数値のクエリは、ユーザーが入力した文字列(数字のみを含むはずです)を受け取り、他の素材と連結して、通常文字列リテラルに関連付けられている引用符なしでクエリを作成します。クライアント側の検証を過度に信頼しているコードは、フィールド名がHTMLクエリ文字列に由来するようなものを持つ場合があります。SQLクエリ文字列を参照するコードが、それがどのようにアセンブルされたかを確認する方法はありません。

重要なのは、SQLステートメントに文字列リテラルが含まれているかどうかではなく、文字列に信頼できないソースからの文字シーケンスが含まれているかどうか、およびクエリのビルドライブラリでその検証が最適に処理されるかどうかです。通常、C#には、文字列リテラルを許可するが他の種類の文字列式を許可しないコードを記述する方法はありませんが、文字列の連結、および非リテラル文字列をクエリビルダーに渡すユーザーは、そのようなアクションを正当化する必要があります。


1
「リテラルですか」の近似として、文字列がインターンされているかどうかを確認できます。
CodesInChaos

1
@CodesInChaos:True。このようなテストは、実行時に文字列を生成する理由がある人は、実行時生成の文字列をインターンして使用するのではなく、非リテラル文字列を受け入れるメソッドを使用すれば、この目的に十分に正確かもしれませんそれ(非リテラル文字列メソッドに別の名前を付けると、コードレビューアがその使用をすべて簡単に検査できるようになります)。
supercat

C#でこれを行う方法はありませんが、他の一部の言語にはそれを可能にする機能があります(たとえば、Perlの汚染された文字列モジュール)。
ジュール

より簡潔に、これはクライアントの問題ではなく、サーバーの問題。
Blrfl

7
SELECT count(ID)
FROM posts
WHERE deleted = false

これらの結果をフォーラムのフッターに入れたい場合は、毎回falseと言うだけのダミーパラメーターを追加する必要があります。または、素朴なWebプログラマーは、その警告を無効にする方法を調べて、続行します。

これで、列挙型に例外を追加すると言うことができますが、それは(より小さくても)再び穴を開けるだけです。言うまでもなく、人々varcharsはそれらのために使用しないように教育する必要があります。

インジェクションの実際の問題は、クエリ文字列をプログラムで構築することです。そのためのソリューションは、ストアドプロシージャメカニズムであり、その使用または許可されたクエリのホワイトリストを強制します。


2
「忘れやすい-そもそも知らない-パラメータ化されたクエリを使用する」という解決策が「ストアドプロシージャを使用することをみんなに覚えてもらい、そもそも知ってもらう」ことなら、 '質問のすべてのポイントが欠落しています。
メイソンウィーラー

5
私は仕事でストアドプロシージャを介したSQLインジェクションを見てきました。すべてのストアドプロシージャを強制するのは悪いことです。真の動的クエリである0.5%は常に存在します(テーブル結合はもちろん、where句全体をパラメーター化することはできません)。
ジョシュア

あなたは置き換えることができ、この答えの例ではdeleted = falseNOT deletedリテラルを回避し、。しかし、ポイントは一般に有効です。
psmears

5

TL; DR:句のリテラルだけでなく、すべてのリテラルを制限する必要がありますWHERE。そうしない理由により、データベースを他のシステムから切り離したままにすることができます。

まず、あなたの前提に欠陥があります。WHERE句のみを制限したいが、それがユーザー入力の唯一の場所ではない 例えば、

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

これは、SQLインジェクションに対しても同様に脆弱です。

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

したがって、WHERE句のリテラルを単に制限することはできません。すべてのリテラルを制限する必要があります。

ここで、「なぜリテラルをまったく許可するのか」という質問が残っています。この点に注意してください:リレーショナルデータベースが別の言語、時間の大部分で書かれたアプリケーションの下に使用されている間、何もありません要件データベースを使用するようにアプリケーションのコードを使用しなければならないことは。そして、ここに答えがあります:コードを書くにはリテラルが必要です。他の唯一の選択肢は、すべてのコードをデータベースに依存しない言語で記述することです。したがって、それらを使用すると、「コード」(SQL)をデータベースに直接書き込むことができます。これは価値のある分離であり、リテラルなしでは不可能です。(リテラルなしで好きな言語で書いてみてください。これがどれほど難しいか想像できると思います。)

一般的な例として、リテラルは値リスト/ルックアップテーブルの作成によく使用されます。

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

それらがなければ、このテーブルにデータを入力するためだけに別のプログラミング言語でコードを書く必要があります。SQLで直接そうする能力は貴重です。

その後、もう1つ質問があります。なぜプログラミング言語のクライアントライブラリはそれをしないのですか?そして、ここに非常に簡単な答えがあります:彼らは、データベースのサポートされているバージョンごとにデータベースパーサー全体再実装するでしょう。どうして?すべてのリテラルを見つけたことを保証する方法は他にないからです。正規表現では十分ではありません。たとえば、これにはPostgreSQLの4つの個別のリテラルが含まれます。

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

特に、データベースのメジャーリリース間で有効な構文が頻繁に変更されるため、これを実行しようとするとメンテナンスの悪夢になります。

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