PHPでSQLインジェクションを防ぐにはどうすればよいですか?


2773

ユーザー入力を変更せずに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;--')

これを防ぐために何ができますか?

回答:


8960

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

これを達成するには、基本的に2つのオプションがあります。

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

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute([ '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以外のデータベースに接続する場合は、あなたが(例えば、参照することができますことをドライバ固有の第二の選択肢がありpg_prepare()pg_execute()PostgreSQLのは)。PDOは汎用オプションです。


接続を正しく設定する

PDOMySQLデータベースへのアクセスに使用する場合、実際の準備済みステートメントはデフォルトでは使用されないことに注意してください。これを修正するには、準備済みステートメントのエミュレーションを無効にする必要があります。PDOを使用して接続を作成する例は次のとおりです。

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

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

上記の例では、エラーモードは厳密には必要ありませんが、追加することをお勧めします。このようにして、スクリプトはFatal Error何かがうまくいかなくても停止しません。そして、それは開発者にnがsでcatchあるエラーの機会を与えます。throwPDOException

ある何必須、しかし、最初にsetAttribute()無効にエミュレートされたプリペアドステートメントおよび使用にPDOを伝えるライン、本当のプリペアドステートメントを。これにより、MySQLサーバーに送信する前に、ステートメントと値がPHPによって解析されないようにします(悪意のあるSQLを挿入する可能性のない攻撃者を与えます)。

charsetコンストラクタのオプションでを設定できますが、PHPの「古い」バージョン(5.3.6より前)はDSNの文字セットパラメータを暗黙的に無視することに注意することが重要です。


説明

渡したSQLステートメントprepareは、データベースサーバーによって解析およびコンパイルされます。パラメーター(上記の例の?ような名前付きパラメーター:name)を指定することにより、フィルター処理する場所をデータベースエンジンに指示します。次に、を呼び出すexecuteと、準備されたステートメントが指定したパラメーター値と結合されます。

ここで重要なのは、パラメーター値がSQL文字列ではなく、コンパイルされたステートメントと組み合わされることです。SQLインジェクションは、スクリプトをだましてデータベースに送信するSQLを作成するときに悪意のある文字列を含めるように仕掛けます。したがって、実際のSQLをパラメーターとは別に送信することで、意図しないものになってしまうリスクを制限します。

準備されたステートメントを使用するときに送信するパラメーターはすべて、文字列として扱われます(もちろん、データベースエンジンが最適化を行う場合があるため、パラメーターが最終的に数値になる場合もあります)。上記の例では、$name変数に'Sarah'; DELETE FROM employees結果が含まれている場合、単に文字列を検索するだけ"'Sarah'; DELETE FROM employees"、空のテーブルが作成されることはありません。

準備済みステートメントを使用するもう1つの利点は、同じセッションで同じステートメントを何度も実行した場合、構文解析とコンパイルが1回だけ行われるため、速度が向上することです。

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

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

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

準備されたステートメントを動的クエリに使用できますか?

クエリパラメーターに準備されたステートメントを引き続き使用できますが、動的クエリ自体の構造はパラメーター化できず、特定のクエリ機能はパラメーター化できません。

これらの特定のシナリオでは、可能な値を制限するホワイトリストフィルターを使用するのが最善の方法です。

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

86
私はここにどこにもそれを見ていないので、ただ、防衛の別の行がある追加するウェブアプリケーションファイアウォール:ルールは、SQLインジェクション攻撃を探しに設定することができます(WAF)
jkerak

37
また、mysql_queryの公式ドキュメントでは1つのクエリしか実行できないため、以外のクエリは; 無視されます。これがすでに廃止されている場合でも、PHP 5.5.0の下には多くのシステムがあり、この関数を使用する可能性があります。php.net/manual/en/function.mysql-query.php
Randall Valenciano

12
これは悪い習慣ですが、問題後の解決策です:SQLインジェクションだけでなく、あらゆるタイプのインジェクション(たとえば、F3フレームワークv2にビューテンプレートインジェクションホールがあった)で、古いウェブサイトやアプリが問題を抱えている場合インジェクションの欠陥から、1つの解決策は、$ _ POSTなどのスーパーグローバルの事前定義された変数の値を、ブートストラップでエスケープされた値で再割り当てすることです。PDOによって、それでも(今日のフレームワークについても)エスケープすることが可能です:substr($ pdo-> quote($ str、\ PDO :: PARAM_STR)、1、-1)
Alix

15
この答えには、準備済みステートメントとは何かの説明が欠けています。1つは、リクエスト中に準備済みステートメントを大量に使用するとパフォーマンスが低下し、場合によっては10倍のパフォーマンスヒットになることもあります。パラメータのバインドをオフにしてPDOを使用し、ステートメントの準備をオフにした方がよいケースです。
donis

6
場合にはあなたが直接クエリメイクを使用している、PDOが優れている使用してあなたが使用mysqliの:: escape_string
Kassem伊谷

1652

非推奨の警告: この回答のサンプルコード(質問のサンプルコードと同様)はMySQL、PHP 5.5.0で非推奨となり、PHP 7.0.0で完全に削除されたPHP の拡張機能を使用します。

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

最近のバージョンのPHPを使用しているmysql_real_escape_string場合、以下に概説されているオプションは使用できなくなります(mysqli::escape_string最近の同等のものです)。最近のmysql_real_escape_stringオプションは、古いバージョンのPHP上のレガシーコードに対してのみ意味があります。


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

最初に1つをエスケープする、インパクトの低い文字列について説明します。

//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関数の詳細もご覧ください。

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

<?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アプローチすると、以下のコメントの多項式で説明されている問題が発生します。整数は引用符で囲まれないため、この場合はトリッキーです。ユーザー入力に数字のみが含まれていることを検証することで対処できます。
  • 私が知らない他のケースがおそらくあります。あなたは見つけるかもしれない、これはあなたが出会うことができ、より微妙な問題のいくつかの便利なリソースです。

1
使用mysql_real_escape_stringで十分ですか、それともパラメーター化されたものを使用する必要がありますか?
ペイマンF.18年

5
@peimanF。ローカルプロジェクトでも、パラメーター化されたクエリを使用することをお勧めします。パラメータ化されたクエリを使用すると、SQLインジェクションがないことが保証されます。しかし、あなたがして(テキストにHTMLコードを入れて、すなわちXSSインジェクション)偽の検索を避けるためにデータをサニタイズする必要があり心に留めておくhtmlentities例えば
Goufalite

2
@peimanF。パラメータ化されたクエリと値をバインドするための良い習慣ですが、今のところ実際のエスケープ文字列は適切です
Richard

mysql_real_escape_string()完全性のためにを含めることは理解していますが、最もエラーが発生しやすいアプローチを最初にリストするのは好きではありません。読者は最初の例をすぐにつかむかもしれません。現在は非推奨になっています。:)
SteenSchütt、2018

1
@SteenSchütt-すべてのmysql_*関数は非推奨です。これらは、などの同様の mysqli_*関数に置き換えられましたmysqli_real_escape_string
リックジェームズ

1073

ここでのすべての回答は、問題の一部のみをカバーしています。実際、SQLに動的に追加できる4つの異なるクエリ部分があります。-

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

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

ただし、クエリをさらに動的にして、演算子または識別子を追加する必要がある場合もあります。したがって、さまざまな保護技術が必要になります。

一般に、このような保護アプローチはホワイトリストに基づいています

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

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

プロセスを簡単にするために、すべての作業を1行で行うホワイトリストヘルパー関数を作成しました。

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

識別子を保護する別の方法があります-エスケープしますが、より堅牢で明示的なアプローチとして、ホワイトリストに固執します。ただし、識別子を引用符で囲んでいる限り、引用文字をエスケープして安全にすることができます。たとえば、mysqlのデフォルトでは、エスケープするには引用符文字を2重にする必要があります。他のDBMSでは、エスケープルールは異なります。

それでも、(ようなSQL構文のキーワードに問題があるANDDESCなど)が、しかし、ホワイトリストは、この場合の唯一の方法と思われます。

したがって、一般的な推奨事項は次のように表現できます

  • SQLデータリテラルを表す変数(または、簡単に言えば、SQL文字列または数値)は、準備されたステートメントを介して追加する必要があります。例外なく。
  • SQLキーワード、テーブルまたはフィールド名、演算子など、その他のクエリ部分は、ホワイトリストでフィルター処理する必要があります。

更新

SQLインジェクション保護に関するベストプラクティスについては一般的な合意がありますが、それでも多くの悪いプラクティスがあります。また、PHPユーザーの心に深く根ざしすぎているものもあります。たとえば、このページには(ほとんどの訪問者には表示されませんが削除された回答が80以上あります。品質が悪い、または古くて古い慣行を推進しているため、コミュニティによってすべて削除されました。さらに悪いことに、いくつかの悪い答えは削除されず、むしろ繁栄しています。

たとえば、there(1) are(2) still(3) many(4) Answers (5)には、手動で文字列をエスケープすることを示唆する2番目に多い回答が含まれています。これは、安全でないことが証明されている古いアプローチです。

または、文字列の書式設定のもう1つの方法を示唆し、究極の万能薬としてそれを誇示する少し良い答えがあります。もちろん、そうではありません。この方法は通常の文字列フォーマットよりも優れていませんが、欠点をすべて保持しています。文字列にのみ適用でき、他の手動フォーマットと同様に、本質的にオプションであり、義務的ではなく、あらゆる種類の人為的エラーが発生しやすくなります。

これはすべて、OWASPPHPマニュアルなどの当局によってサポートされている非常に古い迷信によるもので、SQLのインジェクションからの「エスケープ」と保護の平等を宣言していると思います。

PHPマニュアルで古くから言われていることに関係なく、決して*_escape_stringデータを安全にしたり、意図したことはありません。文字列以外のSQL部分では役に立たないだけでなく、自動エスケープとは対照的に手動エスケープは手動です。

そして、OWASPはさらに悪いことに、まったく無意味なユーザー入力のエスケープに重点を置きます。注入保護の文脈ではそのような言葉はありません。すべての変数は潜在的に危険です-ソースに関係なく!または、言い換えると、ソースに関係なく、すべての変数を適切にフォーマットしてクエリに入れる必要があります。重要なのは目的地です。開発者が羊を山羊から分離し始めた瞬間(特定の変数が「安全」であるかどうかを考える)、彼/彼女は災害に向けて彼/彼女の最初のステップを踏み出します。言い回しでさえ、非常に魔法の引用機能に似た、エントリポイントでの一括エスケープを示唆していることは言うまでもありません-すでに軽視され、非推奨になり、削除されました。

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


848

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

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

そして、PDOではなく、使用することによってmysql_mysqli_およびpgsql_機能を、あなたは、データベースプロバイダを切り替える必要があること稀で、アプリケーションがもう少しデータベースから抽出します。


619

PDOクエリの使用と準備。

$connPDOオブジェクトです)

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

549

ご覧のとおり、多くの場合、準備されたステートメントを使用することをお勧めします。間違いではありませんが、クエリがプロセスごとに1 回だけ実行されると、パフォーマンスがわずかに低下します。

私はこの問題に直面していましたが、非常に洗練された方法で解決したと思います。ハッカーが引用符を使用しないようにする方法です。これをエミュレートされた準備済みステートメントと組み合わせて使用​​しました。あらゆる種類のSQLインジェクション攻撃を防ぐために使用しています。

私のアプローチ:

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

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

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

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

したがって、たとえば、クエリ:

SELECT password FROM users WHERE name = 'root'

となります:

SELECT password FROM users WHERE name = 0x726f6f74

または

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

16進数は完全な脱出です。注入する方法はありません。

UNHEX関数と0xプレフィックスの違い

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

** 0x **プレフィックスは、char、varchar、text、block、binaryなどのデータ列にのみ使用できます。
また、空の文字列を挿入する場合、その使用法は少し複雑になります。完全にに置き換える''必要があります。そうしないと、エラーが発生します。

UNHEX()任意の列で機能します。空の文字列を気にする必要はありません。


Hexメソッドは攻撃としてよく使用されます

この16進法は、SQLインジェクション攻撃としてよく使用されることに注意してください。この場合、整数は文字列のようであり、だけでエスケープされmysql_real_escape_stringます。その後、引用符の使用を回避できます。

たとえば、次のようなことをした場合:

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

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

SELECT ... WHERE id = -1 union all select table_name from information_schema.tables

そして今、ただテーブル構造を抽出します:

SELECT ... WHERE id = -1 union all select column_name from information_schema.column where table_name = 0x61727469636c65

次に、必要なデータを選択します。かっこよくないですか?

しかし、注入可能なサイトのコーダーがそれを16進数にすると、クエリは次のようになるため、注入は不可能になります。 SELECT ... WHERE id = UNHEX('2d312075...3635')


6
@SumitGuptaそうですね。MySQLはと連結し+ませんが、と連結しますCONCAT。そしてパフォーマンスに関して:mysqlはデータを解析する必要があるため、パフォーマンスに影響を与えるとは思いません。起源が文字列であるか16
進数である

11
@YourCommonSense概念が理解できません... mysqlで文字列を使用したい場合は、このように引用する'root'か、16進数にすることができますが0x726f6f74、数値が必要な場合は文字列として送信すると、おそらくCHAR(42 )... 16進数の '42'はそうで0x3432はない0x42
Zaffy

7
@YourCommonSense言うことは何もありません...ただ笑...数値フィールドで16進数を試してみたい場合は、2番目のコメントを参照してください。それがうまくいくと私はあなたと賭けます。
Zaffy 2013

2
答えはそれが整数値ではそのように機能しないことを明確に述べています、その理由はbin2hexが渡された値を文字列に変換するためです(したがって、bin2hex(0)は0x03ではなく0x30です)-それはおそらくあなたを混乱させる部分です。あなたがそれに従えば、それは完全に機能します(少なくとも私のサイトでは、debianマシン上の4つの異なるmysqlバージョン、5.1.xから5.6.xでテストされています)。結局のところ、16進数は表現の方法にすぎず、値ではありません;)
グリフィン

5
まだ理解していない@YourCommonSense?文字列が空の場合、エラーで終了するため、0xおよびconcatは使用できません。クエリの単純な代替手段が必要な場合は、これを試してくださいSELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
Zaffy

499

非推奨の警告: この回答のサンプルコード(質問のサンプルコードと同様)はMySQL、PHP 5.5.0で非推奨となり、PHP 7.0.0で完全に削除されたPHP の拡張機能を使用します。

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

重要

SQLインジェクションを防止する最良の方法は、受け入れられた回答が示すようにエスケープする代わりにプリペアドステートメント を使用することです。

Aura.SqlEasyDBなどのライブラリがあり、開発者は準備されたステートメントをより簡単に使用できます。準備されたステートメントが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インジェクション防止をご覧ください。


30
これは、レガシーmysql拡張で実行できる最高の方法です。新しいコードについては、mysqliまたはPDOに切り替えることをお勧めします。
アルバロゴンサレス

7
私はこの「これらの攻撃を防ぐために特別に作られた機能」に同意しません。mysql_real_escape_string目的は、すべての入力データ文字列に対して正しいSQLクエリを構築できるようにすることだと思います。予防sql-injectionは、この関数の副作用です。
sectus 2013

4
関数を使用して正しい入力データ文字列を記述しないでください。エスケープする必要がない、またはすでにエスケープされている正しいものを記述するだけです。mysql_real_escape_string()は、あなたが言及する目的を念頭に置いて設計されている可能性がありますが、その唯一の値はインジェクションを防ぐことです。
Nazca

17
警告! mysql_real_escape_string() 間違いありません
eggyal 2014

9
mysql_real_escape_stringは非推奨となったため、実行可能なオプションではなくなりました。将来的にはPHPから削除される予定です。PHPまたはMySQLの人々が推奨するものに移ることが最善です。
jww 2015

462

あなたはこのような基本的なことをすることができます:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

これはすべての問題を解決するわけではありませんが、非常に優れた足掛かりです。変数の存在、形式(数字、文字など)の確認など、明白な項目は省略しました。


28
私はあなたの例を試しました、そしてそれは私にとってはうまくいきます。あなたは「これはすべての問題を解決しない」
チヌーク

14
文字列を引用しない場合でも、それは注入可能です。$q = "SELECT col FROM tbl WHERE x = $safe_var";例を挙げましょう。引用符がないため、この場合はに設定$safe_var1 UNION SELECT password FROM usersます。CONCATおよびを使用して、クエリに文字列を挿入することもできますCHR
多項式2013

4
@Polynomial完全に正しいですが、これは単に誤った使い方だと思います。あなたがそれを正しく使用する限り、それは間違いなく動作します。
glglgl 2013

22
警告! mysql_real_escape_string() 間違いありません
eggyal

8
mysql_real_escape_stringは非推奨となったため、実行可能なオプションではなくなりました。将来的にはPHPから削除される予定です。PHPまたはMySQLの人々が推奨するものに移ることが最善です。
jww 2015

380

最終的に何を使用するにしても、入力magic_quotesや他の意味のあるゴミによって入力がすでに破壊されていないことを確認し、必要に応じて実行するstripslashesか、消毒してください。


11
確かに; magic_quotesをオンにして実行すると、不適切な練習が奨励されます。ただし、環境をそのレベルに常に制御できるとは限りません。サーバーを管理するためのアクセス権がないか、アプリケーションがそのような構成に依存する(シャダー)アプリケーションと共存する必要があります。これらの理由から、移植可能なアプリケーションを作成することは良いことです。ただし、デプロイメント環境を制御する場合、それが社内アプリケーションであったり、特定の環境でのみ使用されるなどの理由で、明らかに労力が無駄になります。
Rob

24
PHP 5.4の時点で、「マジッククオート」として知られる忌まわしきものが殺されました。そして、悪いゴミへの良いリダンダンス。
BryanH 2013年

363

非推奨の警告: この回答のサンプルコード(質問のサンプルコードと同様)はMySQL、PHP 5.5.0で非推奨となり、PHP 7.0.0で完全に削除されたPHP の拡張機能を使用します。

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

パラメータ化されたクエリと入力の検証は、進むべき道です。SQLインジェクションがmysql_real_escape_string()使用されている場合でも、SQLインジェクションが発生するシナリオは多数あります。

これらの例は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インジェクション(エスケープでは不十分)


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

312

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

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


5
最初の段落は重要だと思います。理解が鍵です。また、誰もが会社で働いているわけではありません。多数の人々にとって、フレームワークは実際には理解の概念に反します。基本事項に親しむことは、期限内に作業している間は評価されない場合がありますが、日曜大工は、手を汚すことを楽しんでいます。フレームワーク開発者はそれほど特権的ではないので、他の誰もが頭を下げて、間違いを犯してはいけないと思い込まなければなりません。決定を下す力は依然として重要です。私のフレームワークが将来的に他のスキームに取って代わることはないと誰が言っているのですか?
Anthony Rutledge 2017年

@AnthonyRutledgeあなたは絶対に正しいです。何がなぜ起こっているのかを理解することは非常に重要です。ただし、実際に試して積極的に使用および開発されたフレームワークが多くの問題に遭遇して解決し、多くのセキュリティホールにパッチを適用する可能性はかなり高いです。ソースを確認して、コードの品質を確認することをお勧めします。テストされていない混乱の場合は、おそらく安全ではありません。
Johannes Fahrenkrug 2017年

3
ここに。ここに。良い点。ただし、多くの人がMVCシステムの採用を学習および学習できるが、誰もがそれを手で再現できるわけではない(コントローラーおよびサーバー)ことに同意しますか?この点では、行き過ぎることがあります。私のピーナッツバターピーカンクッキーを加熱する前に、私の電子レンジを理解する必要がありますか?;-)
Anthony Rutledge

3
@AnthonyRutledge同意する!ユースケースにも違いがあると思います。個人のホームページ用にフォトギャラリーを構築していますか、それともオンラインバンキングWebアプリケーションを構築していますか?後者の場合、セキュリティの詳細と、私が使用しているフレームワークがそれらにどのように対処しているかを理解することが非常に重要です。
Johannes Fahrenkrug 2017年

3
ああ、セキュリティの例外は当然のことです。参照してください、私はそれをすべて危険にさらし、破産するつもりです。:-)冗談です。十分な時間があれば、人々はかなり安全なアプリケーションを作ることを学ぶことができます。急いでいる人が多すぎます。彼らは手を挙げて、フレームワークがより安全であると思います。結局のところ、彼らは物事をテストして理解するのに十分な時間がないのです。また、セキュリティは専攻が必要な分野です。これは、アルゴリズムやデザインパターンを理解することによって、プログラマが詳細に知っているものではありません。
Anthony Rutledge 2017年

298

セキュリティの観点から、私はストアドプロシージャを好んでいますMySQLは5.0以降、ストアドプロシージャをサポートしています)-利点は-

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

欠点は-

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

297

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インジェクションを防止しないと非常に危険です。


267

PHPとMySQLまたは他のデータベースサーバーを使用したい場合は、次のように考えます。

  1. PDO(PHP Data Objects)の学習について考えてください。これは、複数のデータベースへのアクセスの統一された方法を提供するデータベースアクセスレイヤーです。
  2. MySQLiの学習について考える
  3. strip_tagsmysql_real_escape_stringのようなネイティブのPHP関数を使用するか、変数が数値の場合はだけを使用し(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();

PS

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

しかし、PDOとMySQLiはどちらも非常に高速ですが、MySQLiはベンチマークでわずかに高速で実行されます–準備されていないステートメントでは〜2.5%、準備されたステートメントでは〜6.5%。

そして、データベースへのすべてのクエリをテストしてください-インジェクションを防ぐためのより良い方法です。


mysqliは正しくありません。最初のパラメーターはデータ型を表します。
mickmackusa

257

可能であれば、パラメーターのタイプをキャストします。しかし、これはint、bool、floatなどの単純な型でのみ機能します。

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

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

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

233

RedisMemcachedなどのキャッシュエンジンを利用したい場合は、DALMPを選択することをお勧めします。純粋なMySQLiを使用します。これをチェックしてください:PHPを使用したMySQL用のDALMPデータベース抽象化レイヤー。

また、動的クエリを作成し、最後に完全に準備されたステートメントクエリを作成できるように、クエリを準備する前に引数を「準備」できます。PHPを使用したMySQL用のDALMPデータベース抽象化レイヤー。


224

mysql_関数から来る)PDOの使い方がわからない人のために、単一のファイルである非常に単純なPDOラッパーを作成しました。アプリケーションを実行するために必要なすべての一般的なことを簡単に実行できることを示すために存在します。PostgreSQL、MySQL、SQLiteで動作します。

基本的には、それを読んで、あなたがマニュアルを読みながら、店にそれを簡単にかつ形式で値を取得するために、実際の生活の中での使用にPDO機能を配置する方法を確認するためにあなたがしたいです。

単一の列が欲しい

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

配列(キー=>値)の結果(つまり、selectboxを作成するため)が欲しい

$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));

222

この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

220

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_preparemysqli_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

3
質問は非常に一般的です。上記のいくつかの素晴らしい答えがありますが、ほとんどは準備されたステートメントを提案しています。MySQLi asyncは準備済みステートメントをサポートしていないため、sprintfはこの状況に最適なオプションのように見えます。
ダスティングラハム

183

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

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

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

FLUSH PRIVILEGES; 

フラッシュに関する詳細情報。

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

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

GRANTの詳細をご覧ください。


25
この答えは本質的に間違っています。注射の予防を防ぐのに役立ちませんが、結果を和らげようとするだけです。無駄に。
常識

1
そう、それは解決策を提供しませんが、物事を回避するためにあなたが事前に行うことができることです。
Apurv Nerlekar 2016年

1
@Apurv私の目標がデータベースから個人情報を読み取ることである場合、DELETE権限がなくても意味がありません。
Alex Holsgrove 16年

1
@AlexHolsgrove:簡単に言うと、私は結果を和らげるための良い方法を提案していました。
Apurv Nerlekar 2016年

2
@Apurv「結果を和らげる」必要はありません。それを防ぐために可能な限りのことをしたいのです。公平を期すために、正しいユーザーアクセスを設定することは重要ですが、実際にはOPが求めているものではありません。
Alex Holsgrove 2016年

177

多くの有用な回答に関して、私はこのスレッドにいくつかの価値を追加したいと思っています。

SQLインジェクションは、ユーザー入力(ユーザーによって入力され、クエリ内で使用される入力)を介して実行できる攻撃です。SQLインジェクションパターンは正しいクエリ構文ですが、それは私たちが呼び出すことができます。悪い理由による悪いクエリであり、セキュリティの3つの原則(機密性)に影響を与える(アクセス制御をバイパスして)秘密の情報を取得しようとする悪い人がいる可能性があると想定しています、整合性、可用性)。

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

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

データのフィルタリング(安全でないデータを安全なデータに変換)

PDOMySQLiが利用できないことを考慮してください。アプリケーションをどのように保護できますか?それらを使用するように強制しますか?PHP以外の他の言語についてはどうですか?特定の言語だけでなく、より広い国境で使用できるため、私は一般的なアイデアを提供することを好みます。

  1. SQLユーザー(ユーザー特権の制限):最も一般的なSQL操作は(SELECT、UPDATE、INSERT)ですが、なぜそれを必要としないユーザーにUPDATE特権を与えるのでしょうか。たとえば、ログインページと検索ページはSELECTのみを使用しているのに、これらのページでDBユーザーを高い特権で使用するのはなぜですか。

ルール:すべての権限に対して1人のデータベースユーザーを作成しないでください。すべてのSQL操作で、ユーザー名として(deluser、selectuser、updateuser)のようなスキームを作成して、簡単に使用できます。

最小特権の原則を参照してください。

  1. データのフィルタリング:クエリのユーザー入力を作成する前に、それを検証してフィルタリングする必要があります。プログラマーにとって、ユーザー入力変数ごとにいくつかのプロパティを定義することが重要です: データ型、データパターン、データ長。(xとy)の間の数値であるフィールドは、正確なルールを使用して正確に検証する必要があります。文字列(テキスト)であるフィールドの場合:パターンが当てはまる場合、たとえば、ユーザー名に一部の文字のみを含める必要があります。 [a-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です。これらの単語が検出されると、入力を回避できます。

更新1:

ユーザーがこの投稿は役に立たないとコメントしました、OK!OWASP.ORGが提供するものは次のとおりです。

一次防御:

オプション#1:準備文の使用(パラメータ化クエリ)
オプション#2:ストアドプロシージャの使用
オプション#3:入力付属のすべてのユーザーのエスケープ

:追加の防御を

も強制:最小特権は
また、実行します。ホワイトリスト入力の検証を

ご存じかもしれませんが、記事の主張は、少なくとも1つの参照によって、有効な引数によってサポートされる必要があります。さもなければ、それは攻撃と見なされ、悪い主張です!

アップデート2:

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

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

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

サーバー内の値の自動エスケープは、SQLインジェクションを防ぐためのセキュリティ機能と見なされることがあります。入力値が正しくエスケープされている場合は、準備されていないステートメントで同じ程度のセキュリティを実現できます。

更新3:

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

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

上記のステートメントでも述べたように、

サーバー内の値の自動エスケープは、SQLインジェクションを防ぐためのセキュリティ機能と見なされることがあります。入力値が正しくエスケープされている場合、準備されていないステートメントでも同じ程度のセキュリティを実現できます。

したがって、これintval()は、クエリを送信する前に、整数値に対してなどのデータ検証が適切であることを証明します。さらに、クエリを送信する前に悪意のあるユーザーデータを防止することは、正しい有効なアプローチです。

詳細については、この質問を参照してください:PDOは生のクエリをMySQLに送信し、Mysqliは準備済みクエリを送信しますが、どちらも同じ結果を生成します

参照:

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

175

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

非推奨の警告:mysql拡張は現在非推奨です。PDO拡張機能の使用をお勧めします

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");

169

簡単な方法は、フィルタリングやアクティブレコードなどの組み込み機能を備えたCodeIgniterLaravelなどのPHPフレームワークを使用して、これらのニュアンスを気にする必要がないようにすることです。


7
問題の要点は、そのようなフレームワークを使用せずにこれを実行することです。
Sanke

147

警告:この回答で説明されているアプローチは非常に特定のシナリオにのみ適用され、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アドレス、またはも、そのクッキー、歴史、ブラウザ、またはその他の機密を情報なので、後でアカウントを禁止したり、当局に連絡したりして対処できます。


どうしたの1-1=0?:)
Rápliアンドラーシュ

@RápliAndrásある種の([0-9\-]+)=([0-9]+)
2019年

127

PHPとMySQLには非常に多くの答えがありますが、SQLインジェクションとoci8ドライバーの定期的な使用を防ぐためのPHPとOracleのコードを次に示します。

$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);

oci_bind_by_nameパラメータについて説明してください。
Jahanzeb Awan

127

Idiormのようなオブジェクトリレーショナルマッパーを使用することをお勧めします。

$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インジェクションからだけでなく、構文エラーからも節約できます!また、メソッドチェーンを使用してモデルのコレクションをサポートし、一度に複数の結果と複数の接続をフィルタリングまたはアクションに適用します。


124

非推奨の警告: この回答のサンプルコード(質問のサンプルコードと同様)はMySQL、PHP 5.5.0で非推奨となり、PHP 7.0.0で完全に削除されたPHP の拡張機能を使用します。

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

PDOMYSQLiを使用することは、SQLインジェクションを防ぐための良い習慣ですが、MySQLの関数とクエリを実際に使用したい場合は、

mysql_real_escape_string

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

これを防止する機能は他にもあります。たとえば、識別-入力が文字列、数値、文字、または配列の場合、これを検出する組み込み関数が非常に多くあります。また、これらの関数を使用して入力データをチェックすることをお勧めします。

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


10
また、is_string()を使用して$ _POST配列のメンバーをチェックしてもまったく意味がありません
常識14年

21
警告! mysql_real_escape_string() 間違いありません
eggyal 2014

10
mysql_real_escape_stringは非推奨となったため、実行可能なオプションではなくなりました。将来的にはPHPから削除される予定です。PHPまたはMySQLの人々が推奨するものに移ることが最善です。
jww 2015

2
テーマ:ユーザーが送信したデータを信頼しない。期待するのは、特殊文字またはブールロジックを含むガベージデータであり、それ自体が実行中のSQLクエリの一部になるはずです。$ _POST値はSQL部分ではなく、データとしてのみ保持します。
Bimal Poudel 2017

88

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

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置換にこれらのトークンが含まれている場合、このバージョンでは問題は発生しません。

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