SQLステートメントで常にパラメーターを使用することを好むのはなぜですか?


114

私はデータベースを扱うことに非常に慣れていません。今私は書くことができますSELECTUPDATEDELETE、およびINSERTコマンド。しかし、私は私たちが書くことを好む多くのフォーラムを見てきました:

SELECT empSalary from employee where salary = @salary

...の代わりに:

SELECT empSalary from employee where salary = txtSalary.Text

なぜ常にパラメーターを使用することを好み、どのように使用するのですか?

最初の方法の使用方法と利点を知りたいと思いました。SQLインジェクションについてさえ聞いたことがありますが、完全には理解していません。SQLインジェクションが私の質問に関連しているかどうかもわかりません。


2
そうですね、これはSQLインジェクションに関連しています。パラメータの処理方法は通常、プログラムが実行している言語/フレームワークの責任であり、言語に依存する場合があります。RDBMS(参考)とORMフレームワーク(必須)の両方を投稿してください。
時計じかけのミューズ

1
プログラミング言語としてC#を、データベースとしてSQL Server 2008を使用しています。Microsoft dotNet framework 4.0を使用しています。私は本当に申し訳ありません、あなたが何を求めているのかわからない(RDBMSまたはORM)どうもありがとう
Sandy

1
RDBMSは、SQL Server 2008の場合のデータベースです。ORMは、データベース(この場合はADO.NET)にアクセスする方法です。その他には、LINQ to SQLEntity Frameworkが含まれます。実際、ADO.NETとSQLの基本を学んだら、LINQやEFなどのORMを使用することをお勧めします。これらは、手動でSQLを記述することによって発生する問題の多くを処理するためです。
チャド・レヴィ

回答:


129

データベースをデスクトッププログラムやWebサイトなどのプログラムインターフェイスと組み合わせて使用する場合、パラメーターを使用するとSQLインジェクション攻撃を防ぐことができます

この例では、ユーザーはでステートメントを作成することにより、データベースでSQLコードを直接実行できますtxtSalary

たとえば、ユーザーがを記述した0 OR 1=1場合、実行されるSQLは次のようになります。

 SELECT empSalary from employee where salary = 0 or 1=1

これにより、すべてのempSalariesが返されます。

さらに、ユーザーは次のように書いた場合、データベースを削除するなど、データベースに対してさらに悪いコマンドを実行する可能性があります0; Drop Table employee

SELECT empSalary from employee where salary = 0; Drop Table employee

employeeその後、テーブルは削除されます。


あなたの場合、あなたは.NETを使用しているようです。パラメータの使用は次のように簡単です:

C#

string sql = "SELECT empSalary from employee where salary = @salary";

using (SqlConnection connection = new SqlConnection(/* connection info */))
using (SqlCommand command = new SqlCommand(sql, connection))
{
    var salaryParam = new SqlParameter("salary", SqlDbType.Money);
    salaryParam.Value = txtMoney.Text;

    command.Parameters.Add(salaryParam);
    var results = command.ExecuteReader();
}

VB.NET

Dim sql As String = "SELECT empSalary from employee where salary = @salary"
Using connection As New SqlConnection("connectionString")
    Using command As New SqlCommand(sql, connection)
        Dim salaryParam = New SqlParameter("salary", SqlDbType.Money)
        salaryParam.Value = txtMoney.Text

        command.Parameters.Add(salaryParam)

        Dim results = command.ExecuteReader()
    End Using
End Using

2016-4-25を編集:

George Stockerのコメントに従って、サンプルコードを使用しないように変更しましたAddWithValue。また、IDisposablesをusingステートメントでラップすることをお勧めします。


素晴らしいソリューション。しかし、パラメータの使用が安全である理由と方法について、もう少し詳しく説明できますか。つまり、sqlコマンドは同じになるように見えます
Sandy

sqlコマンドに複数のパラメーターを追加できますか?INSERTコマンドで必要になるかもしれませんか?
サンディ

2
SQL Serverは、パラメーター内のテキストを入力としてのみ扱い、決して実行しません。
チャド・レヴィ

3
はい、複数のパラメータを追加できますInsert Into table (Col1, Col2) Values (@Col1, @Col2)。コードに複数AddWithValueのを追加します。
チャド・レヴィ

1
AddWithValueは使用しないでください!暗黙的な変換の問題が発生する可能性があります。常にサイズを明示的に設定し、でパラメーター値を追加しますparameter.Value = someValue
ジョージストッカー2015年

75

そうです、これはSQLインジェクションに関連しています。これは、悪意のあるユーザーがデータベースに対して任意のステートメントを実行することを可能にする脆弱性です。この昔からお気に入りのXKCDコミックは、概念を示しています。

彼女の娘は、私が運転免許工場に閉じ込められているヘルプと呼ばれています。


あなたの例では、あなたが単に使用した場合:

var query = "SELECT empSalary from employee where salary = " + txtSalary.Text;
// and proceed to execute this query

SQLインジェクションを利用できます。たとえば、誰かがtxtSalaryと入力したとします。

1; UPDATE employee SET salary = 9999999 WHERE empID = 10; --
1; DROP TABLE employee; --
// etc.

このクエリを実行すると、それが実行されますSELECTUPDATEDROP、あるいは彼らが望んでいたものは何でも。--最後には、単にあなたが後に何かを連結した場合、攻撃において有用であろうクエリの残りの部分を、コメントアウトtxtSalary.Text


正しい方法は、パラメーター化されたクエリを使用することです(例:(C#))。

SqlCommand query =  new SqlCommand("SELECT empSalary FROM employee 
                                    WHERE salary = @sal;");
query.Parameters.AddWithValue("@sal", txtSalary.Text);

これにより、クエリを安全に実行できます。

他のいくつかの言語でSQLインジェクションを回避する方法については、SOユーザーが管理するWebサイトbobby-tables.comを確認してください。


1
素晴らしいソリューション。しかし、パラメータの使用が安全である理由と方法について、もう少し詳しく説明できますか。つまり、sqlコマンドは同じように見えます。
サンディ

1
@ user815600:よくある誤解-パラメータを使用したクエリは値を受け取り、実際の値をパラメータに置き換えると信じていますか?いいえ、これは起こっていません!-代わりに、パラメーターを含むSQLステートメントがパラメーターとその値のリストと共にSQL Serverに送信されます
-SQL

1
これは、SQLインジェクションがSQLサーバーの内部メカニズムまたはセキュリティによって監視されていることを意味します。ありがとう。
サンディ

4
私が漫画が好きなのと同じように、テーブルを削除する十分な特権でコードを実行している場合は、おそらくより広い問題が発生します。
philw 2013

9

他の回答に加えて、そのパラメータを追加する必要があるため、SQLインジェクションを防ぐだけでなく、クエリのパフォーマンスを向上させることできます。SQLサーバーは、パラメーター化されたクエリプランをキャッシュし、クエリの繰り返し実行時にそれらを再利用します。クエリのパラメーター化を行わない場合、クエリのテキストが異なる場合、SQLサーバーは各クエリの実行時に(いくつかの除外を含めて)新しいプランをコンパイルします。

クエリプランのキャッシュの詳細


1
これは、思っているよりも適切です。「小さな」クエリでも数千回または数百万回実行され、クエリキャッシュ全体が効果的にフラッシュされます。
James

5

私が最初に行ってから2年後、私は再婚しています...

なぜパラメーターを好むのですか?SQLインジェクションが大きな理由であることは明らかですが、SQL を言語として取り戻すことを密かに望んでいるのかもしれません。文字列リテラル内のSQLは、すでに奇妙な文化的慣習ですが、少なくとも、リクエストをコピーしてManagement Studioに貼り付けることができます。SQLに条件と制御構造がある場合、ホスト言語の条件と制御構造で動的に構築されるSQLは、レベル0の野蛮です。アプリが生成するSQLを確認するには、アプリをデバッグまたはトレースで実行する必要があります。

パラメータだけで停止しないでください。ずっと行き、QueryFirst(免責事項:私が書いたもの)を使用します。SQL は.sqlファイルにあります。素晴らしいTSQLエディターウィンドウで編集し、テーブルと列の構文検証とIntellisenseを使用します。特別なコメントセクションでテストデータを割り当て、[再生]をクリックして、ウィンドウ内でクエリを実行できます。パラメータの作成は、SQLに "@myParam"を置くのと同じくらい簡単です。その後、保存するたびに、QueryFirstはクエリのC#ラッパーを生成します。厳密に型指定されたパラメーターが、Execute()メソッドへの引数としてポップアップします。結果は、IEnumerableまたは厳密に型指定されたPOCOのリストで返されます。POCOは、クエリによって返される実際のスキーマから生成された型です。クエリが実行されない場合、アプリはコンパイルされません。dbスキーマが変更され、クエリが実行されても一部の列が表示されない場合、コンパイルエラーはコードの行を指しています不足しているデータにアクセスしようとします。そして、他にも多くの利点があります。他の方法でデータにアクセスしたいのはなぜですか?


4

SQLでは、任意の単語に@記号が含まれている場合、それは変数であることを意味します。この変数を使用して値を設定し、同じSQLスクリプトの数値領域で使用します。これは、多くの変数を宣言できる一方で、単一のスクリプトでのみ制限されているためです。多くのスクリプトで同じタイプと名前の。ストアドプロシージャは事前にコンパイルされたクエリであるため、この変数をストアドプロシージャロットで使用します。詳細については、ローカル変数の宣言SQLストアドプロシージャ、およびSQLインジェクションを参照するために、スクリプト、デスクトップ、およびWebサイトからこれらの変数に値を渡すことができます。

また、SQLインジェクションからの保護を読んで、データベースを保護する方法を説明してください。

それがあなたがどんな質問コメントも私を理解するのを助けるのを助けることを望みます。


3

他の回答は、なぜパラメーターが重要であるかをカバーしていますが、欠点もあります!.netには、パラメーターを作成するためのいくつかの方法(Add、AddWithValue)がありますが、それらはすべて、パラメーター名について不必要に心配する必要があり、すべてコード内のSQLの可読性を低下させます。SQLについて瞑想しようとしているときは、パラメーターで使用されている値を確認するために、上または下を探し回る必要があります。

私は私の小さなSqlBuilderクラスがパラメーター化されたクエリを記述する最もエレガントな方法であると謙虚に主張しています。コードは次のようになります...

C#

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

コードは短くなり、読みやすくなります。追加の行も必要ありません。また、読み返すときに、パラメータの値を探す必要はありません。必要なクラスはこちらです...

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

public class SqlBuilder
{
private StringBuilder _rq;
private SqlCommand _cmd;
private int _seq;
public SqlBuilder(SqlCommand cmd)
{
    _rq = new StringBuilder();
    _cmd = cmd;
    _seq = 0;
}
public SqlBuilder Append(String str)
{
    _rq.Append(str);
    return this;
}
public SqlBuilder Value(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append(paramName);
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public SqlBuilder FuzzyValue(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append("'%' + " + paramName + " + '%'");
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public override string ToString()
{
    return _rq.ToString();
}
}

パラメータに名前を付けると、サーバーが実行しているクエリをプロファイリングするときに役立ちます。
Dave R.

上司も同じことを言った。意味のあるパラメーター名が重要な場合は、paramName引数を値メソッドに追加します。あなたは不必要に物事を複雑にしていると思います。
bbsimonbb 2015年

悪いアイデア。以前に述べたように、AddWithValue暗黙的な変換の問題を引き起こす可能性があります。
Adam Calvet Bohl 2017年

@Adamあなたは正しいですが、それはAddWithValue()が非常に広く使われるのを止めるものではありません、そしてそれがアイデアを無効にすることはないと思います。しかし、それまでの間、パラメーター化されたクエリを作成するより優れた方法を思いついたので、AddWithValue()を使用していません:-)
bbsimonbb '25年

正しい!すぐに確認することを約束します。
Adam Calvet Bohl 2017年

3

古い投稿ですが、新参者がストアドプロシージャを認識できるようにしたいと考えていました。

ここで私の10分の1の価値は、SQLステートメントをストアドプロシージャとして記述できる場合は、これが最適なアプローチであるということです。私は常に私のメインのコードでレコードを保存procsの、決してループを使用します。例:SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class

ストアドプロシージャを使用すると、ユーザーをEXECUTE権限のみに制限できるため、セキュリティリスク軽減できます

ストアドプロシージャは本質的にパラメーター化されており、入力パラメーターと出力パラメーターを指定できます。

ストアドプロシージャ(SELECTステートメントを介してデータを返す場合)にはSELECT、コード内の通常のステートメントとまったく同じ方法でアクセスして読み取ることができます。

また、SQL Serverでコンパイルされるため、実行速度も速くなります。

updateテーブル、別のDBサーバーで値を確認するなどの複数のステップを実行し、最終的に終了すると、すべて同じサーバー上でクライアントにデータを返し、クライアントとのやり取りがないことについても触れました。したがって、このロジックをコードでコーディングするよりもはるかに高速です。

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