web-dev-qa-db-ja.com

パラメーター化されたステートメントはすべてのSQLインジェクションを停止できますか?

はいの場合、なぜそれほど多くのSQLインジェクションが成功するのですか?一部の開発者があまりにも愚かすぎて、パラメーター化されたステートメントを使用できないからですか?

73
iceagle

質問へのコメントに投稿したリンクは、問題を非常によく説明しています。問題が続く理由についての私の気持ちを以下にまとめました。

  1. 始めたばかりの人は、SQLインジェクションに気付かないかもしれません。

  2. SQLインジェクションを知っている人もいますが、エスケープは(唯一の?)ソリューションだと思います。 php mysql queryをすばやくGoogle検索すると、最初に表示されるページは mysql_query ページです。このページには、エスケープされたユーザー入力をクエリに挿入する例を示しています。代わりに準備されたステートメントを使用することについては言及されていません(少なくとも、私にはわかりません)。他の人が言ったように、パラメータ補間を使用するチュートリアルは非常に多くありますが、それがどれほど頻繁に使用されるかは驚くことではありません。

  3. パラメーター化されたステートメントがどのように機能するかについての理解不足。一部の人々は、それが値をエスケープする単なる空想的な手段であると考えています。

  4. 他の人はパラメーター化されたステートメントを認識していますが、遅すぎると聞いたため、それらを使用しないでください。パラメーター化されたステートメントが非常に遅いことを多くの人が聞いたことがあると思いますが、実際に自分でテストを行ったことはありません。ビル・カーウィンが彼の講演で指摘したように、準備されたステートメントの使用を検討するとき、パフォーマンスの違いが要因として使用されることはめったにありません。 prepareを1回実行し、多くを実行するという利点は、しばしば忘れられているように見えますが、セキュリティとコードの保守性の向上も同様です。

  5. どこでもパラメーター化されたステートメントを使用するものもありますが、テーブル名や列名、キーワード、条件演算子などの未チェックの値を補間します。ユーザーがさまざまな検索フィールド、比較条件、並べ替え順序を指定できるような動的検索は、この典型的な例です。

  6. ORMを使用するときの誤った安心感。 ORMでは、引き続きSQLステートメント部分の補間が可能です-5。

  7. プログラミングは大きく複雑な主題であり、データベース管理は大きく複雑な主題であり、セキュリティは大きく複雑な主題です。安全なデータベースアプリケーションの開発は簡単ではありません-経験豊富な開発者でさえも理解できます。

  8. Stackoverflowの答えの多くは役に立たない。人々が動的SQLとパラメーター補間を使用する質問を書くとき、代わりにパラメーター化されたステートメントを使用することを提案する応答が不足していることがよくあります。いくつかの機会に、準備されたステートメントを使用するという私の提案に反論しました-通常、許容できないパフォーマンスのオーバーヘッドが原因です。これらの質問のほとんどを尋ねている人たちが、パラメーター化されたステートメントを準備するのに余分な数ミリ秒かかると、彼らのアプリケーションに壊滅的な影響を与える立場にあることを真剣に疑います。

61
Mike

記事がSQL攻撃を阻止するパラメーター化されたクエリについて話すとき、彼らは実際に理由を説明しません。悪い教育者の確かな兆候は、彼らが何かを知らないことを認めることができないものです。しかし、私は脱線します。私が言うのは、混乱するのは完全に理解できることは簡単だということです。動的SQLクエリを想像してください

sqlQuery='SELECT * FROM custTable WHERE User=' + Username + ' AND Pass=' + password

したがって、単純なSQLインジェクションは、ユーザー名を 'OR 1 = 1--

sqlQuery='SELECT * FROM custTable WHERE User='' OR 1=1-- ' AND PASS=' + password

これは、ユーザー名が空白( '')または1 = 1(ブール値)で、trueに等しいすべての顧客を選択することを意味します。次に-を使用して、クエリの残りの部分をコメント化します。したがって、これはすべての顧客テーブルを印刷するか、または必要に応じて何でも実行します。ログインすると、最初のユーザーの権限(多くの場合は管理者)でログインします。

現在、パラメータ化されたクエリは、次のようなコードを使用して異なる方法で実行します。

sqlQuery='SELECT * FROM custTable WHERE User=? AND Pass=?'

parameters.add("User", username)
parameters.add("Pass", password)

ここで、ユーザー名とパスワードは、関連する入力されたユーザー名とパスワードを指す変数です

この時点で、あなたは考えているかもしれませんが、これは何も変えません。もちろん、ユーザー名フィールドにNobody OR 1 = 1 '---

sqlQuery='SELECT * FROM custTable WHERE User=Nobody OR 1=1'-- AND Pass=?'

そして、これは有効な議論のように思えます。しかし、あなたは間違っているでしょう。

パラメータ化されたクエリが機能する方法は、sqlQueryがクエリとして送信され、データベースがこのクエリの処理内容を正確に認識し、その値が設定された場合のみユーザー名とパスワードを値として挿入することです。これは、データベースがクエリの処理内容をすでに知っているため、クエリに影響を与えることができないことを意味します。そのため、この場合、「Nobody OR 1 = 1 '-」のユーザー名と空のパスワードを検索しますが、これは間違っているはずです。

ただし、これは完全なソリューションではありません。また、JavaScriptをデータベースに入れることができるため、XSS攻撃などの他の問題には影響しないため、入力の検証を行う必要があります。次に、これがページに読み込まれると、出力検証に応じて、通常のjavascriptとして表示されます。そのため、実際に行うのに最適なのは入力検証を使用することですが、パラメーター化されたクエリまたはストアドプロシージャを使用してSQL攻撃を阻止します。

48
Josip Ivic

いい質問ですね。答えは決定論的よりも確率論的であり、小さな例を使用して私の見解を説明しようとします。

ネット上には、クエリでパラメーターを使用するか、SQLインジェクション(SQLi)を回避するためにパラメーターを持つストアドプロシージャを使用することを示唆する多くの参照があります。ストアドプロシージャ(たとえば)がSQLiに対する魔法の棒ではないことを示します。責任はプログラマに残っています。

テーブル 'Users'からユーザー行を取得する次のSQL Serverストアドプロシージャを検討してください。

create procedure getUser
 @name varchar(20)
,@pass varchar(20)
as
declare @sql as nvarchar(512)
set @sql = 'select usrID, usrUName, usrFullName, usrRoleID '+
           'from Users '+
           'where usrUName = '''+@name+''' and usrPass = '''+@pass+''''
execute(@sql)

ユーザー名とパスワードをパラメーターとして渡すことで、結果を取得できます。パスワードがフリーテキスト(この例の単純化のため)であるとすると、通常の呼び出しは次のようになります

DECLARE @RC int
DECLARE @name varchar(20)
DECLARE @pass varchar(20)

EXECUTE @RC = [dbo].[getUser] 
   @name = 'admin'
  ,@pass = '!@Th1siSTheP@ssw0rd!!'
GO

しかし、ここでは、ストアドプロシージャ内でプログラマが使用する不適切なプログラミング手法があるため、攻撃者は次のことを実行できます。

DECLARE @RC int
DECLARE @name varchar(20)
DECLARE @pass varchar(20)

EXECUTE @RC = [TestDB].[dbo].[getUser] 
   @name = 'admin'
  ,@pass = 'any'' OR 1=1 --'
GO

上記のパラメーターは引数としてストアドプロシージャに渡され、最終的に実行されるSQLコマンドは次のとおりです。

select usrID, usrUName, usrFullName, usrRoleID 
from Users 
where usrUName = 'admin' and usrPass = 'any' OR 1=1 --'

..これにより、すべての行がユーザーから返されます

ここでの問題は、「ストアドプロシージャを作成し、パラメータとして検索するフィールドを渡す」という原則に従っても、SQLiが実行されることです。これは、ストアドプロシージャ内に不適切なプログラミング手法をコピーするだけだからです。この問題の解決策は、ストアドプロシージャを次のように書き換えることです。

alter procedure getUser
 @name varchar(20)
,@pass varchar(20)
as
select usrID, usrUName, usrFullName, usrRoleID 
from Users 
where usrUName = @name and usrPass = @pass

私が言いたいのは、開発者は最初にSQLi攻撃とは何か、どのように実行できるかを学ばなければならないということです。 「ベストプラクティス」を盲目的に守ることは常に安全な方法ではありません...そして、おそらくこれが非常に多くの「ベストプラクティス」-失敗がある理由です!

10

はい、少なくとも理論上は、準備済みステートメントを使用すると、すべてのSQLインジェクションが停止します。実際には、パラメータ化されたステートメントは、実際に準備されたステートメントではない場合があります。 PDO in PHPはデフォルトでそれらをエミュレートするので、 エッジケース攻撃にさらされる です。

実際に準備されたステートメントを使用している場合、すべてが安全です。少なくとも、たとえばテーブル名を準備できないことに対する反応として、安全でないSQLをクエリに連結しない限りは。

はいの場合、なぜそれほど多くのSQLインジェクションが成功するのですか?一部の開発者があまりにも愚かすぎて、パラメーター化されたステートメントを使用できないからですか?

はい、教育はここでの主要なポイントであり、レガシーコードベースです。多くのチュートリアルではエスケープを使用しており、残念ながらこれらをWebから簡単に削除することはできません。

5
kelunik

プログラミングの絶対的なことは避けます。常に例外があります。ストアドプロシージャとコマンドオブジェクトを強くお勧めします。私のバックグラウンドの大部分はSQL Serverを使用していますが、時々MySqlを使用します。キャッシュされたクエリプランなど、ストアドプロシージャには多くの利点があります。はい、これはパラメーターとインラインSQLで実現できますが、インジェクション攻撃の可能性が広がり、懸念の分離には役立ちません。私のアプリケーションは通常、上記のストアドプロシージャの実行権限しか持っていないため、データベースをセキュリティで保護する方がはるかに簡単です。テーブル/ビューへの直接アクセスがなければ、何かを注入することははるかに困難です。アプリケーションユーザーが侵害された場合、ユーザーは事前に定義された内容を正確に実行する権限のみを持ちます。

私の2セント。

3
Derek

「バカ」とは言いません。

チュートリアルが問題だと思います。ほとんどのSQLチュートリアル、書籍、インライン値でSQLを説明するものは何でも、バインドパラメーターはまったく言及していません。これらのチュートリアルから学習する人々は、正しく学習する機会がありません。

2
Markus Winand

パラメーター化されたステートメントはすべてのSQLインジェクションを停止できますか?

はい、データベースドライバーがプレースホルダーを提供している限り可能なすべてのSQLリテラルに対してほとんどの準備済みステートメントドライバーはそうではありません。たとえば、フィールド名や値の配列のプレースホルダーは決して見つかりません。これにより、開発者は、連結と手動の書式設定を使用して、手動でクエリを調整することになります。 予測結果付き

そのため、MySQLラッパーをPHP用に作成しました。これは、配列や識別子など、クエリに動的に追加できるほとんどのリテラルをサポートしています。

はいの場合、なぜそれほど多くのSQLインジェクションが成功するのですか?一部の開発者があまりにも愚かすぎて、パラメーター化されたステートメントを使用できないからですか?

ご覧のとおり、実際には、あなたが愚かではない場合でも、クエリをパラメーター化することは不可能ですall

2

最初の最初の質問に対する私の答え:はい、私が知る限り、パラメーター化されたクエリを使用することにより、SQLインジェクションはもう不可能になります。あなたの次の質問に関して、私は確信が持てず、理由について私の意見を述べることができるだけです。

挿入する値といくつかの異なる部分(論理チェックに依存している場合もある)を連結することで、SQLクエリ文字列を「単に」記述する方が簡単だと思います。クエリを作成して実行するだけです。もう1つの利点は、SQLクエリ文字列を(エコー、出力、その他)印刷し、データベースエンジンへの手動クエリにこの文字列を使用できることです。

プリペアドステートメントを使用する場合、少なくとも1つ以上の手順が必要です。クエリ(もちろん、パラメーターを含む)を作成する必要がありますサーバーでクエリを準備する必要がありますパラメーターを必要な実際の値にバインドする必要がありますクエリに使用するにはクエリを実行する必要があります。

これは、特に非常に長命であることが判明している「クイックでダーティな」ジョブの場合、特に多少の作業です(プログラムするのはそれほど簡単ではありません)。

宜しくお願いします、

ボックス

2
TomS

ほとんどのコードはセキュリティと管理を念頭に置いて書かれていないため、機能(特に販売可能なもの)の追加とセキュリティ/安定性/信頼性(これははるかに難しい販売です)の選択を考えると、ほとんどの場合、前者。セキュリティが問題になるのは、セキュリティだけです。

2
evil otto

アプリケーションをSQLインジェクションから保護するには、次の手順を実行します。

手順1.入力を制限します。手順2.ストアドプロシージャでパラメーターを使用します。ステップ3.動的SQLでパラメーターを使用します。

http://msdn.Microsoft.com/en-us/library/ff648339.aspx を参照してください

1
Fahad Hussain

準備済みステートメントがWebアプリケーションのコード全体で適切に使用されている場合でも、データベースコードコンポーネントが安全でない方法でユーザー入力からクエリを作成すると、SQLインジェクションの欠陥が存在する可能性があります。以下は、@ nameパラメータでのSQLインジェクションに対して脆弱なストアドプロシージャの例です。

CREATE PROCEDURE show_current_orders
(@name varchar(400) = NULL)
AS
DECLARE @sql nvarchar(4000)
SELECT @sql = ‘SELECT id_num, searchstring FROM searchorders WHERE ‘ +
‘searchstring = ‘’’ + @name + ‘’’’;
EXEC (@sql)
GO

アプリケーションがユーザーが指定した名前の値を安全な方法でストアドプロシージャに渡す場合でも、プロシージャ自体がこれを直接動的クエリに連結するため、脆弱です。

1
Hadid Graphics

SQLインジェクションは、コードインジェクションの大きな問題のサブセットであり、データとコードが同じチャネルを介して提供され、データがコードと間違えられます。パラメータ化されたクエリは、データとコードに関するコンテキストを使用してクエリを形成することにより、これが発生するのを防ぎます。

特定のケースでは、これでは十分ではありません。多くのDBMSで、ストアドプロシージャを使用してSQLを動的に実行することが可能で、DBMSレベルでSQLインジェクションの欠陥が発生します。パラメータ化されたクエリを使用してこのようなストアドプロシージャを呼び出しても、プロシージャ内のSQLインジェクションが悪用されることはありません。別の例は このブログ投稿 で見ることができます。

より一般的には、開発者は機能を誤って使用します。通常、コードは正しく実行されると次のようになります。

db.parameterize_query("select foo from bar where baz = '?'", user_input)

一部の開発者は、文字列を連結してからパラメーター化クエリを使用しますが、実際には、前述のデータ/コードを区別せずに、探しているセキュリティを保証します。

db.parameterize_query("select foo from bar where baz = '" + user_input + "'")

パラメータ化されたクエリを正しく使用すると、SQLインジェクション攻撃に対する非常に強力な、しかし不可解な保護が提供されます。

1
Daniel Crowley