ここでは、コードでパラメーター化された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関数を使用するだけで十分かどうかです。私はこの安全対策を破るコードの例を見つけようとしてきましたが、その例は見つかりません。
これを破ることができる人はいますか?どうしますか?
EDIT:これまでの返信を要約するには:
だから誰もSafeDBString関数の単純なセキュリティを破ることができなかったが、他にもたくさんの良い議論があった。ありがとう!
正しい答えは次のとおりです。
自分でセキュリティをしようとしないでください。 tryingではなく、あなたがやろうとしていることに使用できる信頼できる業界標準ライブラリを使用してください。セキュリティに関してどのような仮定を行っても、間違っている可能性があります。独自のアプローチが安全に見える(そしてせいぜい不安定に見える)ので、何かを見落としているリスクがあり、セキュリティに関してはそのチャンスを本当に使いたいですか?
パラメーターを使用します。
そして、誰かが行って、 'の代わりに "を使用します。パラメータは、IMOで唯一の安全な方法です。
また、日付/数値に関するi18nの問題を回避します。 01/02/03は何日ですか? 123,456はいくらですか?サーバー(app-serverとdb-server)は互いに同意していますか?
リスク要因が彼らに納得できない場合、パフォーマンスはどうですか?パラメーターを使用すると、RDBMSはクエリプランを再利用できるため、パフォーマンスが向上します。これは文字列だけではできません。
議論は勝てない。脆弱性を見つけることができた場合、同僚はそれを説明するためにSafeDBString関数を変更し、再び安全でないことを証明するように求めます。
パラメータ化されたクエリは、議論の余地のないプログラミングのベストプラクティスであるため、より安全でパフォーマンスの高いメソッドを使用していない理由を説明するための証拠の負担が必要です。
問題がすべてのレガシコードを書き換えている場合、簡単な妥協点は、すべての新しいコードでパラメーター化されたクエリを使用し、古いコードをリファクタリングしてそのコードで作業することです。
私の推測では、実際の問題は誇りと頑固さであり、あなたがそれについてできることはこれ以上ありません。
まず、「置換」バージョンのサンプルが間違っています。テキストをアポストロフィで囲む必要があります。
_string sql = "SELECT * FROM Users WHERE Name='" + SafeDBString(name) & "'";
SqlCommand getUser = new SqlCommand(sql, connection);
_
これが、パラメーターが行うもう1つのことです。値を引用符で囲む必要があるかどうかを心配する必要はありません。もちろん、それを関数に組み込むこともできますが、関数に多くの複雑さを追加する必要があります。「NULL」としての「NULL」と単なる文字列としての「NULL」、または数字とたまたま多くの数字を含む文字列。バグの別のソースにすぎません。
もう1つのことはパフォーマンスです。パラメーター化されたクエリプランは、多くの場合、連結されたプランよりも適切にキャッシュされるため、クエリを実行するときのサーバーのステップを節約できます。
さらに、単一引用符をエスケープするだけでは十分ではありません。多くのDB製品では、攻撃者が利用できる文字をエスケープする代替方法が許可されています。たとえば、MySQLでは、一重引用符をバックスラッシュでエスケープすることもできます。したがって、次の「名前」の値はSafeDBString()
関数だけでMySQLを爆破します。単一引用符を2重にすると、最初の引用符はバックスラッシュによってエスケープされ、2番目の引用符は「アクティブ」のままになるためです:
x\'OR 1 = 1;-
また、JulianRは以下の優れた点をもたらします。[〜#〜] never [〜#〜]セキュリティを試みる自分で働きます。綿密なテストを行ったとしても、appearが機能するように微妙な方法でセキュリティプログラミングを間違えるのは非常に簡単です。それから時間が経ち、1年後にシステムが6か月前にクラックされたことがわかり、その時までそれを知りませんでした。
プラットフォームに提供されているセキュリティライブラリに可能な限り依存してください。それらは生計のためにセキュリティコードを作成する人々によって書かれ、管理できるものよりもはるかによくテストされ、脆弱性が見つかった場合はベンダーによってサービスされます。
だから私は言うだろう:
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();
}
正しいか間違っているかは、セキュリティの問題なしに必要な情報を提供してくれます。
パラメータ化されたクエリを使用すると、SQLインジェクションに対する保護以上のものを取得できます。また、実行計画のキャッシングの可能性が向上します。 SQLサーバークエリプロファイラーを使用する場合、「データベースで実行される正確なSQL」が表示されるため、SQLステートメントのデバッグに関しても何も失われません。
私は両方のアプローチを使用してSQLインジェクション攻撃を回避し、間違いなくパラメーター化されたクエリを好みます。連結されたクエリを使用したとき、ライブラリ関数を使用して変数をエスケープしました(mysql_real_escape_stringなど)。
パラメーターを使用しないと、ユーザー入力の型チェックを簡単に行うことはできません。
SQLCommandクラスとSQLParameterクラスを使用してDB呼び出しを行っている場合でも、実行されているSQLクエリを確認できます。 SQLCommandのCommandTextプロパティを見てください。
私は常に、パラメーター化されたクエリが非常に使いやすい場合に、SQLインジェクションを防止するための独自のアプローチに少し疑いを持っています。第二に、「常にそのように行われている」からといって、それが正しい方法であることを意味するわけではありません。
これは、文字列を渡すことが保証されている場合にのみ安全です。
ある時点で文字列を渡さないとどうなりますか?数字だけを渡すとどうなりますか?
http://www.mywebsite.com/profile/?id=7;DROP DATABASE DB
最終的に次のようになります。
SELECT * FROM DB WHERE Id = 7;DROP DATABASE DB
セキュリティ問題に大いに同意します。
パラメータを使用するもう1つの理由は効率のためです。
データベースは常にクエリをコンパイルしてキャッシュし、キャッシュされたクエリを再利用します(以降のリクエストでは明らかに高速です)。パラメーターを使用すると、異なるパラメーターを使用しても、データベースはパラメーターをバインドする前にSQLストリングに基づいて一致するキャッシュクエリを再利用します。
ただし、パラメーターをバインドしない場合、SQLストリングはすべての要求(異なるパラメーターを持つ)で変更され、キャッシュ内の内容と一致することはありません。
すべてにストアドプロシージャまたは関数を使用するので、問題は発生しません。
SQLをコードに配置する必要がある場合は、パラメーターを使用しますが、これが唯一の理にかなっています。反対者に、彼らよりも賢いハッカーがいることを思い出させてください。そして、ハッカーは彼らをだまそうとしているコードを破ろうとするより良いインセンティブがあります。パラメータを使用することは、単に不可能であり、難しいことではありません。
「なぜ自分でやるのが悪いのか」のこの側面に対処する他のアンサーは見ていませんが、 SQL Truncation attack を検討してください。
また、QUOTENAME
T-SQL関数もあり、paramsを使用するように説得できない場合に役立ちます。逃げられたクートの懸念の多く(すべて?)をキャッチします。
破損する可能性がありますが、手段は正確なバージョン/パッチなどに依存します。
既に提起されているものの1つは、悪用可能なオーバーフロー/切り捨てのバグです。
別の将来的な手段は、他のデータベースと同様のバグを見つけることです-たとえば、特定のUTF8シーケンスを使用して置換関数を操作できるため、MySQL/PHPスタックでエスケープの問題が発生しました-置換関数はintroducing =注入文字。
結局のところ、置換セキュリティメカニズムはexpectedに依存していますが、intendedの機能には依存していません。機能はコードの意図された目的ではなかったため、発見されたいくつかの癖が期待される機能を壊す可能性が高い可能性があります。
レガシーコードが多数ある場合、replaceメソッドを一時的なギャップとして使用して、長時間の書き換えやテストを避けることができます。新しいコードを書いている場合、言い訳はできません。
同僚を説得するのに役立つ記事をいくつか紹介します。
http://www.sommarskog.se/dynamic_sql.html
http://unixwiz.net/techtips/sql-injection.html
個人的には、動的コードがデータベースにアクセスすることを許可せず、すべての連絡先をsps(動的SQlを使用するものではなく)を介して要求することを好みません。これは、ユーザーに許可を与えたことを何も実行できないことを意味し、内部ユーザー(管理目的で本番アクセスを持つごく少数のユーザーを除く)は、テーブルに直接アクセスできず、大混乱を引き起こし、データを盗み、詐欺を犯すことができません。金融アプリケーションを実行する場合、これが最も安全な方法です。
既に説明した理由から、パラメーターは非常に良いアイデアです。しかし、パラメーターを作成し、後でクエリで使用するためにその名前を変数に割り当てることは、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();
}
}
}
2年後 、私は再分割した...痛みを感じるパラメータを見つけた人は誰でも私のVS Extensionを試してみてください。QueryFirst 。実際の.sqlファイル(検証、Intellisense)でリクエストを編集します。パラメータを追加するには、「@」で始まるSQLに直接入力します。ファイルを保存すると、QueryFirstはラッパークラスを生成し、クエリを実行して結果にアクセスできるようにします。パラメータのDBタイプを検索し、.netタイプにマップします。これは、生成されたExecute()メソッドへの入力として検出されます。 もっとシンプルにできなかった。適切な方法で実行することは、他の方法で実行するよりも根本的に迅速かつ簡単であり、SQLインジェクションの脆弱性を作成することは不可能になり、少なくとも逆に困難になります。 DBの列を削除し、アプリケーションのコンパイルエラーをすぐに確認できるなど、他の致命的な利点があります。
免責事項:私はQueryFirstを書いた
可能な場合は常にパラメーター化されたクエリを使用します。変な文字を使用しない単純な入力でさえ、データベースのフィールドへの入力として識別されない場合、SQLインジェクションを作成することがあります。
したがって、データベースに入力そのものを識別する作業を任せてください。もちろん、エスケープまたは変更される奇妙な文字を実際に挿入する必要がある場合、それはトラブルの多くを節約します。入力を計算する必要がないため、最終的には貴重なランタイムを節約することもできます。
SQLインジェクションの問題を調査しなければならなかった非常に短い時間から、値を「安全」にすることは、データに実際にアポストロフィが必要な状況への扉を閉ざしていることを意味することがわかります-誰かの名前はどうですか、たとえばO'Reilly。
パラメータとストアドプロシージャが残ります。
そして、はい、あなたは常にあなたが今知っている最良の方法でコードを実装しようとする必要があります-それが常に行われた方法だけではありません。
もう1つの重要な考慮事項は、エスケープされたデータとエスケープされていないデータを追跡することです。データが未加工のユニコード、&エンコード、フォーマットされたHTMLなどであるかどうかを適切に追跡できないように思われるWebなどのアプリケーションが山ほどあります。どの文字列が''
–エンコードされ、どの文字列がエンコードされていないかを追跡することが難しくなることは明らかです。
また、何らかの変数のタイプを変更する場合にも問題になります。おそらく以前は整数でしたが、現在は文字列になっています。これで問題が発生しました。
以下に、パラメーター化されたクエリを使用するいくつかの理由を示します。
SQLステートメントのバッファオーバーフローに関連する脆弱性はほとんどありませんでした(どのデータベースであったか思い出せません)。
私が言いたいのは、SQLインジェクションは単なる「引用をエスケープする」以上のものであり、あなたは次に何が来るかわからないということです。