パラメータなしでSQLインジェクションを回避する


109

ここで、コードでパラメーター化されたSQLクエリを使用することについて、別の議論が行われています。議論には2つの側面があります。私と、SQLインジェクションから常に保護するためにパラメーターを使用する必要があると言う他の人と、それが必要だと思わない他の人です。代わりに、SQLインジェクションを回避するために、すべての文字列で単一のアポストロフィを2つのアポストロフィに置き換えたいと考えています。私たちのデータベースはすべてSql Server 2005または2008を実行しており、コードベースは.NET Framework 2.0で実行されています。

C#で簡単な例を挙げましょう。

私はこれを使って欲しい:

string sql = "SELECT * FROM Users WHERE Name=@name";
SqlCommand getUser = new SqlCommand(sql, connection);
getUser.Parameters.AddWithValue("@name", userName);
//... blabla - do something here, this is safe

他の人がこれをしたい間:

string sql = "SELECT * FROM Users WHERE Name=" + SafeDBString(name);
SqlCommand getUser = new SqlCommand(sql, connection);
//... blabla - are we safe now?

SafeDBString関数は次のように定義されています。

string SafeDBString(string inputValue) 
{
    return "'" + inputValue.Replace("'", "''") + "'";
}

これで、クエリのすべての文字列値でSafeDBStringを使用する限り、安全であるはずです。正しい?

SafeDBString関数を使用する理由は2つあります。1つ目は、石器時代からのやり方です。2つ目は、データベースで実行されている正確なクエリが表示されるため、SQLステートメントのデバッグが簡単です。

それで。私の質問は、SQLインジェクション攻撃を回避するためにSafeDBString関数を使用するだけで十分かどうかです。私はこの安全対策を破るコードの例を見つけようとしましたが、その例は見つかりません。

これを壊すことができる誰かがいますか?どうしますか

編集: これまでの返信を要約するには:

  • Sql Server 2005または2008でSafeDBStringを回避する方法はまだ見つかっていません。それは良いと思いますか?
  • パラメータ化されたクエリを使用するとパフォーマンスが向上することがいくつかの回答で指摘されています。その理由は、クエリプランを再利用できるためです。
  • また、パラメーター化されたクエリを使用すると、より読みやすいコードが維持されやすくなることに同意します
  • さらに、SafeDBStringのさまざまなバージョン、文字列から数値への変換、文字列から日付への変換を使用するよりも、常にパラメーターを使用する方が簡単です。
  • パラメータを使用すると、自動型変換が行われます。これは、日付や10進数を扱うときに特に役立ちます。
  • そして最後に:JulianRが書いたように自分でセキュリティを試みようとしないでください。データベースベンダーは、セキュリティに多くの時間とお金を費やしています。私たちがもっと上手にできる方法はなく、私たちが彼らの仕事をしようとする理由はありません。

したがって、SafeDBString関数の単純なセキュリティを破ることができた人はいませんでしたが、私は他の多くの良い議論を得ました。ありがとう!


16
あなたの同僚は、道を外れ、道を外れています。彼らの立場を支持する一冊の文学を見つけるように彼らに挑戦してください。ネオリソスの前の議論はばかげています、物事は変わります、石器時代で立ち往生している人だけが適応できません。
アナカタ2009年

1
ええと、少なくともあなたの同僚は、ハッキングのさまざまな形式の1つから保護しています...彼らはすべてのパラメータ化されたクエリが実行することを確信していますか?(私は
違い

1
いずれの脆弱性もそれらを説得しません。いくつかの脆弱性(これはあなたが求めているものです)と他の問題をもたらし、パラメータがその問題を解決し、機能の一部を提供するためにチームがコードの山を書かなければならないことを1つずつ指摘する場合、それらに勝つ。幸運を。
ロバートゴーランド

3
一重引用符がなくても、ロジックでコードを分割できます。ユーザー名「test OR 1 = 1」を使用してみてください。ユーザー名testの行だけでなく、すべての行が返されます。

1
はぁ。私が業界としてこの種の専門外の振る舞いを許容し続けている方法は本当にわかりません。
jeroenh

回答:


83

正解は次のとおりです。

自分でセキュリティを設定しようとしないでください。信頼できる業界標準のライブラリを利用して、自分でやろうとするのではなく、自分がやろうとしていることに利用できます。セキュリティについてどのような仮定をしても、正しくない可能性があります。あなた自身のアプローチが見えるかもしれません(そしてそれはせいぜい不安定であるように見えます)と同じくらい安全ですが、あなたが何かを見落としているリスクがあり、セキュリティに関してはそのチャンスを本当に取りたいですか?

パラメータを使用します。


「信頼できる業界標準のライブラリを使用する」について-.NET用に推奨できますか?SQLServer、MySQL、PostgreSQLのDBによっては、複数あるのでしょうか?私はSQLサニタイザーを探しましたが、あまり運がなかったので、可能な限り自分で実装することを余儀なくされました(間違いなく間違いのないことです)。
PSU、

72

そして誰かが "の代わりに"を使います。パラメータはIMOだけが安全な方法です。

また、日付/数値に関する多くのi18n問題を回避します。01/02/03は何日ですか?123,456はいくらですか?サーバー(app-serverとdb-server)は互いに同意していますか?

リスク要因が説得力がない場合、パフォーマンスはどうですか?パラメータを使用すると、RDBMSはクエリプランを再利用できるため、パフォーマンスが向上します。文字列だけでこれを行うことはできません。


私はフォーマットとパフォーマンスの議論を試しましたが、それでもまだ確信が持てません。
ルーングリムスタッド

5
実際、SQLサーバーは、パラメーターを使用するかどうかに関係なく、クエリプランを再利用できます。私は他の引数に同意しますが、ほとんどの場合、パラメータ化されたSQLのパフォーマンス引数はもはや機能しません。
tnyfst 2009年

1
@tnyfst:パラメータ値のすべての組み合わせでクエリ文字列が変更されたときに、実行プランを再利用できますか?それは可能だとは思いませんでした。
ジョンサンダース

4
クエリテキストが以前のクエリテキストと同一である場合、クエリプランは再利用されます。そのため、EXACT SAMEクエリを2回送信すると、再利用されます。ただし、スペースやコンマなどを変更した場合でも、新しいクエリプランを決定する必要があります。
marc_s 2009年

1
@マーク:あなたが完全に正しいかどうかはわかりません。SQL Serverのキャッシュヒューリスティックは少し奇妙です。パーサーは、テキスト内の定数を識別でき、SQL文字列を、パラメーターを使用するものに人工的に変換できます。次に、この新しいパラメーター化されたクエリのテキストをキャッシュに挿入できます。後続の同様のSQLでは、パラメーター化されたバージョンがキャッシュ内で一致する場合があります。ただし、パラメーター化されたバージョンが常にキャッシュされる元のSQLバージョンで常に使用されるとは限りません。SQLには、2つのアプローチのどちらかを選択するためのパフォーマンス関連の理由が無数にあると思います。
AnthonyWJones、2009年

27

議論は勝てない。どうにかして脆弱性を見つけた場合、同僚はSafeDBString関数を変更してそれを説明し、それが再び安全でないことを証明するように求めます。

パラメータ化されたクエリは誰もが認めるプログラミングのベストプラクティスであることを考えると、証明の負担は、安全でパフォーマンスの高い方法を使用していない理由を説明することです。

問題がすべてのレガシーコードの書き換えである場合、簡単な妥協策は、すべての新しいコードでパラメーター化されたクエリを使用し、古いコードをリファクタリングして、そのコードで作業するときに使用することです。

私の推測では、実際の問題はプライドと頑固さであり、それについてあなたができることはこれ以上ありません。


19

まず、「置き換え」バージョンのサンプルが間違っています。テキストをアポストロフィで囲む必要があります。

string sql = "SELECT * FROM Users WHERE Name='" + SafeDBString(name) & "'";
SqlCommand getUser = new SqlCommand(sql, connection);

つまり、パラメーターが行うもう1つのことは、値を引用符で囲む必要があるかどうかを気にする必要がないことです。もちろん、それを関数に組み込むこともできますが、その場合、関数に多くの複雑さを追加する必要があります。NULLとしての「NULL」と単なる文字列としての「NULL」の違い、または数値とたまたまたくさんの数字を含んでいる文字列。これはバグのもう1つの原因です。

もう1つはパフォーマンスです。パラメーター化されたクエリプランは、連結されたプランよりも多くの場合キャッシュされるため、クエリを実行するときにサーバーを一歩節約できます。

さらに、単一引用符をエスケープするだけでは十分ではありません。 多くのDB製品では、攻撃者が利用する可能性のある文字をエスケープする別の方法が許可されています。たとえば、MySQLでは、バックスラッシュを使用して単一引用符をエスケープすることもできます。したがって、次の "name"値は、SafeDBString()関数だけでMySQLを爆破します。単一引用符を二重にしても、最初の引用符はバックスラッシュによってエスケープされ、2番目の引用符は「アクティブ」のままです。

x \ 'OR 1 = 1;-


:また、JulianRは、下の良い点アップします NEVERセキュリティの仕事を自分でやろうありませんが。徹底的なテストを行っても、機能しているように見える微妙な方法でセキュリティプログラミングを誤解するのは非常に簡単です。その後時間が経過し、1年後、システムが6か月前にクラックされたことが判明し、それまでそれを知ることができませんでした。

常に、プラットフォームに提供されているセキュリティライブラリに可能な限り依存してください。それらは、生活のためにセキュリティコードを実行する人々によって書かれ、管理できるものよりもはるかによくテストされ、脆弱性が見つかった場合はベンダーによってサービスされます。


5
replace関数はアポストロフィを追加します
Rune Grimstad '26年

5
次に、それはバグのもう1つの原因です。NULL値としてのNULLとテキスト文字列としてのNULLの違いはどのようにしてわかりますか?または、数値入力とたまたま数字を含む文字列の間ですか?
Joel Coehoorn、2009年

いい視点ね。文字列、および場合によっては日付に対してのみ関数を使用する必要があるため、注意が必要です。これが、パラメーターを使用するもう1つの理由です。わーい!
Rune Grimstad

10

だから私は言うでしょう:

1)組み込まれているものを再実装しようとしているのはなぜですか?すぐに利用でき、使いやすく、すでにグローバル規模でデバッグされています。将来のバグが見つかった場合、何もしなくても修正され、誰でもすぐに利用できます。

2)SafeDBStringの呼び出しを見逃さないこと保証するためにどのようなプロセスが用意されていますか?それを1か所に欠けていると、多くの問題が発生する可能性があります。あなたはこれらのことをどれだけ目撃するつもりですか?そして、受け入れられた正解が到達するのがとても簡単であるとき、その努力がどれだけ無駄になるかを考えてください。

3)Microsoft(DBの作成者とアクセスライブラリ)がSafeDBString実装で知っているすべての攻撃経路を覆い隠したことはどれほど確かですか。

4)SQLの構造を読み取るのはどれくらい簡単ですか?この例では+連結を使用しており、パラメーターはstring.Formatと非常によく似ています。

また、実際に実行されたものを解決する方法は2つあります。独自のLogCommand関数、セキュリティ上の懸念のない単純な関数をロールする方法、またはSQLトレースを調べて、データベースが実際に何が起こっているかを把握する方法です。

LogCommand関数は次のとおりです。

    string LogCommand(SqlCommand cmd)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine(cmd.CommandText);
        foreach (SqlParameter param in cmd.Parameters)
        {
            sb.Append(param.ToString());
            sb.Append(" = \"");
            sb.Append(param.Value.ToString());
            sb.AppendLine("\"");
        }
        return sb.ToString();
    }

正しいか間違っているかを問わず、必要な情報をセキュリティの問題なく提供しています。


1
彼はおそらく、XMLやSQLを含むすべてのことを文字列連結で行うことに慣れている、多くの古いVBSCRIPTプログラマーに対処しなければなりません。これらは、APIの使用に恐怖を感じる人々です。彼らにできることは何もない、少なくとも人道的なものはない。
ジョンサンダース

1
アイテム#2の+1。ただし、実際のパラメーターを強制する方法もありません。
Joel Coehoorn、2009年

7

パラメータ化されたクエリを使用すると、SQLインジェクションに対する保護以上の効果が得られます。また、実行プランのキャッシュの可能性が向上します。SQLサーバークエリプロファイラーを使用している場合でも、 'データベースで実行されている正確なSQL'を確認できるため、SQLステートメントのデバッグに関して何も失われていません。


MySQLはまた、パラメータ値が挿入されたパラメータ値のクエリを記録します。
ビルカーウィン、

5

SQLインジェクション攻撃を回避するために両方のアプローチを使用しており、パラメーター化されたクエリを確実に優先しています。連結クエリを使用したときは、ライブラリ関数を使用して変数をエスケープし(mysql_real_escape_stringなど)、独自の実装ですべてをカバーしたとは確信していません(あなたもそうです)。


2
mysql_real_escape_string()は\ x00、\ x1a、\ n \ r 'および "をエスケープするため、+ 1。文字セットの問題も処理します。OPの同僚のナイーブ関数はそれを行いません!
Bill Karwin

4

パラメータを使用しないと、ユーザー入力の型チェックを簡単に行うことができません。

SQLCommandクラスとSQLParameterクラスを使用してDB呼び出しを行っている場合でも、実行中のSQLクエリを確認できます。SQLCommandのCommandTextプロパティを見てください。

私は、パラメーター化されたクエリが非常に使いやすいときにSQLインジェクションを防止するための独自のアプローチを常に疑っています。第二に、「常にそのように行われている」からといって、それが正しい方法であるとは限りません。


3

これは、文字列を渡すことが保証されている場合にのみ安全です。

文字列を渡さない場合はどうなりますか?数値だけを渡すとどうなりますか?

http://www.mywebsite.com/profile/?id=7;DROP DATABASE DB

最終的には:

SELECT * FROM DB WHERE Id = 7;DROP DATABASE DB

文字列または数値です。文字列はSafeDbStringでエスケープされます。数値はInt32であり、データベースを削除できません。
Andomar

数字の方が扱いやすいです。クエリで使用する前に、パラメーターをint / float / whateverに変換するだけです。問題は、文字列データを受け入れる必要がある場合です。
ルーングリムスタット

Andomar-SQLステートメントを手動で作成するだけの場合、意図する「タイプ」は問題ではなく、非常に簡単に番号をSQLインジェクションできます。ルーン-SQLインジェクションを手動で解決するすべてのニュアンスを覚えておくには、これは個々の開発者に依存しすぎていると思います。「パラメータを使用する」とだけ言うと、それは非常に簡単で、間違いはありません。
joshcomley 2009年

@Andomar:NULLはどうですか?または数字のように見える文字列?
Joel Coehoorn、2009年

2

私はすべてにストアドプロシージャまたは関数を使用するので、問題は発生しません。

SQLをコードに配置する必要がある場合は、パラメーターを使用しますが、これは意味のある唯一のものです。ハッカーが実際よりも賢く、彼らを裏切ろうとしているコードを解読するより良いインセンティブで、反対者に思い出させます。パラメータを使用すると、それは単に不可能であり、難しいことではありません。


さて、パラメーターを使用してSQLインジェクションを行う方法は?
John Saunders、

@Saunders:ステップ1は、DBのパラメーター処理機能でバッファーオーバーフローのバグを見つけることです。
ブライアン、

2
まだ見つけましたか?毎日何十万人ものハッカーに押し寄せている商用DBでは?非常に深いポケットを持っていることが知られているソフトウェア会社によって作られたもの?可能であれば、名前で訴訟を引用することができます。
John Saunders、

1
もちろん、SPROCが連結とEXEC(sp_ExecuteSQLの代わりに)を使用している場合、問題が発生します...(私は何度も間違っているため、割り引くのを見ていません...)
Marc Gravell

2

セキュリティ問題について非常に同意します。
パラメータを使用するもう1つの理由は、効率のためです。

データベースは常にクエリをコンパイルしてキャッシュし、キャッシュされたクエリを再利用します(これは後続のリクエストに対して明らかに高速です)。パラメータを使用する場合、別のパラメータを使用しても、SQLストリングに基づいて一致するため、データベースはキャッシュされたクエリを再利用してから、パラメータをバインドします。

ただし、パラメーターをバインドしない場合、SQL文字列はすべての要求(パラメーターが異なる)で変更され、キャッシュ内のものと一致することはありません。


2

既に与えられた理由から、パラメータは非常に良い考えです。しかし、パラメーターを作成し、後でそのクエリで使用するためにその名前を変数に割り当てるのは、3つの間接間接的な問題であるため、それらを使用するのは嫌いです。

次のクラスは、SQLリクエストの作成に通常使用する文字列ビルダーをラップします。これにより、パラメーターを作成する必要なくパラメーター化されたクエリ記述できるため、SQLに集中できます。コードは次のようになります...

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId, SqlDbType.Int);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName, SqlDbType.NVarChar);
myCommand.CommandText = bldr.ToString();

コードの可読性は、あなたが同意することを望みますが、大幅に改善されており、出力は適切なパラメーター化されたクエリです。

クラスは次のようになります...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace myNamespace
{
    /// <summary>
    /// Pour le confort et le bonheur, cette classe remplace StringBuilder pour la construction
    /// des requêtes SQL, avec l'avantage qu'elle gère la création des paramètres via la méthode
    /// Value().
    /// </summary>
    public class SqlBuilder
    {
        private StringBuilder _rq;
        private SqlCommand _cmd;
        private int _seq;
        public SqlBuilder(SqlCommand cmd)
        {
            _rq = new StringBuilder();
            _cmd = cmd;
            _seq = 0;
        }
        //Les autres surcharges de StringBuilder peuvent être implémenté ici de la même façon, au besoin.
        public SqlBuilder Append(String str)
        {
            _rq.Append(str);
            return this;
        }
        /// <summary>
        /// Ajoute une valeur runtime à la requête, via un paramètre.
        /// </summary>
        /// <param name="value">La valeur à renseigner dans la requête</param>
        /// <param name="type">Le DBType à utiliser pour la création du paramètre. Se référer au type de la colonne cible.</param>
        public SqlBuilder Value(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append(paramName);
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this;
        }
        public SqlBuilder FuzzyValue(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append("'%' + " + paramName + " + '%'");
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this; 
        }

        public override string ToString()
        {
            return _rq.ToString();
        }
    }
}

1

SQLインジェクションの問題を調査する必要があった非常に短い時間から、値を「安全」にすることは、データに実際にアポストロフィが必要になる状況への扉を閉ざしていることも意味することがわかります-誰かの名前はどうですか? 、例えばオライリー。

パラメータとストアドプロシージャが残ります。

そして、はい、常にそれがいつもどのように行われたかだけでなく、あなたが今知っている最善の方法で常にコードを実装するように努めるべきです。


オライリーは=「O''Reilly」名前に変換されますので、二重のアポストロフィは、単一のアポストロフィにSQL Serverので翻訳される
ルーングリムスター

それで、ユーザーがデータを見たいときにアポストロフィを削除するための対応する関数はありますか?
quamrana 2009年

必要なし。エスケープシーケンスにより、パーサーは文字列の終わりではなく単一引用符を見ることができます。解析中''はリテラルとして認識される'ため、文字列は内部的に文字シーケンスとして認識されますO'Reilly。これが、DBが保存、取得、比較などを行うものです。エスケープした後にユーザーにデータを表示したい場合は、エスケープされていない文字列のコピーをappsideに保持します。
cHao

1

同僚を説得するのに役立つと思われる記事をいくつか紹介します。

http://www.sommarskog.se/dynamic_sql.html

http://unixwiz.net/techtips/sql-injection.html

個人的には、動的コードがデータベースに触れないようにしたいので、すべての連絡はspsを経由する必要があります(動的SQlを使用する連絡先は必要ありません)。これは、ユーザーに許可を与えたことを除けば何もできず、内部ユーザー(管理目的での本番アクセス権を持つ非常に少数のユーザーを除く)がテーブルに直接アクセスして大混乱を作成したり、データを盗んだり、詐欺を犯したりすることができないことを意味します。金融アプリケーションを実行する場合、これが最も安全な方法です。


1

壊れる可能性がありますが、手段は正確なバージョン/パッチなどによって異なります。

すでに取り上げられているのは、悪用可能なオーバーフロー/トランケーションのバグです。

別の将来の手段は、他のデータベースと同様のバグを見つけることです。たとえば、MySQL / PHPスタックは、特定のUTF8シーケンスを使用して置換関数を操作できるため、エスケープ問題が発生しました-置換関数は、注入文字を導入するようにだまされます。

結局のところ、交換用のセキュリティメカニズムは、想定されている意図されていない機能に依存してます。機能はコードの意図された目的ではなかったため、発見された奇妙なものによって期待された機能が損なわれる可能性が高くなります。

多くのレガシーコードがある場合、replaceメソッドを一時的なギャップとして使用して、長い書き換えとテストを回避できます。新しいコードを書いているなら、言い訳はありません。


1

可能な限り、常にパラメーター化されたクエリを使用します。奇妙な文字を使用しない単純な入力でも、データベース内のフィールドの入力として識別されていない場合、SQLインジェクションが作成されることがあります。

したがって、データベースに入力自体を識別する作業を任せるだけでなく、実際にエスケープまたは変更される奇妙な文字を実際に挿入する必要がある場合にも、問題を回避できます。最終的には、入力を計算する必要がないため、貴重なランタイムを節約することもできます。


1

他の回答者が「なぜ自分で行うのが悪いのか」というこの側面に対処することは見ませんでしたが、SQL切り捨て攻撃を検討してください。

ありQUOTENAMEますがのparamsを使用するように説得することができない場合に役立ちますT-SQLの関数が。エスケープされたqouteの懸念の多く(すべて?)をキャッチします。


1

2年後、私は再犯しました...パラメータに苦痛を見つけた人は誰でも私のVS拡張機能QueryFirstを試すことができます。実際の.sqlファイル(検証、Intellisense)でリクエストを編集します。パラメータを追加するには、「@」で始まるSQLに直接入力するだけです。ファイルを保存すると、QueryFirstはラッパークラスを生成し、クエリを実行して結果にアクセスできるようにします。パラメータのDBタイプを検索し、それを.netタイプにマップします。これは、生成されたExecute()メソッドへの入力として見つかります。簡単にできない。正しい方法で行うことは、他の方法で行うよりも根本的に迅速かつ簡単であり、SQLインジェクションの脆弱性を作成することが不可能になるか、少なくとも逆に困難になります。DB内の列を削除して、アプリケーションのコンパイルエラーをすぐに確認できるなど、他にも大きな利点があります。

法的免責事項:私はQueryFirstを書きました


0

パラメータ化されたクエリを使用するいくつかの理由を次に示します。

  1. セキュリティ-データベースアクセスレイヤーは、データで許可されていないアイテムを削除またはエスケープする方法を認識しています。
  2. 懸念の分離-私のコードは、データベースが好む形式にデータを変換する責任はありません。
  3. 冗長性なし-このデータベースのフォーマット/エスケープを行うすべてのプロジェクトにアセンブリまたはクラスを含める必要はありません。クラスライブラリに組み込まれています。

0

SQLステートメントのバッファオーバーフローに関連する脆弱性はほとんどありませんでした(どのデータベースだったかは思い出せません)。

私が言いたいのは、SQL-Injectionは単なる「エスケープクォート」であり、次に何が起こるかわからないということです。


0

別の重要な考慮事項は、エスケープされたデータとエスケープされていないデータを追跡することです。Webなど、アプリケーションが無数にあり、データがraw-Unicode、&-encoded、フォーマット済みHTMLなどである場合を適切に追跡していないようです。''エンコードされている文字列とエンコードされていない文字列を追跡することが困難になることは明らかです。

変数の型を変更してしまう場合も問題になります。以前は整数でしたが、現在は文字列になっています。今、あなたは問題を抱えています。

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