2780

ユーザーの入力が修正されずにSQLクエリに挿入されると、アプリケーションは脆弱になりますSQLインジェクション次の例のように

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

それはユーザーが次のようなものを入力できるからですvalue'); DROP TABLE table;--、クエリは次のようになります。

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

これが起こらないようにするには何ができますか?

28 답변


8220

準備された文とパラメータ化されたクエリを使用します。これらは、パラメータとは別にデータベースサーバに送信され、解析されるSQL文です。この方法では、攻撃者が悪質なSQLを挿入することは不可能です。

基本的にこれを達成するには2つの選択肢があります:

  1. 使用PDO(サポートされているデータベースドライバの場合):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. 使用MySQLi(MySQLの場合):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // do something with $row
    }
    

MySQL以外のデータベースに接続している場合は、参照可能なドライバ固有の第2のオプションがあります(例:pg_prepare()そしてpg_execute()PostgreSQLの場合)。 PDOは普遍的な選択肢です。

接続を正しく設定する

なお、PDOMySQLデータベースにアクセスするリアル準備されたステートメントはデフォルトでは使用されません。これを修正するには、プリペアドステートメントのエミュレーションを無効にする必要があります。 PDOを使用して接続を作成する例は次のとおりです。

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

上記の例では、エラーモードは厳密には必要ではありませんが、それを追加することをお勧めします。この方法では、スクリプトはFatal Error何かがうまくいかないときまた、開発者にcatchすべてのエラーthrownとしてPDOExceptions。

何ですか必須しかし、最初のsetAttribute()PDOに、エミュレートされたプリペアドステートメントを無効にして使用するよう指示しますリアル準備されたステートメント。これにより、MySQLサーバに送信する前にPHPが文と値を解析しないようにします(攻撃者に悪意のあるSQLを挿入する機会は与えません)。

あなたはcharsetコンストラクタのオプションでは、PHPの古いバージョン(<5.3.6)が使用されていることに注意することが重要ですcharsetパラメータを暗黙に無視するDSNに

説明

何が起こるかは、渡すSQL文prepareデータベースサーバーによって解析され、コンパイルされます。パラメータを指定することにより(a?のような名前付きパラメータ:name上記の例では、フィルターを適用するデータベースエンジンに指示します。あなたが電話するときexecute準備されたステートメントは、指定したパラメーター値と組み合わされます。

ここで重要なのは、パラメータ値がSQL文字列ではなく、コンパイルされた文と結合されていることです。 SQLインジェクションは、データベースに送信するSQLを作成するときに、悪意のある文字列を含むようにスクリプトをトリックすることによって機能します。したがって、実際のSQLをパラメータとは別に送信することで、意図しない何かで終わるリスクが制限されます。プリペアドステートメントを使用するときに送信するパラメータはすべて文字列として扱われます(ただしデータベースエンジンではいくつかの最適化が行われるため、パラメータも数字になります)。上記の例では、$name変数が含まれます'Sarah'; DELETE FROM employees結果は単純に文字列の検索となります"'Sarah'; DELETE FROM employees"、あなたは終わらないでしょう空のテーブル

プリペアドステートメントを使用するもう1つの利点は、同じセッションで同じステートメントを何度も実行すると、一度解析されてコンパイルされるだけで、速度が向上します。

ああ、あなたが挿入のためにそれを行う方法について尋ねたので、ここでは(PDOを使って)例を挙げます:

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute(array('column' => $unsafeValue));

動的なクエリにプリペアドステートメントを使用できますか?

クエリパラメータにプリペアドステートメントを使用することはできますが、動的クエリ自体の構造はパラメータ化できず、特定のクエリ機能をパラメータ化できません。

これらの特定のシナリオでは、可能な値を制限するホワイトリストフィルタを使用することをお勧めします。

// Value whitelist
// $dir can only be 'DESC' otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}


  • ここに他の場所では見られなかったので追加するだけで、もう一つの防衛線はWebアプリケーションファイアウォール(WAF)は、SQLインジェクション攻撃を探すようにルールを設定できます。 - jkerak
  • また、mysql_queryの公式ドキュメントでは1つのクエリしか実行できないので、他のクエリも実行できます。無視されます。これがすでに廃止されているとしても、PHP 5.5.0には多くのシステムがあり、この機能を使用する可能性があります。php.net/manual/en/function.mysql-query.php - Randall Valenciano
  • これは悪い習慣ですが、問題解決後の解決策です:SQLインジェクションだけでなく、あらゆるタイプのインジェクション(例えば、F3フレームワークv2にビューテンプレート注入口があった場合)に備えています。注入欠陥から、1つの解決策は、ブートストラップでエスケープされた値で$ _POSTのような、あなたの予備グローバルに定義された変数の値を再割り当てすることです。 PDOでは、引き続き(今日のフレームワークの場合でも)substr($ pdo-&gt; quote($ str、\ PDO :: PARAM_STR)、1、-1)をエスケープすることができます。 - Alix
  • この回答には、準備された声明の説明が欠けています.1つは、リクエスト中に多くの準備文を使用するとパフォーマンスが低下し、10倍のパフォーマンスが得られることがあります。より良い場合は、パラメータバインディングをオフにして、使用するPDOを使用しますが、文の準備はオフにします。 - donis
  • 直接クエリを使用している場合は、PDOを使用する方が良いです。mysqli :: escape_string - Kassem Itani

1558

警告:この回答のサンプルコード(質問のサンプルコードのような)は、PHPのMySQLPHP 5.5.0では廃止され、PHP 7.0.0では完全に削除されました。

最新バージョンのPHPを使用している場合は、mysql_real_escape_string下記のオプションは使用できなくなります(mysqli::escape_string現代的なものです)。最近では、mysql_real_escape_stringPHPの旧バージョンではレガシーコードにのみ意味があります。


あなたは2つのオプションがあります - あなたの特殊文字をエスケープするunsafe_variable、またはパラメータ化されたクエリを使用します。どちらもSQLインジェクションからあなたを守ります。パラメータ化されたクエリはより良い方法であると考えられますが、使用する前にPHPの新しいMySQL拡張に変更する必要があります。

最初に最初にエスケープする文字列を取り上げます。

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

また、mysql_real_escape_string関数。

パラメータ化されたクエリを使用するには、MySQLiその代わりにMySQL関数。あなたの例を書き直すには、次のようなものが必要です。

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

読んでみたいキー機能はmysqli::prepare

また、他の人が示唆しているように、抽象化のレイヤーを上手にPDO

質問したケースはかなり単純なものであり、複雑なケースではもっと複雑なアプローチが必要な場合があります。特に:

  • ユーザーの入力に基づいてSQLの構造を変更する場合は、パラメータ化されたクエリは役立たず、必要なエスケープ処理はmysql_real_escape_string。このような場合、「安全な」値だけが許可されるように、ユーザーの入力をホワイトリストに通す方がよいでしょう。
  • ある条件でユーザー入力からの整数を使用し、mysql_real_escape_stringアプローチでは、次のような問題が発生します。多項式以下のコメントで。整数は引用符で囲まれないので、この場合は扱いにくいので、ユーザ入力に数字だけが含まれていることを検証することで対処できます。
  • 私が気づいていない他のケースがあります。あなたが見つけるかもしれないこのあなたが遭遇する可能性があるより微妙な問題のいくつかに役立つリソースです。


  • を使用してmysql_real_escape_stringまたは十分に私はパラメータ化された使用する必要がありますか? - peiman F.
  • @peimanF。ローカルプロジェクトでも、パラメータ化されたクエリを使用することをお勧めします。パラメータ化されたクエリを使用すると、保証付きSQLインジェクションはありません。ただし、偽の検索(テキストにHTMLコードを挿入するなどのXSS注入)を避けるためには、データをサニタイズする必要がありますhtmlentities例えば - Goufalite
  • @peimanF。パラメータ化されたクエリとバインド値には適していますが、実際のエスケープ文字列は今のところ良いです - Richard
  • 私は、mysql_real_escape_string()完全性のために、最も誤りの多いアプローチを最初に列挙するファンではありません。読者はすぐに最初の例をつかむかもしれません。いいことは今は廃止されました:) - Time Sheep

980

すべての答えは、問題の一部のみをカバーしています。 実際、そこには動的に追加できるさまざまなクエリ部分: -

  • 文字列
  • 識別子
  • 構文キーワード。

そして準備された声明はそれらの2つだけをカバーします。

しかし、時にはクエリをさらに動的にし、演算子や識別子を追加する必要があります。 したがって、我々は異なる保護技術が必要になるでしょう。

一般に、そのような保護アプローチは、ホワイトリスト

この場合、すべての動的パラメータをスクリプトでハードコードし、そのセットから選択する必要があります。 たとえば、動的順序付けを行うには:

$orders  = array("name", "price", "qty"); // Field names
$key     = array_search($_GET['sort'], $orders)); // See if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. smart enuf :)
$query   = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

しかし、識別子を保護するもう1つの方法、つまりエスケープがあります。引用符で囲まれた識別子がある限り、それらを倍にすることで内部のバッククエストをエスケープすることができます。

さらなるステップとして、用意されたステートメントからいくつかのプレースホルダ(クエリの実際の値を表すプロキシ)を使用し、識別子プレースホルダという別のタイプのプレースホルダを作成するという、本当に素晴らしい考えを借りることができます。

だから、長い話を短くするには:それはプレースホルダ、ない準備声明銀の弾丸と見なすことができます。

だから、一般的な勧告はプレースホルダを使用してクエリに動的パーツを追加している限り(もちろんこれらのプレースホルダも正しく処理されます)、クエリが安全であることを確認できます

それでも、SQL構文キーワードに問題があります(ANDDESCこのような場合には、ホワイトリストが唯一のアプローチと思われます。

更新

SQLインジェクション保護に関するベストプラクティスには一般的な合意がありますが、まだ多くの悪い習慣があります。また、PHPユーザーの心に深く根ざしたものもあります。たとえば、このページには(ほとんどの訪問者には見えませんが)80を超える削除された回答 - 品質が悪くなったり、悪い時代遅れの習慣を促進したりするため、コミュニティーから取り除かれました。さらに悪いことに、悪い回答のいくつかは削除されず、むしろ繁栄しています。

例えば、そこに(1) (2) まだ(3) 多く(4) 答え(5)、 含んでいる2番目に大きなアップ手動の文字列エスケープを提案します。これは、安全でないことが判明している時代遅れのアプローチです。

またはちょっとだけ良い答えがあります文字列書式の別の方法究極の万能薬としてもそれを誇っています。もちろん、そうではありません。このメソッドは、通常の文字列フォーマットよりも優れていますが、文字列にのみ適用でき、その他の手動フォーマットと同様に、必須ではありませんが、必須ではありません。

私はこのようなことは、非常に古い迷信のために、OWASPまたはPHPマニュアル「エスケープ」とSQLインジェクションからの保護との間の平等を宣言します。

PHPマニュアルが何世代にもわたって言ったことにかかわらず、*_escape_stringデータを安全にすることは決してありません決して意図されたことはありません。文字列以外のSQL部分では無用ですが、手動エスケープは間違っています。

そしてOWASPはそれをさらに悪化させ、エスケープに重点を置いていますユーザー入力それは完全なナンセンスです:注射の保護の文脈の中にそのような言葉はありません。すべての変数は、ソースに関係なく潜在的に危険です!つまり、ソースに関係なく、すべての変数を適切にフォーマットしてクエリに入れる必要があります。それは重要な目的地です。開発者がヤギからヒツジを分離し始める瞬間(特定の変数が「安全」であるかどうかを考える)、彼は災害への第一歩を踏み出す。言葉でさえ、エントリーポイントで大量にエスケープし、非常に魔法のクォート機能に似ていることを示していることは言うまでもありません。

したがって、「エスケープする」ものとは異なり、準備されたステートメントSQLインジェクションから実際に保護する手段(該当する場合)。

あなたがまだ確信していないなら、ここに私が書いたステップバイステップの説明があります、ヒッチハイクのSQLインジェクション防止ガイドここで私はこれらの事柄をすべて詳細に説明し、悪い習慣とその開示に完全に専念したセクションを編集しました。


  • 偉大な、よく考えられた記事。私は、PHPのサニタイズフィルタを使用して、白いソートリストの種類(ただし正確ではない)を追加するかもしれません。例えば、FILTER_SANITIZE_NUMBER_INT数字の文字のみを許可するので、文字列全体ではなく、ホワイトリストの文字になります。準備されたステートメントと組み合わせて、それは良好な「ベルトおよびサスペンダー」を作り出す。アプローチ。 - Sablefoste
  • @Sablefosteあなたはここでホワイトリストを作成する必要はありません。すべてのサニタイズは冗長になります。従うべき規則が少なくなるほど、間違いが少なくなります。任意のバリデーションを行うことができますが、アプリケーションロジックのために行いますが、データベースに対しては行いません。 - Your Common Sense

787

私は使用をお勧めしたいPDO(PHP Data Objects)を使用して、パラメータ化されたSQLクエリを実行します。

これはSQLインジェクションから保護するだけでなく、クエリの処理速度も向上させます。

そして、PDOを使うのではなく、mysql_mysqli_、およびpgsql_まれにデータベースプロバイダーを切り替える必要があるため、アプリケーションをデータベースから少し抽象化します。


  • MySQL DBの場合、PDOでmysqliをラップしませんか?どちらの場合でも、mysqliよりも速くなることはありません。私はまだそれをお勧めします。これは、mysqli APIよりもはるかに優れたインターフェースです。 - Peter Bagnall
  • パラメータ化されたクエリを使用すると、クエリの処理速度が向上します。技術的には、mysqliは非常に小さなマージンですら速いかもしれません。クエリーに応答するためにサーバーが実際に費やす時間は、ラッパーを使用しているために発生する可能性のあるタイミングの違いを一掃します。しかし、mysqliはデータベースに結びついています。別のデータベースエンジンを使用する場合は、mysqliを使用するすべての呼び出しを変更する必要があります。 PDOではそうではありません。 - Kibbee
  • PDOはダイナミックをサポートしていませんorder by残念ながら:( - Horse

584

つかいますPDO準備されたクエリ。

$conn〜ですPDOオブジェクト)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();


  • からウィキペディア:後で別のプロトコルを使用して送信されるパラメータ値が正しくエスケープされる必要がないため、プリペアドステートメントはSQLインジェクションに対して弾力性があります。元のステートメントテンプレートが外部入力から派生していない場合、SQLインジェクションは実行できません。 - Imran

519

ご覧のとおり、人々はあなたが準備したステートメントを最大限に使うことを提案します。間違いではありませんが、クエリが実行されたとき一度だけプロセスごとにわずかなパフォーマンス上のペナルティがあります。

私はこの問題に直面していましたが、私はそれを非常に洗練された方法 - 引用符の使用を避けるためにハッカーが使用する方法。私はこれを、エミュレートされた準備文とともに使用しました。私はそれを防ぐために使うすべて可能なSQLインジェクション攻撃の種類。

私のアプローチ:

  • 入力が整数であることを期待するなら、それが本当に整数。 PHPのような可変型言語では、これは非常に重要。たとえば、この非常にシンプルで強力なソリューションを使用できます。sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • 整数から何かを期待するならばそれを16進数。 16進数の場合、すべての入力を完全にエスケープします。 C / C ++には、以下の関数があります。mysql_hex_string()、PHPで使用することができますbin2hex()

    エスケープされた文字列は元の長さの2倍のサイズになることを心配しないでください。mysql_real_escape_string、PHPは同じ容量を割り当てる必要があります((2*input_length)+1)これは同じです。

  • この16進法は、バイナリデータを転送するときによく使用されますが、SQLインジェクション攻撃を防ぐためにすべてのデータに使用しない理由はありません。データの前に0xまたはMySQL関数を使用するUNHEX代わりに。

したがって、たとえば、次のクエリは次のようになります。

SELECT password FROM users WHERE name = 'root'

となります:

SELECT password FROM users WHERE name = 0x726f6f74

または

SELECT password FROM users WHERE name = UNHEX('726f6f74')

六角は完璧なエスケープです。注射する方法はありません。

UNHEX関数と0x接頭辞の違い

コメントにいくつかの議論があったので、私はついにそれを明確にしたい。これらの2つのアプローチは非常に似ていますが、いくつかの点で少し異なります。

** 0x **接頭辞は、次のようなデータ列に対してのみ使用できます。char、varchar、テキスト、ブロック、バイナリなど

また、空の文字列を挿入しようとすると少し複雑になります。あなたはそれを完全に置き換える必要があります''、またはエラーが発生します。

UNHEX()に作用するどれかカラム;空の文字列について心配する必要はありません。


16進法は攻撃として使用されることが多い

この16進法は、SQLインジェクション攻撃としてよく使われることに注意してください。整数は文字列と似ていて、mysql_real_escape_string。次に、引用符の使用を避けることができます。

たとえば、次のようなことをすればよい:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

攻撃はあなたに非常に注入することができます簡単に。スクリプトから返された次の注入コードを考えてみましょう:

SELECT ... WHERE id =-1共用体はすべて、table__nameをinformation_schema.tablesから選択します。

今度はテーブル構造を抽出するだけです:

SELECT ... WHERE id = -1 unionすべてのselect column_name from information_schema.column where table_name =0x61727469636c65

そして、必要なデータを選択してください。それはクールではないですか?

しかし、注入可能なサイトのコーダが16進数であれば、クエリは次のようになるので注入はできません。SELECT ... WHERE id = UNHEX('2d312075...3635')


  • @すみません.Ya、あなたはしました。 MySQLは+しかし、CONCAT。そしてパフォーマンス:mysqlはデータを解析しなければならないので、パフォーマンスに影響するとは思えません。元が文字列か16進数かどうかは問題ではありません - Zaffy
  • @ YourCommonSenseどのようなエラーが発生しますか?具体的にする。 - Zaffy
  • @ YourCommonSenseあなたはコンセプトを理解していません...あなたがmysqlの文字列を持っていたい場合は、このように引用します'root'またはあなたはそれを16進数で0x726f6f74しかし、数字を入力して文字列として送信する場合は、おそらく&#42;&#39; CHAR(42)ではなく...&#42;&#39; 16進数で0x3432ない0x42 - Zaffy
  • @ YourCommonSense私は何も言わない...ちょうど笑...あなたはまだ数値フィールドで16進数を試したい場合は、2番目のコメントを参照してください。私はあなたにそれが働くことを賭ける。 - Zaffy
  • あなたはまだ理解していない@ YourCommonSense?文字列が空の場合、エラーで終了するため、0xとconcatは使用できません。あなたがあなたのクエリに単純な代替をしたい場合は、これを試してみてくださいSELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ') - Zaffy

469

重要

SQLインジェクションを防止する最善の方法は、準備されたステートメント エスケープする代わりに、として受け入れられた答え実証する。

図書館のようなAura.SqlそしてEasyDB開発者が準備されたステートメントを簡単に使用できるようにします。準備されたステートメントがより優れている理由の詳細については、SQLインジェクションを停止する、 参照するこのmysql_real_escape_string()バイパスそしてWordPressで最近修正されたUnicode SQLインジェクションの脆弱性

注射の防止 - mysql_real_escape_string()

PHPには、これらの攻撃を防ぐ特別な機能があります。あなたがする必要があるのは、関数の口一杯を使うことだけです。mysql_real_escape_string

mysql_real_escape_stringMySQLクエリで使用される文字列を受け取り、すべてのSQLインジェクションを安全にエスケープして同じ文字列を返します。基本的には、ユーザがMySQLで安全な代用品であるエスケープされた引用符で入力するという面倒な引用符( ')を置き換えます。

注意:この機能を使用するには、データベースに接続する必要があります。

// MySQLに接続する

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

より詳細な情報はMySQL - SQLインジェクション防止


  • これは、従来のmysql拡張でできる最善の方法です。新しいコードでは、mysqliやPDOに切り替えることをお勧めします。 - Álvaro González
  • これらの攻撃を防止するために特別に用意された機能であると私は同意していません。私はそう思うmysql_real_escape_string目的は、すべての入力データ文字列に対して正しいSQLクエリを構築できるようにすることです。予防SQLインジェクションは、この関数の副作用です。 - sectus
  • あなたは正しい入力データ文字列を書くために関数を使用しません。エスケープする必要がないか、既にエスケープされている正しいものを書いてください。 mysql_real_escape_string()は、あなたが念頭に置く目的で設計されているかもしれませんが、その唯一の価値は注入を妨げることです。 - Nazca
  • 警告! mysql_real_escape_string() 間違いではない。 - eggyal
  • mysql_real_escape_string現在は推奨されていないため、もはや実行可能なオプションではありません。将来PHPから削除されます。 PHPやMySQLの皆さんが推奨するものに移行するのがベストです。 - jww

425

セキュリティ警告:この回答は、セキュリティのベストプラクティスに沿ったものではありません。エスケープは、SQLインジェクションを防ぐのには不十分です、 つかいます準備文代わりに。自己責任で以下に示す戦略を使用してください。 (また、mysql_real_escape_string()PHP 7で削除されました)

次のような基本的なことができます:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

これはすべての問題を解決するわけではありませんが、それは非常に良い踏み台です。変数の存在、形式(数字、文字など)をチェックするなどの明白な項目は除外しました。


  • 私はあなたの事例を試してみましたが、私にとってはうまくいっています。「これはすべての問題を解決することはできません。 - Chinook
  • 文字列を引用符で囲まないと、それはまだ注射可能です。取る$q = "SELECT col FROM tbl WHERE x = $safe_var";例えば。設定$safe_var1 UNION SELECT password FROM usersこの場合、引用符がないために動作します。クエリを使用して文字列を挿入することも可能ですCONCATそしてCHR。 - Polynomial
  • @Polynomial完全に正しいですが、私はこれを誤った使い方とみなしています。あなたが正しく使用する限り、間違いなく動作します。 - glglgl
  • 警告! mysql_real_escape_string() 間違いではない。 - eggyal
  • mysql_real_escape_string現在は推奨されていないため、もはや実行可能なオプションではありません。将来PHPから削除されます。 PHPやMySQLの皆さんが推奨するものに移行するのがベストです。 - jww

360

あなたが何をしても、あなたの入力がまだ壊れていないことを確認してくださいmagic_quotesまたは他の何らかの意味のあるごみを取り除き、必要に応じてstripslashesまたはそれを衛生的にするために何でも。


  • 確かに; magic_quotesをオンにして実行するだけで、貧弱な練習が促進されます。ただし、そのレベルまで環境を常に制御することはできません。サーバーの管理にアクセスできない場合や、アプリケーションがそのような構成に依存するアプリケーションと共存する場合があります。このような理由から、ポータブルアプリケーションを作成することは有効です。デプロイメント環境を制御すると、明らかにその作業が無駄になります。社内のアプリケーションであるか、特定の環境でのみ使用されるためです。 - Rob
  • PHP 5.4以降では、「マジッククオート」と呼ばれる偽物は、されている殺された。そして、悪いごみにうんざりする。 - BryanH

343

パラメータ化されたクエリと入力の検証が行なわれています。 SQLインジェクションが発生するシナリオはたくさんありますが、mysql_real_escape_string()使用されています。

これらの例はSQLインジェクションに対して脆弱です。

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

または

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

どちらの場合も、あなたは使用できません'カプセル化を保護する。

ソース予期しないSQLインジェクション(エスケープ処理が不十分な場合)


  • 長さ、タイプ、構文、およびビジネスルールに対して定義された一連のルールに対してユーザー入力が認証される入力検証手法を採用すると、SQLインジェクションを防止できます。 - Josip Ivic

292

私の意見では、PHPアプリケーション(または他のWebアプリケーション)のSQLインジェクションを防止する最も良い方法は、アプリケーションのアーキテクチャについて考えることです。 SQLインジェクションから守る唯一の方法は、データベースと対話するたびに正しいことを行う特別なメソッドや関数を使用することを忘れないようにすることです。そうすれば、コード内のある時点でクエリを正しくフォーマットするのを忘れるまでは時間の問題です。

MVCパターンとフレームワークを採用CakePHPまたはCodeIgniterのおそらく適切な方法です。セキュアデータベースクエリの作成などの一般的なタスクは、このようなフレームワークで解決され、一元的に実装されています。これらは、Webアプリケーションを賢明に整理し、単一のSQLクエリーを安全に構築するよりも、オブジェクトの読み込みと保存についてより多くのことを考えるのに役立ちます。


  • 私はあなたの最初の段落が重要だと思います。理解は重要です。また、誰もが会社のために働いていない。大規模な人々のために、フレームワークは実際には理解。締め切りの下で働いている間、ファンダメンタルズに親しみを感じることは価値がないかもしれませんが、そこにいるdo-it-yourselfersは手を汚して楽しんでいます。フレームワークの開発者は他の誰もが過ちを犯すことはなく、決して間違いがないと仮定しなければならないほど特権がありません。決定を下す権限は依然として重要です。将来私の枠組みが他の計画に代わるものとは誰でしょうか? - Anthony Rutledge
  • @AnthonyRutledgeあなたは絶対に正しいです。それは非常に重要ですわかる何が起こっているのか。しかし、真実に試され、積極的に使用され、開発されたフレームワークが多くの問題に取り組み、解決し、すでに多くのセキュリティホールを埋め込んでいる可能性はかなり高いです。ソースを見てコード品質を感じることをおすすめします。テストされていない混乱があれば、おそらく安全ではありません。 - Johannes Fahrenkrug
  • ここに。ここに。良い点。しかし、多くの人がMVCシステムを学び、学ぶことができますが、誰もが手でそれを再現できるわけではないことに同意しますか(コントローラとサーバ)。 1つはこの点では遠すぎることができます。私はピーナッツバターを加熱する前に私の電子レンジを理解する必要がありますペカンクッキー私の女の子の友人は私を作った? ;-) - Anthony Rutledge
  • @AnthonyRutledge私は同意する!私はユースケースも違いがあると思う:個人的なホームページのためのフォトギャラリーを構築しているのか、オンラインバンキングWebアプリケーションを構築しているのだろうか?後者の場合、セキュリティの詳細を理解すること、および私が使用しているフレームワークがそれらの問題に対処する方法を理解することは非常に重要です。 - Johannes Fahrenkrug
  • ああ、セキュリティ上の例外は、あなた自身が結実している。参照してください、私はそれをすべて危険にさらして喜んで壊れてしまう傾向があります。 :-)キッド。十分な時間があれば、人々は安全なアプリケーションを作ることができます。あまりにも多くの人々が急いでいる。彼らは彼らの手を投げ、フレームワークがより安全な。結局のところ、彼らはテストして物事を把握するのに十分な時間がありません。また、セキュリティは専任の研究が必要な分野です。これは、アルゴリズムと設計パターンを理解していることによって、プログラマーが深く知っているだけではありません。 - Anthony Rutledge

277

私は好きですストアドプロシージャMySQLには5.0以降のプロシージャサポートが保存されています)のセキュリティの観点から - 利点は -

  1. ほとんどのデータベース(MySQL)は、ユーザー・アクセスをストアード・プロシージャーの実行に制限することができます。きめ細かなセキュリティアクセス制御は、特権攻撃のエスカレーションを防ぐのに役立ちます。これにより、侵害されたアプリケーションがデータベースに対してSQLを直接実行できなくなります。
  2. それらは、アプリケーションから未処理のSQLクエリを抽象化するので、データベース構造の情報はアプリケーションで利用できません。これにより、人々はデータベースの基礎構造を理解し、適切な攻撃を設計することが難しくなります。
  3. パラメータだけを受け入れるので、パラメータ化されたクエリの利点があります。もちろん、IMOの場合は、依然として入力をサニタイズする必要があります。特に、ストアドプロシージャ内で動的SQLを使用している場合は特にそうです。

欠点は -

  1. それら(ストアドプロシージャ)は維持するのが難しく、非常に迅速に乗算する傾向があります。これにより、これらの問題を管理できます。
  2. 動的クエリにはあまり適していません。動的コードをパラメータとして受け入れるように構築されていれば、多くの利点が否定されます。


273

SQLインジェクションや他のSQLハックを防ぐ方法はたくさんあります。インターネット上で簡単に見つけることができます(Google検索)。もちろんPDOは良いソリューションの1つです。しかし、私はあなたにSQLインジェクションからの良いリンク防止を提案したいと思います。

SQLインジェクションとは何ですか?

SQLインジェクションのPHPマニュアル

PHPのSQLインジェクションと予防に関するMicrosoftの説明

その他いくつかのMySQLとPHPによるSQLインジェクションの防止

今、なぜあなたはSQLインジェクションからあなたのクエリを防ぐ必要がありますか?

私は以下のような短い例でSQLインジェクションを防止しようとしているのはなぜですか?

ログイン認証の照合:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

今、もし誰か(ハッカー)が

$_POST['email']= admin@emali.com' OR '1=1

パスワード何か....

クエリはシステム内でのみ解析されます。

$query="select * from users where email='admin@emali.com' OR '1=1';

他の部分は破棄されます。だから、どうなるの?承認されていないユーザー(ハッカー)は、パスワードを使わずに管理者としてログインできます。今、彼は管理者/電子メールの人ができることを何でもすることができます。 SQLインジェクションが防止されないと、非常に危険です。


250

私は誰かがPHPとMySQLまたはいくつかの他のデータベースサーバーを使用したいと思う:

  1. 学習を考えようPDO(PHPデータオブジェクト) - 複数のデータベースへの一様なアクセス方法を提供するデータベースアクセスレイヤーです。
  2. 学習を考えようMySQLi
  3. 次のようなネイティブPHP関数を使用します。strip_tagsmysql_real_escape_string変数数値の場合は(int)$foo。 PHPでの変数の型の詳細ここに。 PDOやMySQLiなどのライブラリを使用している場合は、常にPDO :: quote()そしてmysqli_real_escape_string()

ライブラリの例:

----PDO

-----プレースホルダなし - SQLインジェクションに熟しています!これは悪いです

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

-----無名のプレースホルダ

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

-----名前付きプレースホルダ

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

---MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

P.S

PDOはこの戦いに勝ちます。 12人のサポート 異なるデータベースドライバと名前付きパラメータを使用すると、 パフォーマンスの低下が少なく、APIに慣れています。セキュリティから 開発者がそれらを使用している限り、どちらも安全です 彼らが使用されるはずのやり方

しかし、PDOとMySQLiの両方がかなり高速ですが、MySQLiは ベンチマークではるかに高速 - 非準備の場合は〜2.5% 、準備したものは〜6.5%です。

また、すべてのクエリをデータベースにテストしてください。注射を防ぐより良い方法です。


240

可能であれば、パラメータの型をキャストします。しかし、int、bool、floatなどの単純な型でしか動作しません。

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");


  • これは、「エスケープされた値」を使用する少数のケースの1つです。準備されたステートメントの代わりに。整数型変換は非常に効率的です。 - HoldOffHunger

221

あなたがキャッシュエンジンを利用したい場合、RedisのまたはMemcachedおそらくDALMPが選択肢になる可能性があります。それは純粋なMySQLi。これをチェックして:PHPを使用したMySQL用DALMPデータベース抽象化レイヤ。

また、クエリを準備する前に引数を準備して、動的クエリを構築し、最終的には完全に準備されたクエリクエリを作成することができます。PHPを使用したMySQL用DALMPデータベース抽象化レイヤ。


213

PDOの使用方法が不明な方(mysql_関数)、私は非常に簡単なPDOラッパーそれは単一のファイルです。これは、アプリケーションが行う必要があるすべての一般的なことをいかに簡単に行うことができるかを示すために存在します。 PostgreSQL、MySQL、およびSQLiteで動作します。

基本的には、それを読むあなたがマニュアルを読んでいる間現実に使用するPDO関数をどのように配置して、フォーマットで値を格納して取り出すのが簡単かを確認する君は欲しいです。

私は単一の列が欲しい

$count = DB::column('SELECT COUNT(*) FROM `user`);

私は配列(キー=>値)の結果が必要です(つまり選択ボックスを作るため)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

私は単一の行の結果が欲しい

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

私は結果の配列が欲しい

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));


211

このPHP関数を使うmysql_escape_string()あなたはすぐに良い予防を得ることができます。

例えば:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - mysql_queryで使用する文字列をエスケープする

より多くの予防のために、最後に追加することができます...

wHERE 1=1   or  LIMIT 1

最後にあなたは:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1


193

SQL文中の特殊文字をエスケープするためのガイドライン。

使用しないでくださいMySQLこの拡張機能は廃止予定です。MySQLiまたはPDO

MySQLi

文字列内の特殊文字を手動でエスケープするには、mysqli_real_escape_string関数。正しい文字セットが設定されていないと、この関数は正しく動作しませんmysqli_set_charset

例:

$mysqli = new mysqli( 'host', 'user', 'password', 'database' );
$mysqli->set_charset( 'charset');

$string = $mysqli->real_escape_string( $string );
$mysqli->query( "INSERT INTO table (column) VALUES ('$string')" );

プリペアドステートメントで値を自動的にエスケープするには、mysqli_prepare、およびmysqli_stmt_bind_param適切な変換のために、対応するバインド変数の型を指定する必要があります。

例:

$stmt = $mysqli->prepare( "INSERT INTO table ( column1, column2 ) VALUES (?,?)" );

$stmt->bind_param( "is", $integer, $string );

$stmt->execute();

プリペアドステートメントやmysqli_real_escape_stringを使用する場合でも、作業する入力データのタイプを常に把握しておく必要があります。

したがって、プリペアドステートメントを使用する場合は、mysqli_stmt_bind_param関数の変数の型を指定する必要があります。

また、mysqli_real_escape_stringの使用は、名前が示すように、文字列内の特殊文字をエスケープするため、整数を安全にしません。この関数の目的は、SQL文内の文字列の破損や、データベースに与える可能性のある破損を防ぐことです。 mysqli_real_escape_stringは、特にsprintfと組み合わされた場合に、適切に使用されると便利な機能です。

例:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647


  • 問題は非常に一般的です。上のいくつかの大きな答えが、ほとんどの準備ができてステートメントを示唆している。 MySQLi asyncはプリペアドステートメントをサポートしていないので、sprintfはこのような状況のための素晴らしいオプションのようです。 - Dustin Graham

173

この問題の簡単な代替方法は、データベース自体に適切な権限を与えることで解決できます。 たとえば、MySQLデータベースを使用している場合は、端末または提供されたUIを使用してデータベースに入力し、次のコマンドを実行します。

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

これにより、ユーザーは指定したクエリのみに限定されるように制限されます。削除権限を削除すると、PHPページから実行されたクエリからデータが削除されることはありません。 もう1つは、MySQLが権限と更新をリフレッシュするように権限をフラッシュすることです。

FLUSH PRIVILEGES; 

さらに詳しい情報流す

ユーザーの現在の権限を確認するには、次のクエリを実行します。

select * from mysql.user where User='username';

詳しくはこちら付与


  • この回答は基本的に間違っそれは注入予防を防ぐのではなく、その結果を弱めることに役立ちます。無駄に。 - Your Common Sense
  • そうではありませんが、ソリューションを提供していませんが、回避するために事前に行うことができます。 - Apurv Nerlekar
  • @Apurv私の目標があなたのデータベースから私的情報を読むことであるならば、DELETEパーミッションを持たないことは何も意味しません。 - Alex Holsgrove
  • @AlexHolsgrove:それを楽にしてください。結果を和らげるための良い方法を提案していただけです。 - Apurv Nerlekar
  • @Apurvあなたは「結果を和らげる」ことを望んでいませんが、あなたはそれを守るためにすべてのことをやりたいと思っています。しかし、公正であるためには、正しいユーザーアクセスを設定することは重要ですが、実際にOPが求めていることではありません。 - Alex Holsgrove

167

私は、WebアプリケーションがSQLインジェクションに対して脆弱にならないように、3つの異なる方法を使用します。

  1. の使用mysql_real_escape_string()これは、PHPこのコードでは、バックスラッシュを次の文字に追加します。\x00\n\r\'"そして\x1a。 SQLインジェクションの可能性を最小限に抑えるために、入力値をパラメータとして渡します。
  2. 最も高度な方法は、PDOを使用することです。

これがあなたに役立つことを願っています。

以下のクエリを考えてみましょう。

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string()はここで保護しません。クエリ内で変数の前後に一重引用符( '')を使用すると、これを防ぐことができます。これは以下の解決策です:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

この質問これについていくつかの良い答えがあります。

私は、PDOを使用することが最良の選択肢であることを示唆している。

編集:

mysql_real_escape_string()PHP 5.5.0以降では推奨されていません。 mysqliまたはPDOを使用します。

mysql_real_escape_string()の代わりに

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

例:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");


162

簡単な方法は、以下のようなPHPフレームワークを使用することです。CodeIgniterのまたはララベルフィルタリングやアクティブレコードなどの機能が組み込まれているため、これらのニュアンスについて心配する必要はありません。


  • 問題の全体のポイントは、このようなフレームワークを使用せずにこれを行うことです。 - Sanke

162

便利な回答については、このスレッドにいくつかの値を追加したいと考えています。 SQLインジェクション・パターンは、ユーザー入力(ユーザーが入力してクエリーの内部で使用する入力)によって実行できる攻撃です。SQLインジェクション・パターンは、正しい問合せ構文です。悪い理由で不正な問合せが発生する可能性があります。セキュリティの3つの原則(機密性、完全性、可用性)に影響を与える秘密情報(アクセス制御を迂回する)を取得しようとする悪い人であること。

さて、私たちのポイントは、SQLインジェクション攻撃などのセキュリティ上の脅威、PHPを使用したSQLインジェクション攻撃を防ぐ方法、もっと現実的なデータフィルタリング、入力データのクリアなどです。 PHPやその他のプログラミング言語を使用していない場合や、準備されたステートメントや現在SQLインジェクション防止をサポートしている他のツールなど、現代のテクノロジーを使用する人々が多く推奨するように、これらのツールはもはや利用できないと考えていますか?アプリケーションを保護する方法

SQLインジェクションに対する私のアプローチは、ユーザー入力データをデータベースに送信する前にクリアすることです(クエリ内で使用する前に)。

データのフィルタリング(安全でないデータから安全なデータへの変換)それを考慮するPDOそしてMySQLi利用できない場合は、アプリケーションをどのように保護できますか?あなたは私にそれらを使用するように強制しますか? PHP以外の言語はどうですか?私は一般的なアイデアを提供することを好みます。特定の言語だけでなく、より広い国境にも使用できます。

  1. SQLユーザー(制限ユーザー権限):最も一般的なSQL操作は(SELECT、UPDATE、INSERT)ですから、それを必要としないユーザーにUPDATE特権を与えるのはなぜですか?例えばログイン、検索ページSELECTを使用しているだけなので、高い権限を持つこれらのページでDBユーザーを使用するのはなぜですか?RULE:すべての権限に対して1つのデータベースユーザーを作成しないでください。すべてのSQL操作で、ユーザー名として(deluser、selectuser、updateuser)などのスキームを簡単に作成できます。

見る最小特権の原則

  1. データのフィルタリング:クエリのユーザー入力を構築する前に、検証とフィルタリングを行う必要があります。プログラマにとっては、ユーザー入力変数ごとにいくつかのプロパティを定義することが重要です。データタイプ、データパターン、データ長。 (xとy)の間の数字のフィールドは、文字列(テキスト)のフィールドに対して正確な規則を使用して正確に検証されなければなりません:パターンは大文字です。例えば、 zA-Z0-9_-]長さは、xとnとの間で(xとnとの間で)変化する(整数、x≦n)。ルール:正確なフィルタと検証ルールを作成することが私のベストプラクティスです。

  2. 他のツールを使用する:ここでは、準備されたステートメント(パラメータ化されたクエリ)とストアドプロシージャにも同意します。ここでの短所は、これらの方法ではほとんどのユーザーには存在しない高度なスキルが必要です。ここでの基本的な考え方はSQLユーザ入力データは(anyまたはx = xなどの)元のクエリに何も追加しないため、安全でないデータであっても両方のアプローチを使用できます。 詳細は、お読みくださいOWASP SQLインジェクション防止チートシート

あなたが高度なユーザーであれば、この防衛を好きなように使い始めることができますが、初心者の方はすぐにストアドプロシージャを実装できず、ステートメントを準備できない場合は、できるだけ入力データをフィルタするほうがよいでしょう。

最後に、ユーザがユーザ名を入力する代わりに、このテキストを以下のように送信することを考えてみましょう。

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

この入力は、準備されたステートメントやストアドプロシージャを使用せずに早期にチェックすることができますが、ユーザー側のデータのフィルタリングと検証の後で、安全な側に置くことができます。

最後のポイントは、より多くの労力と複雑さを必要とする予期しない動作を検出することです。通常のWebアプリケーションでは推奨されません。 上記のユーザー入力の予期しない動作は、これらの単語が検出されたらSELECT、UNION、IF、SUBSTRING、BENCHMARK、SHA、rootであり、入力を避けることができます。

UPDATE1:

ユーザーはこの投稿は役に立たないとコメントしました。ここにOWASP.ORG提供:

主な防衛:



オプション#1:プリペアドステートメントの使用(パラメータ化されたクエリ)

オプション#2:ストアドプロシージャの使用

オプション#3:ユーザが入力したすべての入力をエスケープする



その他の防御:



また、強制:最低限の特権

また、実行:ホワイトリストの入力検証

ご存知のように、記事の主張は有効な議論、少なくとも1つの参考文献によって支持されるべきです!さもなければ、それは攻撃と悪い主張とみなされます!

Update2:

PHPマニュアルから、PHP:Prepared Statements - Manual

エスケープとSQLインジェクション

バウンド変数はサーバーによって自動的にエスケープされます。ザ   サーバーは、エスケープされた値を適切な場所に   実行前のステートメントテンプレート。ヒントを提供する必要があります   バインドされた変数の型のサーバーを作成し、適切な   変換。詳細は、mysqli_stmt_bind_param()関数を参照してください。   情報。

サーバー内の値の自動エスケープは、時には   SQLインジェクションを防ぐためのセキュリティ機能を考慮しました。同じ   セキュリティの程度は、   入力値は正しくエスケープされます。

Update3:

準備されたステートメントを使用するときに、PDOとMySQLiがMySQLサーバーにクエリを送信する方法を知っているテストケースを作成しました。

PDO:

$user = "''1''"; //Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

クエリログ:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

クエリログ:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

準備されたステートメントもデータをエスケープしていることは明らかです。

上記の声明でも述べたようにThe automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctlyしたがって、これは、intval()任意のクエリを送信する前に整数値のための良いアイデアです、さらに、クエリを送信する前に悪意のあるユーザーデータを防止する正しい正しいアプローチ

詳細については、この質問をご覧ください:PDOはMySQLに未処理のクエリを送信し、Mysqliは準備されたクエリを送信しますが、どちらも同じ結果を生成します

参考文献:

  1. SQLインジェクションチートシート
  2. SQLインジェクション
  3. 情報セキュリティー
  4. セキュリティ原則
  5. データ検証


136

**警告:この回答に記載されているアプローチは、非常に特定のシナリオにのみ適用され、SQLインジェクション攻撃はインジェクションできることに依存しているわけではないので安全ではありませんX=Y。**

攻撃者がPHPを介してフォームにハックしようとしている場合$_GET変数やURLのクエリ文字列を使用すると、セキュリティで保護されていない場合にそれらをキャッチすることができます。

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

なぜなら1=12=21=22=11+1=2などは、攻撃者のSQLデータベースに対するよくある質問です。また、多くのハッキングアプリケーションで使用されている可能性があります。

しかし、あなたはあなたのサイトから安全なクエリを書き換えてはならないことに注意する必要があります。上のコードは、書き直しやリダイレクトのヒントを提供しています(あなた次第です)そのハッキング固有の動的クエリ文字列を攻撃者のIPアドレス、またはそれらのCookie、履歴、ブラウザ、または他の機密情報が含まれているため、後でそれらのアカウントを禁止するか、または当局に連絡することで対処できます。


125

多くの答えがありますPHPとMySQLしかし、ここにはコードがありますPHPとOracleSQLインジェクションやoci8ドライバの定期的な使用を防ぐためのものです:

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);


121

良いアイデアは、'オブジェクト・リレーショナル・マッパー'好きなイディオム

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

SQLインジェクションからだけでなく、構文エラーからもあなたを救うことができます!また、一度に複数の結果にアクションをフィルタリングしたり、複数の接続を適用するメソッド連鎖を持つモデルのコレクションをサポートします。


118

使用PDOそしてMYSQLiSQLインジェクションを防ぐ良い方法ですが、実際にMySQLの関数やクエリを扱う場合は、

mysql_real_escape_string

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

このようなことを防ぐより多くの能力があります:identifyのように - 入力が文字列、数値、文字または配列の場合、これを検出するためには非常に多くの組み込み関数があります。また、これらの関数を使用して入力データをチェックする方がよいでしょう。

is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

これらの関数を使用して入力データをチェックする方がずっと優れていますmysql_real_escape_string


  • また、$ _POST配列メンバをis_string()でチェックすることは絶対に必要ありません。 - Your Common Sense
  • 警告! mysql_real_escape_string() 間違いではない。 - eggyal
  • mysql_real_escape_string現在は推奨されていないため、もはや実行可能なオプションではありません。これは将来PHPから削除されます。 PHPやMySQLの皆さんが推奨するものに移行するのがベストです。 - jww
  • テーマ:ユーザーの送信データを信頼しないでください。あなたが期待するものは、特別な文字やブール論理を持つガベージ・データです。これは、実行しているSQL問合せの一部になるはずです。 $ _POST値はSQL部分ではなく、データとしてのみ保持します。 - Bimal Poudel

84

私は数年前にこの小さな関数を書いた:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

これにより、1行のC#のString.Format形式で文を実行できます。

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

変数型を考慮してエスケープします。テーブル、カラム名をパラメータ化しようとすると、すべての文字列が引用符で囲まれているため、無効な構文になります。

セキュリティアップデート:以前のstr_replaceバージョンでは、ユーザーデータに{#}トークンを追加して注入を許可しました。このpreg_replace_callbackバージョンにこれらのトークンが含まれている場合は問題は発生しません。

最近の質問