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

これを防ぐために何ができるでしょうか?

4

28 に答える 28

9408

使用するデータベースに関係なく、SQL インジェクション攻撃を回避する正しい方法は、データを SQL から分離することです。これにより、データはデータのままであり、SQL パーサーによってコマンドとして解釈されることはありません。正しい形式のデータ部分を使用して SQL ステートメントを作成することは可能ですが、詳細を十分に理解していない場合は、常に準備済みステートメントとパラメーター化されたクエリを使用する必要があります。これらは、パラメータとは別にデータベース サーバーに送信され、解析される 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 以外のデータベースに接続している場合は、参照できるドライバー固有の 2 番目のオプションがあります (たとえば、pg_prepare()PostgreSQLpg_execute()の場合)。PDO はユニバーサル オプションです。


接続を正しく設定する

PDOを使用して MySQL データベースにアクセスする場合、デフォルトでは実際の準備済みステートメントは使用されないことに注意してください。これを修正するには、準備済みステートメントのエミュレーションを無効にする必要があります。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 as scatchであるエラーの可能性を与えます。throwPDOException

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

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


説明

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

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

プリペアド ステートメントを使用するときに送信するパラメータはすべて文字列として扱われます (ただし、データベース エンジンが何らかの最適化を行う可能性があるため、パラメータももちろん数値になる可能性があります)。上記の例では、$name変数に'Sarah'; DELETE FROM employees結果が含まれている場合、単に string を検索することになり、空の table"'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';
}
于 2008-09-13T12:30:26.950 に答える
1711

非推奨の警告: この回答のサンプル コード (質問のサンプル コードと同様) は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 拡張機能に変更する必要があります。

最初に、影響の少ない文字列をエスケープする方法について説明します。

//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によって説明されている問題に悩まされます。整数は引用符で囲まれないため、このケースは扱いにくいため、ユーザー入力に数字のみが含まれていることを検証することで対処できます。
  • 私が気づいていない他のケースがある可能性があります。これは、遭遇する可能性のあるより微妙な問題のいくつかに関する有用なリソースであることがわかるかもしれません.
于 2008-09-13T09:48:48.617 に答える
1129

ここでのすべての答えは、問題の一部のみをカバーしています。実際、 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を超える削除された回答があります。これらはすべて、品質が悪いか、古くて悪い慣行を促進しているためにコミュニティによって削除されています。さらに悪いことに、悪い答えのいくつかは削除されず、むしろ繁栄しています。

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

または、文字列フォーマットの別の方法を提案し、それを究極の万能薬として自慢する、もう少し良い答えがあります。もちろん、そうではありません。この方法は、通常の文字列フォーマットよりも優れているわけではありませんが、すべての欠点があります。文字列にのみ適用可能であり、他の手動フォーマットと同様に、基本的にオプションであり、義務ではなく、あらゆる種類の人為的エラーが発生しやすくなります。

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

PHPマニュアルが何年にもわたって述べていることに関係なく、*_escape_stringデータを安全にすることは決してなく、意図されたこともありません。文字列以外のSQL部分には役に立たないことに加えて、自動エスケープとは逆に手動であるため、手動エスケープは間違っています。

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

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

于 2011-11-24T09:50:49.593 に答える
882

パラメータ化されたSQLクエリを実行するには、 PDO (PHPデータオブジェクト)を使用することをお勧めします。

これはSQLインジェクションから保護するだけでなく、クエリを高速化します。

mysql_また、、、mysqli_および関数ではなくPDOを使用pgsql_することで、データベースプロバイダーを切り替える必要があるというまれなケースで、アプリケーションをデータベースから少し抽象化することができます。

于 2008-09-13T00:02:20.430 に答える
654

使用PDOおよび準備されたクエリ。

($connPDOオブジェクトです)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();
于 2008-09-13T13:20:56.860 に答える
577

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

私はこの問題に直面していましたが、非常に洗練された方法で解決したと思います。ハッカーが引用符の使用を避けるために使用する方法です。これをエミュレートされたプリペアドステートメントと組み合わせて使用​​しました。あらゆる種類の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関数を追加するか、MySQL関数を使用する必要があることに注意してくださいUNHEX

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

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プレフィックスは、、、、、、などのデータ列にのみ使用できますcharvarcharまた、text空 の文字列を挿入しようとしている場合、プレフィックスの使用は少し複雑になりblockます。完全にに置き換える必要があります。そうしないと、エラーが発生します。binary
''

UNHEX()任意の列で機能します。空の文字列について心配する必要はありません。


16進法は、攻撃としてよく使用されます

この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');
于 2012-10-03T14:07:42.387 に答える
513

非推奨の警告: この回答のサンプル コード (質問のサンプル コードと同様) は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 に接続します

$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 インジェクション防止を参照してください。

于 2011-06-17T04:00:21.353 に答える
480

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

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

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

于 2008-09-13T00:15:53.283 に答える
393

最終的に何を使用する場合でも、入力がmagic_quotes他の意味のあるゴミによってまだ台無しにされていないことを確認し、必要に応じて、それを実行するstripslashesか、それをサニタイズします。

于 2008-12-08T05:26:35.927 に答える
375

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

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

パラメータ化されたクエリと入力の検証が進むべき道です。使用されたとしても、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 インジェクション (エスケープが不十分な場合)

于 2011-07-03T21:50:15.280 に答える
320

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

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

于 2012-07-03T10:14:50.590 に答える
311

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

于 2012-07-23T10:19:50.530 に答える
311

セキュリティの観点から、ストアド プロシージャを好みます( MySQL は 5.0 以降、ストアド プロシージャをサポートしています)。利点は次のとおりです。

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

欠点は -

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

誰かがPHPとMySQLまたは他のデータベースサーバーを使用したい場合:

  1. PDO (PHP データ オブジェクト) の学習について考えてみてください。これは、複数のデータベースへの統一されたアクセス方法を提供するデータベース アクセス レイヤーです。
  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% です。

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

于 2012-10-10T14:53:49.153 に答える
267

可能であれば、パラメーターのタイプをキャストしてください。ただし、int、bool、floatなどの単純な型でのみ機能します。

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
于 2012-06-12T08:03:08.573 に答える
238

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

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

于 2012-10-15T13:52:37.080 に答える
231

PDO (mysql_関数に由来する)の使用方法がわからない人のために、1 つのファイルである非常に単純な 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'));
于 2012-09-19T18:10:33.653 に答える
227

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
于 2013-05-09T16:36:55.423 に答える
226

この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
于 2012-08-03T19:59:31.307 に答える
186

この問題の簡単な代替手段は、データベース自体に適切な権限を付与することで解決できます。例: 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の詳細をご覧ください。

于 2012-10-25T08:07:25.437 に答える
182

多くの有用な回答について、このスレッドに何らかの価値を追加したいと考えています。

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

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

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

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

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

  1. SQL ユーザー (ユーザー特権の制限): 最も一般的な SQL 操作は (SELECT、UPDATE、INSERT) です。では、UPDATE 特権を必要としないユーザーになぜ 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

MySQL:

$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. データ検証
于 2013-01-28T19:37:48.973 に答える
178

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

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

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

  1. PHPmysql_real_escape_string()で事前定義された関数である、を使用すると、このコードは次の文字に円記号を追加します:、、、、、、、。SQLインジェクションの可能性を最小限に抑えるために、入力値をパラメーターとして渡します。\x00\n\r\'"\x1a
  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()PHP5.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");
于 2012-09-14T14:37:56.560 に答える
172

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

于 2013-06-25T19:00:36.200 に答える
135

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 インジェクションだけでなく、構文エラーも回避できます。また、一度に複数の結果にアクションをフィルター処理または適用するためのメソッド チェーンと複数の接続を備えたモデルのコレクションもサポートしています。

于 2014-03-20T00:17:35.683 に答える
131

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);
于 2014-01-30T07:00:43.773 に答える