ユーザーの入力が修正されずに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;--')
これが起こらないようにするには何ができますか?
準備された文とパラメータ化されたクエリを使用します。これらは、パラメータとは別にデータベースサーバに送信され、解析されるSQL文です。この方法では、攻撃者が悪質なSQLを挿入することは不可能です。
基本的にこれを達成するには2つの選択肢があります:
使用PDO(サポートされているデータベースドライバの場合):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
$stmt->execute(array('name' => $name));
foreach ($stmt as $row) {
// do something with $row
}
使用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は普遍的な選択肢です。
なお、PDO
MySQLデータベースにアクセスするリアル準備されたステートメントはデフォルトでは使用されません。これを修正するには、プリペアドステートメントのエミュレーションを無効にする必要があります。 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
すべてのエラーthrow
nとしてPDOException
s。
何ですか必須しかし、最初の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';
}
警告:この回答のサンプルコード(質問のサンプルコードのような)は、PHPの
MySQL
PHP 5.5.0では廃止され、PHP 7.0.0では完全に削除されました。
最新バージョンのPHPを使用している場合は、mysql_real_escape_string
下記のオプションは使用できなくなります(mysqli::escape_string
現代的なものです)。最近では、mysql_real_escape_string
PHPの旧バージョンではレガシーコードにのみ意味があります。
あなたは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。
質問したケースはかなり単純なものであり、複雑なケースではもっと複雑なアプローチが必要な場合があります。特に:
mysql_real_escape_string
。このような場合、「安全な」値だけが許可されるように、ユーザーの入力をホワイトリストに通す方がよいでしょう。mysql_real_escape_string
アプローチでは、次のような問題が発生します。多項式以下のコメントで。整数は引用符で囲まれないので、この場合は扱いにくいので、ユーザ入力に数字だけが含まれていることを検証することで対処できます。mysql_real_escape_string
または十分に私はパラメータ化された使用する必要がありますか? - peiman F.htmlentities
例えば - Goufalitemysql_real_escape_string()
完全性のために、最も誤りの多いアプローチを最初に列挙するファンではありません。読者はすぐに最初の例をつかむかもしれません。いいことは今は廃止されました:) - Time Sheep
すべての答えは、問題の一部のみをカバーしています。 実際、そこには四動的に追加できるさまざまなクエリ部分: -
そして準備された声明はそれらの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構文キーワードに問題があります(AND
、DESC
このような場合には、ホワイトリストが唯一のアプローチと思われます。
SQLインジェクション保護に関するベストプラクティスには一般的な合意がありますが、まだ多くの悪い習慣があります。また、PHPユーザーの心に深く根ざしたものもあります。たとえば、このページには(ほとんどの訪問者には見えませんが)80を超える削除された回答 - 品質が悪くなったり、悪い時代遅れの習慣を促進したりするため、コミュニティーから取り除かれました。さらに悪いことに、悪い回答のいくつかは削除されず、むしろ繁栄しています。
例えば、そこに(1) (2) まだ(3) 多く(4) 答え(5)、 含んでいる2番目に大きなアップ手動の文字列エスケープを提案します。これは、安全でないことが判明している時代遅れのアプローチです。
またはちょっとだけ良い答えがあります文字列書式の別の方法究極の万能薬としてもそれを誇っています。もちろん、そうではありません。このメソッドは、通常の文字列フォーマットよりも優れていますが、文字列にのみ適用でき、その他の手動フォーマットと同様に、必須ではありませんが、必須ではありません。
私はこのようなことは、非常に古い迷信のために、OWASPまたはPHPマニュアル「エスケープ」とSQLインジェクションからの保護との間の平等を宣言します。
PHPマニュアルが何世代にもわたって言ったことにかかわらず、*_escape_string
データを安全にすることは決してありません決して意図されたことはありません。文字列以外のSQL部分では無用ですが、手動エスケープは間違っています。
そしてOWASPはそれをさらに悪化させ、エスケープに重点を置いていますユーザー入力それは完全なナンセンスです:注射の保護の文脈の中にそのような言葉はありません。すべての変数は、ソースに関係なく潜在的に危険です!つまり、ソースに関係なく、すべての変数を適切にフォーマットしてクエリに入れる必要があります。それは重要な目的地です。開発者がヤギからヒツジを分離し始める瞬間(特定の変数が「安全」であるかどうかを考える)、彼は災害への第一歩を踏み出す。言葉でさえ、エントリーポイントで大量にエスケープし、非常に魔法のクォート機能に似ていることを示していることは言うまでもありません。
したがって、「エスケープする」ものとは異なり、準備されたステートメントはSQLインジェクションから実際に保護する手段(該当する場合)。
あなたがまだ確信していないなら、ここに私が書いたステップバイステップの説明があります、ヒッチハイクのSQLインジェクション防止ガイドここで私はこれらの事柄をすべて詳細に説明し、悪い習慣とその開示に完全に専念したセクションを編集しました。
FILTER_SANITIZE_NUMBER_INT
数字の文字のみを許可するので、文字列全体ではなく、ホワイトリストの文字になります。準備されたステートメントと組み合わせて、それは良好な「ベルトおよびサスペンダー」を作り出す。アプローチ。 - Sablefoste
私は使用をお勧めしたいPDO(PHP Data Objects)を使用して、パラメータ化されたSQLクエリを実行します。
これはSQLインジェクションから保護するだけでなく、クエリの処理速度も向上させます。
そして、PDOを使うのではなく、mysql_
、mysqli_
、およびpgsql_
まれにデータベースプロバイダーを切り替える必要があるため、アプリケーションをデータベースから少し抽象化します。
order by
残念ながら:( - Horse
つかいますPDO
準備されたクエリ。
($conn
〜ですPDO
オブジェクト)
$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();
ご覧のとおり、人々はあなたが準備したステートメントを最大限に使うことを提案します。間違いではありませんが、クエリが実行されたとき一度だけプロセスごとにわずかなパフォーマンス上のペナルティがあります。
私はこの問題に直面していましたが、私はそれを非常に洗練された方法 - 引用符の使用を避けるためにハッカーが使用する方法。私はこれを、エミュレートされた準備文とともに使用しました。私はそれを防ぐために使うすべて可能な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')
六角は完璧なエスケープです。注射する方法はありません。
コメントにいくつかの議論があったので、私はついにそれを明確にしたい。これらの2つのアプローチは非常に似ていますが、いくつかの点で少し異なります。
** 0x **接頭辞は、次のようなデータ列に対してのみ使用できます。char、varchar、テキスト、ブロック、バイナリなど。
また、空の文字列を挿入しようとすると少し複雑になります。あなたはそれを完全に置き換える必要があります''
、またはエラーが発生します。
UNHEX()に作用するどれかカラム;空の文字列について心配する必要はありません。
この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')
+
しかし、CONCAT
。そしてパフォーマンス:mysqlはデータを解析しなければならないので、パフォーマンスに影響するとは思えません。元が文字列か16進数かどうかは問題ではありません - Zaffy'root'
またはあなたはそれを16進数で0x726f6f74
しかし、数字を入力して文字列として送信する場合は、おそらく&#42;&#39; CHAR(42)ではなく...&#42;&#39; 16進数で0x3432
ない0x42
- ZaffySELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
- Zaffy
重要
SQLインジェクションを防止する最善の方法は、準備されたステートメント エスケープする代わりに、として受け入れられた答え実証する。
図書館のようなAura.SqlそしてEasyDB開発者が準備されたステートメントを簡単に使用できるようにします。準備されたステートメントがより優れている理由の詳細については、SQLインジェクションを停止する、 参照するこの
mysql_real_escape_string()
バイパスそしてWordPressで最近修正されたUnicode SQLインジェクションの脆弱性。
注射の防止 - mysql_real_escape_string()
PHPには、これらの攻撃を防ぐ特別な機能があります。あなたがする必要があるのは、関数の口一杯を使うことだけです。mysql_real_escape_string
。
mysql_real_escape_string
MySQLクエリで使用される文字列を受け取り、すべての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_real_escape_string
目的は、すべての入力データ文字列に対して正しいSQLクエリを構築できるようにすることです。予防SQLインジェクションは、この関数の副作用です。 - sectusmysql_real_escape_string()
間違いではない。 - eggyalmysql_real_escape_string
現在は推奨されていないため、もはや実行可能なオプションではありません。将来PHPから削除されます。 PHPやMySQLの皆さんが推奨するものに移行するのがベストです。 - jww
セキュリティ警告:この回答は、セキュリティのベストプラクティスに沿ったものではありません。エスケープは、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 . "')");
これはすべての問題を解決するわけではありませんが、それは非常に良い踏み台です。変数の存在、形式(数字、文字など)をチェックするなどの明白な項目は除外しました。
$q = "SELECT col FROM tbl WHERE x = $safe_var";
例えば。設定$safe_var
に1 UNION SELECT password FROM users
この場合、引用符がないために動作します。クエリを使用して文字列を挿入することも可能ですCONCAT
そしてCHR
。 - Polynomialmysql_real_escape_string()
間違いではない。 - eggyalmysql_real_escape_string
現在は推奨されていないため、もはや実行可能なオプションではありません。将来PHPから削除されます。 PHPやMySQLの皆さんが推奨するものに移行するのがベストです。 - jww
あなたが何をしても、あなたの入力がまだ壊れていないことを確認してくださいmagic_quotes
または他の何らかの意味のあるごみを取り除き、必要に応じてstripslashes
またはそれを衛生的にするために何でも。
パラメータ化されたクエリと入力の検証が行なわれています。 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インジェクション(エスケープ処理が不十分な場合)
私の意見では、PHPアプリケーション(または他のWebアプリケーション)のSQLインジェクションを防止する最も良い方法は、アプリケーションのアーキテクチャについて考えることです。 SQLインジェクションから守る唯一の方法は、データベースと対話するたびに正しいことを行う特別なメソッドや関数を使用することを忘れないようにすることです。そうすれば、コード内のある時点でクエリを正しくフォーマットするのを忘れるまでは時間の問題です。
MVCパターンとフレームワークを採用CakePHPまたはCodeIgniterのおそらく適切な方法です。セキュアデータベースクエリの作成などの一般的なタスクは、このようなフレームワークで解決され、一元的に実装されています。これらは、Webアプリケーションを賢明に整理し、単一のSQLクエリーを安全に構築するよりも、オブジェクトの読み込みと保存についてより多くのことを考えるのに役立ちます。
私は好きですストアドプロシージャ(MySQLには5.0以降のプロシージャサポートが保存されています)のセキュリティの観点から - 利点は -
欠点は -
SQLインジェクションや他のSQLハックを防ぐ方法はたくさんあります。インターネット上で簡単に見つけることができます(Google検索)。もちろんPDOは良いソリューションの1つです。しかし、私はあなたにSQLインジェクションからの良いリンク防止を提案したいと思います。
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インジェクションが防止されないと、非常に危険です。
私は誰かがPHPとMySQLまたはいくつかの他のデータベースサーバーを使用したいと思う:
(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%です。
また、すべてのクエリをデータベースにテストしてください。注射を防ぐより良い方法です。
可能であれば、パラメータの型をキャストします。しかし、int、bool、floatなどの単純な型でしか動作しません。
$unsafe_variable = $_POST['user_id'];
$safe_variable = (int)$unsafe_variable ;
mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
あなたがキャッシュエンジンを利用したい場合、RedisのまたはMemcachedおそらくDALMPが選択肢になる可能性があります。それは純粋なMySQLi。これをチェックして:PHPを使用したMySQL用DALMPデータベース抽象化レイヤ。
また、クエリを準備する前に引数を準備して、動的クエリを構築し、最終的には完全に準備されたクエリクエリを作成することができます。PHPを使用したMySQL用DALMPデータベース抽象化レイヤ。
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));
この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
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
この問題の簡単な代替方法は、データベース自体に適切な権限を与えることで解決できます。 たとえば、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';
詳しくはこちら付与。
私は、WebアプリケーションがSQLインジェクションに対して脆弱にならないように、3つの異なる方法を使用します。
mysql_real_escape_string()
これは、PHPこのコードでは、バックスラッシュを次の文字に追加します。\x00
、\n
、\r
、\
、'
、"
そして\x1a
。 SQLインジェクションの可能性を最小限に抑えるために、入力値をパラメータとして渡します。これがあなたに役立つことを願っています。
以下のクエリを考えてみましょう。
$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");
簡単な方法は、以下のようなPHPフレームワークを使用することです。CodeIgniterのまたはララベルフィルタリングやアクティブレコードなどの機能が組み込まれているため、これらのニュアンスについて心配する必要はありません。
便利な回答については、このスレッドにいくつかの値を追加したいと考えています。 SQLインジェクション・パターンは、ユーザー入力(ユーザーが入力してクエリーの内部で使用する入力)によって実行できる攻撃です。SQLインジェクション・パターンは、正しい問合せ構文です。悪い理由で不正な問合せが発生する可能性があります。セキュリティの3つの原則(機密性、完全性、可用性)に影響を与える秘密情報(アクセス制御を迂回する)を取得しようとする悪い人であること。
さて、私たちのポイントは、SQLインジェクション攻撃などのセキュリティ上の脅威、PHPを使用したSQLインジェクション攻撃を防ぐ方法、もっと現実的なデータフィルタリング、入力データのクリアなどです。 PHPやその他のプログラミング言語を使用していない場合や、準備されたステートメントや現在SQLインジェクション防止をサポートしている他のツールなど、現代のテクノロジーを使用する人々が多く推奨するように、これらのツールはもはや利用できないと考えていますか?アプリケーションを保護する方法
SQLインジェクションに対する私のアプローチは、ユーザー入力データをデータベースに送信する前にクリアすることです(クエリ内で使用する前に)。
データのフィルタリング(安全でないデータから安全なデータへの変換)それを考慮するPDOそしてMySQLi利用できない場合は、アプリケーションをどのように保護できますか?あなたは私にそれらを使用するように強制しますか? PHP以外の言語はどうですか?私は一般的なアイデアを提供することを好みます。特定の言語だけでなく、より広い国境にも使用できます。
見る最小特権の原則
データのフィルタリング:クエリのユーザー入力を構築する前に、検証とフィルタリングを行う必要があります。プログラマにとっては、ユーザー入力変数ごとにいくつかのプロパティを定義することが重要です。データタイプ、データパターン、データ長。 (xとy)の間の数字のフィールドは、文字列(テキスト)のフィールドに対して正確な規則を使用して正確に検証されなければなりません:パターンは大文字です。例えば、 zA-Z0-9_-]長さは、xとnとの間で(xとnとの間で)変化する(整数、x≦n)。ルール:正確なフィルタと検証ルールを作成することが私のベストプラクティスです。
他のツールを使用する:ここでは、準備されたステートメント(パラメータ化されたクエリ)とストアドプロシージャにも同意します。ここでの短所は、これらの方法ではほとんどのユーザーには存在しない高度なスキルが必要です。ここでの基本的な考え方は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であり、入力を避けることができます。
ユーザーはこの投稿は役に立たないとコメントしました。ここにOWASP.ORG提供:
主な防衛:
オプション#1:プリペアドステートメントの使用(パラメータ化されたクエリ)
オプション#2:ストアドプロシージャの使用
オプション#3:ユーザが入力したすべての入力をエスケープする
その他の防御:
また、強制:最低限の特権
また、実行:ホワイトリストの入力検証
ご存知のように、記事の主張は有効な議論、少なくとも1つの参考文献によって支持されるべきです!さもなければ、それは攻撃と悪い主張とみなされます!
PHPマニュアルから、PHP:Prepared Statements - Manual:
エスケープとSQLインジェクション
バウンド変数はサーバーによって自動的にエスケープされます。ザ サーバーは、エスケープされた値を適切な場所に 実行前のステートメントテンプレート。ヒントを提供する必要があります バインドされた変数の型のサーバーを作成し、適切な 変換。詳細は、mysqli_stmt_bind_param()関数を参照してください。 情報。
サーバー内の値の自動エスケープは、時には SQLインジェクションを防ぐためのセキュリティ機能を考慮しました。同じ セキュリティの程度は、 入力値は正しくエスケープされます。
準備されたステートメントを使用するときに、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は準備されたクエリを送信しますが、どちらも同じ結果を生成します
参考文献:
**警告:この回答に記載されているアプローチは、非常に特定のシナリオにのみ適用され、SQLインジェクション攻撃はインジェクションできることに依存しているわけではないので安全ではありませんX=Y
。**
攻撃者がPHPを介してフォームにハックしようとしている場合$_GET
変数やURLのクエリ文字列を使用すると、セキュリティで保護されていない場合にそれらをキャッチすることができます。
RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php
なぜなら1=1
、2=2
、1=2
、2=1
、1+1=2
などは、攻撃者のSQLデータベースに対するよくある質問です。また、多くのハッキングアプリケーションで使用されている可能性があります。
しかし、あなたはあなたのサイトから安全なクエリを書き換えてはならないことに注意する必要があります。上のコードは、書き直しやリダイレクトのヒントを提供しています(あなた次第です)そのハッキング固有の動的クエリ文字列を攻撃者のIPアドレス、またはそれらのCookie、履歴、ブラウザ、または他の機密情報が含まれているため、後でそれらのアカウントを禁止するか、または当局に連絡することで対処できます。
多くの答えがあります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);
良いアイデアは、'オブジェクト・リレーショナル・マッパー'好きなイディオム:
$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インジェクションからだけでなく、構文エラーからもあなたを救うことができます!また、一度に複数の結果にアクションをフィルタリングしたり、複数の接続を適用するメソッド連鎖を持つモデルのコレクションをサポートします。
使用PDOそしてMYSQLiSQLインジェクションを防ぐ良い方法ですが、実際にMySQLの関数やクエリを扱う場合は、
$unsafe_variable = mysql_real_escape_string($_POST['user_input']);
このようなことを防ぐより多くの能力があります:identifyのように - 入力が文字列、数値、文字または配列の場合、これを検出するためには非常に多くの組み込み関数があります。また、これらの関数を使用して入力データをチェックする方がよいでしょう。
$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');
$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');
これらの関数を使用して入力データをチェックする方がずっと優れていますmysql_real_escape_string
。
mysql_real_escape_string()
間違いではない。 - eggyalmysql_real_escape_string
現在は推奨されていないため、もはや実行可能なオプションではありません。これは将来PHPから削除されます。 PHPやMySQLの皆さんが推奨するものに移行するのがベストです。 - jww
私は数年前にこの小さな関数を書いた:
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
バージョンにこれらのトークンが含まれている場合は問題は発生しません。