htmlspecialcharsとmysql_real_escape_stringはPHPコードをインジェクションから保護しますか?


116

本日、Webアプリでの入力検証戦略について質問がありました。

トップの答えは、執筆時点で、中に示唆するPHPだけで使用するhtmlspecialcharsmysql_real_escape_string

私の質問は、これは常に十分ですか?知っておくべきことがもっとありますか?これらの機能はどこで壊れますか?

回答:


241

データベースクエリに関しては、常に準備済みのパラメーター化されたクエリを使用してください。ライブラリはこれをサポートしています。これは、などのエスケープ関数を使用するよりもはるかに安全です。mysqliPDOmysql_real_escape_string

はい、mysql_real_escape_string事実上、文字列エスケープ関数です。それは魔法の弾丸ではありません。危険な文字を1つのクエリ文字列で安全に使用できるようにエスケープするだけです。ただし、事前に入力をサニタイズしないと、特定の攻撃ベクトルに対して脆弱になります。

次のSQLを想像してください。

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

これはエクスプロイトに対して脆弱であることを確認できるはずです。パラメータに一般的な攻撃ベクトルが含まれていると
想像してidください。

1 OR 1=1

エンコードする危険な文字はないため、エスケープフィルターを直接通過します。私たちを残して:

SELECT fields FROM table WHERE id= 1 OR 1=1

これは素敵なSQLインジェクションベクトルであり、攻撃者がすべての行を返すことを可能にします。または

1 or is_admin=1 order by id limit 1

生成する

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

これにより、攻撃者はこの完全に架空の例で最初の管理者の詳細を返すことができます。

これらの機能は便利ですが、注意して使用する必要があります。すべてのWeb入力がある程度検証されていることを確認する必要があります。この場合、数値として使用している変数が実際に数値であることを確認しなかったため、悪用される可能性があることがわかります。PHPでは、一連の関数を使用して、入力が整数、浮動小数点、英数字などであることを確認する必要があります。しかし、SQLに関しては、準備されたステートメントの値にほとんど注意してください。上記のコードは、データベース関数が1 OR 1=1有効なリテラルではないことがわかっているため、準備されたステートメントであれば安全でした。

についてはhtmlspecialchars()。それは独自の地雷原です。

PHPには、さまざまなHTML関連のエスケープ関数がすべて選択されており、どの関数が何を実行するかについての明確なガイダンスがないという点で、PHPには実際の問題があります。

まず、HTMLタグの内部にいると、本当に困ります。見る

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

私たちはすでにHTMLタグの中にいるので、危険なことをするのに<や>は必要ありません。私たちの攻撃ベクトルはjavascript:alert(document.cookie)

結果のHTMLは次のようになります

<img src= "javascript:alert(document.cookie)" />

攻撃はまっすぐに進みます。

悪くなる。どうして?htmlspecialchars(この方法で呼び出された場合)は、単一引用符ではなく二重引用符のみをエンコードするためです。したがって、もし

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

悪意のある攻撃者はまったく新しいパラメーターを注入できるようになりました

pic.png' onclick='location.href=xxx' onmouseover='...

くれます

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

これらの場合、特効薬はありません。入力を自分で意味付けする必要があります。不正な文字を除外しようとすると、必ず失敗します。ホワイトリストのアプローチを取り、優れたcharsのみを通過させます。XSSのチートシートを見て、ベクターがどのように多様化できるかについての例を確認してください。

使っても htmlspecialchars($string)HTMLタグの外でマルチバイト文字セットの攻撃ベクトルに対して脆弱です。

最も効果的な方法は、次のようにmb_convert_encodingとhtmlentitiesを組み合わせて使用​​することです。

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

これでも、UTFの処理方法が原因でIE6は脆弱です。ただし、IE6の使用が減少するまで、ISO-8859-1などのより制限されたエンコーディングにフォールバックすることができます。

マルチバイト問題の詳細な調査については、https://stackoverflow.com/a/12118602/1820を参照してください。


24
ここで見逃した唯一のことは、DBクエリの最初の例...単純なintval()が注入を解決することです。文字列ではなく数値が必要な場合は、常にmysqlescape ...()の代わりにintval()を使用してください。
ロバートK

11
また、パラメーター化されたクエリを使用すると、データをコードではなくデータとして常に処理できるようになります。PDOなどのライブラリを使用し、可能な限りパラメーター化されたクエリを使用します。
Cheekysoft 2009

9
2つの注釈:1.最初の例では$result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";、2のようにパラメーターを引用符で囲んでも問題ありません。2番目の例(URLを含む属性)では、何も使用しませんhtmlspecialchars。このような場合は、URLエンコードスキームを使用して、たとえばを使用して入力をエンコードする必要がありますrawurlencode。そうすれば、ユーザーはjavascript:他を挿入できません。
Marcel Korpel 2011年

7
「htmlspecialcharsは単一引用符ではなく二重引用符のみをエンコードします」:これは真実ではなく、設定されているフラグに依存します。そのパラメーターを参照してください
Marcel Korpel、2011

2
これは太字にする必要がありますTake a whitelist approach and only let through the chars which are good.。ブラックリストは常に何かを見逃します。+1
Jo Smo 2014

10

Cheekysoftの優れた答えに加えて:

  • はい、それらはあなたを安全に保ちますが、それらが完全に正しく使用されている場合に限ります。それらを誤って使用すると、まだ脆弱であり、他の問題(たとえば、データの破損)を抱えている可能性があります
  • 代わりに、パラメータ化されたクエリを使用してください(上記のとおり)。PDOやPEAR DBなどのラッパーを介して使用できます。
  • magic_quotes_gpcとmagic_quotes_runtimeが常にオフになっていることを確認してください。短時間であっても、誤ってオンにしないでください。これは、PHPの開発者がセキュリティ上の問題(データを破壊する)を防止するための、初期の非常に誤った試みです。

HTMLインジェクション(クロスサイトスクリプティングなど)を防ぐための特効薬は実際にはありませんが、ライブラリまたはテンプレートシステムを使用してHTMLを出力している場合は、より簡単に実現できる可能性があります。物事を適切にエスケープする方法については、そのドキュメントを読んでください。

HTMLでは、状況に応じて異なる方法でエスケープする必要があります。これは、Javascriptに配置される文字列に特に当てはまります。


3

私は上記の投稿に間違いなく同意しますが、Cheekysoftの回答に返信するために1つ小さなことを追加します。

データベースクエリに関しては、常に準備済みのパラメーター化されたクエリを使用してください。mysqliおよびPDOライブラリはこれをサポートしています。これは、mysql_real_escape_stringなどのエスケープ関数を使用するよりもはるかに安全です。

はい、mysql_real_escape_stringは事実上単なる文字列エスケープ関数です。それは魔法の弾丸ではありません。危険な文字を1つのクエリ文字列で安全に使用できるようにエスケープするだけです。ただし、事前に入力をサニタイズしないと、特定の攻撃ベクトルに対して脆弱になります。

次のSQLを想像してください。

$ result = "テーブルからフィールドを選択WHERE id =" .mysql_real_escape_string($ _ POST ['id']);

これはエクスプロイトに対して脆弱であることを確認できるはずです。idパラメータに一般的な攻撃ベクトルが含まれていると想像してください。

1 OR 1 = 1

エンコードする危険な文字はないため、エスケープフィルターを直接通過します。私たちを残して:

テーブルWHERE id = 1 OR 1 = 1からフィールドを選択

数値ではないものをすべて取り除くデータベースクラスに配置する簡単な関数をコード化しました。それはpreg_replaceを使用するので、もう少し最適化された関数がありますが、ピンチで動作します...

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

だから代わりに

$ result = "テーブルからフィールドを選択WHERE id =" .mysqlrealescapestring( "1 OR 1 = 1");

私は使うだろう

$ result = "テーブルからフィールドを選択WHERE id =" .Numbers( "1 OR 1 = 1");

安全にクエリを実行します

テーブルWHERE id = 111からフィールドを選択

確かに、正しい行が表示されなくなっただけですが、SQLをサイトに挿入しようとしている人にとっては、これは大きな問題ではないと思います;)


1
パーフェクト!これはまさにあなたが必要とする種類の消毒です。数値が数値であることを検証しなかったため、最初のコードは失敗しました。あなたのコードはこれを行います。コードベースの外部から値が発生するすべての整数使用変数でNumbers()を呼び出す必要があります。
Cheekysoft 2008

1
PHPが整数を文字列に自動的に強制変換するので、intval()がこれに対して完全に正常に動作することは言及する価値があります。
Adam Ernst、

11
私はintvalを好みます。1abc2は12ではなく1になります。
jmucchiello

1
intvalは、特にIDが優れています。ほとんどの場合、破損している場合は、上記と同じように、1または1 = 1です。他人のIDを漏らしてはいけません。したがって、intvalは正しいIDを返します。その後、元の値とクリーンアップされた値が同じかどうかを確認する必要があります。攻撃を阻止するだけでなく、攻撃者を見つけるための素晴らしい方法です。
triunenature

2
個人データを表示している場合、誤った行は悲惨なものとなり、別のユーザーの情報が表示されます。代わりに、チェックすることreturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
フランクフォルテ

2

このパズルの重要な部分はコンテキストです。クエリですべての引数を引用符で囲んだ場合、IDとして「1 OR 1 = 1」を送信する人は問題になりません。

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

その結果:

SELECT fields FROM table WHERE id='1 OR 1=1'

これは効果がありません。文字列をエスケープしているので、入力は文字列コンテキストから抜け出すことができません。MySQLのバージョン5.0.45までこれをテストしましたが、整数列に文字列コンテキストを使用しても問題は発生しません。


15
次に、マルチバイト文字0xbf27で攻撃ベクトルを開始します。これは、latin1データベースでは、フィルター機能によって0xbf5c27として変換されます。これは、単一のマルチバイト文字とそれに続く単一引用符です。
Cheekysoft 2008

8
単一の既知の攻撃ベクトルから保護しないようにしてください。パッチをコードに適用する時間がなくなるまで、尾を追跡することになります。一般的なケースに立ち戻って見れば、より安全なコードとより良いセキュリティ中心の考え方に傾くでしょう。
Cheekysoft 2008

同意する; 理想的には、OPは準備されたステートメントを使用します。
ルーカスオマーン

1
この投稿で提案されている引数の引用は間違いのないものではありませんが、一般的な1 OR 1 = 1タイプの攻撃の多くを軽減するため、言及する価値があります。
Night Owl

2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

64ビットシステムではさらにうまく機能します。ただし、多数のアドレスを処理する場合のシステムの制限に注意してください。ただし、データベースIDの場合、これは99%の確率で機能します。

値のクリーンアップにも単一の関数/メソッドを使用する必要があります。この関数がmysql_real_escape_string()の単なるラッパーであっても。どうして?ある日、データの最適なクリーニング方法へのエクスプロイトが見つかった場合、システム全体の検索と置換ではなく、1か所で更新するだけで済みます。


-3

なぜ、ああ、なぜ、あなたはしませんか、あなたのSQL文では、ユーザー入力の前後に引用符が含まれていますか?かなりばかげていないようです!SQLステートメントに引用符を含めると、「1または1 = 1」は無駄な試みになります。

それでは、「ユーザーが入力に引用符(または二重引用符)を含めた場合はどうなるでしょうか」と言います。

まあ、そのための簡単な修正:ユーザー入力の引用符を削除するだけです。例:input =~ s/'//g;。今、とにかく、ユーザー入力は保護されるようです...


「なぜ、なぜ、SQLステートメントにユーザー入力を引用符で囲まないのですか?」—この質問は、ユーザー入力を引用しないことについては何も述べていません。
クエンティン

1
「まあ、そのための簡単な修正」—そのためのひどい修正。それはデータを捨てます。質問自体で言及されている解決策は、より優れたアプローチです。
クエンティン

質問がユーザー入力の引用を扱っていないことに同意しますが、それでも入力を引用しない方がよいようです。そして、私は悪いデータを入力するよりもむしろデータを投げたいです。一般的に、インジェクション攻撃では、とにかくそのデータを必要としません。
Jarett L 2017

「質問がユーザー入力の引用を扱っていないことに同意するが、それでも入力を引用しないように思われる。」—いいえ、ありません。質問はそれを何らかの方法で実証するものではありません。
クエンティン

1
@JarettL準備済みステートメントの使用に慣れるか、毎週火曜日にデータを破壊するBobby Tablesに慣れます。パラメータ化されたSQLは、SQLインジェクションから身を守るための最良の方法です。準備済みステートメントを使用している場合は、「SQLインジェクションチェック」を実行する必要はありません。それらは非常に簡単に実装でき(そして私の意見では、コードをはるかに読みやすくする)、文字列連結やSQLインジェクションのさまざまな特異性から保護します。何よりも、実装するためにホイールを再発明する必要はありません。 。
Siyual
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.