短い答えは「いいえ」です。PDOは、SQLインジェクション攻撃から防御するわけではありません。特定のあいまいなエッジケースの場合。
私はこの答えをPDOについて話すようにしています...
長い答えはそれほど簡単ではありません。これは、ここで説明した攻撃に基づいています。
攻撃
それでは、攻撃を示すことから始めましょう...
$pdo->query('SET NAMES gbk');
$var = "\xbf\x27 OR 1=1 /*";
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute(array($var));
特定の状況では、それは複数の行を返します。ここで何が起こっているのかを分析してみましょう:
文字セットの選択
$pdo->query('SET NAMES gbk');
仕事へのこの攻撃のために、我々は、サーバーの両方のエンコードへの接続に期待していることエンコーディングを必要とする'
ASCII、すなわちのように0x27
してその最後のバイトASCIIであるいくつかの文字持っている\
すなわち0x5c
。結局のところ、デフォルトでのMySQL 5.6でサポートされている5つのなどのエンコーディングがありますbig5
、cp932
、gb2312
、gbk
とsjis
。gbk
ここで選択します。
ここで、SET NAMES
ここでの使用に注意することが非常に重要です。これにより、サーバー上の文字セットが設定されます。それを行う別の方法がありますが、私たちはすぐにそこに着きます。
ペイロード
この注入に使用するペイロードは、バイトシーケンスから始まります0xbf27
。にgbk
、これは無効なマルチバイト文字です。ではlatin1
、文字列¿'
です。latin1
と gbk
で0x27
は、それ自体がリテラル'
文字であることに注意してください。
呼び出しaddslashes()
た場合、文字の前にASCII \
ieを挿入するため、このペイロードを選択しました。我々が羽目になるだろうので、その中には、2つの文字列です:0x5c
'
0xbf5c27
gbk
0xbf5c
続きます0x27
。または、言い換えると、エスケープされていない有効な文字が続き'
ます。ただし、は使用していませんaddslashes()
。次のステップに進みます...
$ stmt-> execute()
ここで重要なことは、PDOはデフォルトでは真の準備済みステートメントを実行しないことです。それらをエミュレートします(MySQLの場合)。したがって、PDOは内部的にクエリ文字列を作成し、mysql_real_escape_string()
バインドされた各文字列値に対して(MySQL C API関数)を呼び出します。
へのC API呼び出しmysql_real_escape_string()
はaddslashes()
、接続文字セットを認識するという点で異なります。したがって、サーバーが予期している文字セットに対して適切にエスケープを実行できます。ただし、この時点までは、クライアントはlatin1
接続にまだ使用していると考えています。使用しているサーバーを指定しましたgbk
が、クライアントはまだそれを使用していると考えていlatin1
ます。
したがって、 mysql_real_escape_string()
バックスラッシュ挿入'
、「エスケープされた」コンテンツにフリーハンギング文字が含まれます。私たちが見にした場合、実際には、$var
中にgbk
文字セット、我々は参照してくださいね。
縗 'OR 1 = 1 / *
これはまさに攻撃に必要なものです。
クエリ
この部分は単なる形式ですが、ここにレンダリングされたクエリがあります:
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
おめでとうございます。PDOPrepared Statementsを使用してプログラムを攻撃しました...
簡単な修正
ここで、エミュレートされた準備済みステートメントを無効にすることでこれを防ぐことができることに注意してください。
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
これは通常、真の準備済みステートメントになります(つまり、データはクエリとは別のパケットで送信されます)。ただし、PDOはMySQLがネイティブで準備できないステートメントを列挙するエミュレートに自動的にフォールバックすることに注意してください。注意してください。これはマニュアルにれが、適切なサーバーバージョンを選択するように注意してください)。
正しい修正
ここでの問題は、mysql_set_charset()
代わりにC APIを呼び出さなかったことです。SET NAMES
。使用した場合、2006年以降のMySQLリリースを使用していれば問題ありません。
以前のMySQLリリースを使用している場合、バグによりmysql_real_escape_string()
、ペイロードに含まれるような無効なマルチバイト文字は、クライアントが接続エンコーディングを正しく通知されていたとしても、エスケープの目的で 1バイトとして扱われるため、この攻撃はまだ成功しています。バグがMySQLの中で修正されました4.1.20、5.0.22および5.1.11。
しかし、最悪なのは、PDO
C APIがmysql_set_charset()
5.3.6まで公開されなかったことです。そのため、以前のバージョンでは、考えられるすべてのコマンドに対してこの攻撃を防ぐことはできません。これはDSNパラメータとして公開されています。代わりに 使用する必要がありSET NAMES
ます...
救いの恵み
最初に述べたように、この攻撃が機能するには、脆弱な文字セットを使用してデータベース接続をエンコードする必要があります。 utf8mb4
は脆弱ではなく、すべての Unicode文字をサポートできます。そのため、代わりにそれを使用することを選択できますが、MySQL 5.5.3以降でのみ使用可能です。もう1つの方法はですutf8
。これも脆弱ではなく、Unicode Basic Multilingual Plane全体をサポートできます。ます。
または、NO_BACKSLASH_ESCAPES
SQLモードを有効にすることもできます。これにより、(特に)の操作が変更されmysql_real_escape_string()
ます。このモードを有効に0x27
すると、0x2727
ではなくに置き換えられる0x5c27
ため、エスケーププロセスでは、以前は存在しなかった(つまり、まだ存在する)脆弱なエンコーディングで有効な文字を作成できないため、サーバーは文字列を無効として拒否します。 。ただし、@ eggyalの回答を参照してください0xbf27
0xbf27
このSQLモードを使用すると発生する可能性がある別の脆弱性については、を(ただし、PDOでは使用できません)。
安全な例
次の例は安全です。
mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
サーバーが期待しているのでutf8
...
mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
クライアントとサーバーが一致するように文字セットを適切に設定しているためです。
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
エミュレートされた準備済みステートメントをオフにしたからです。
$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
文字セットを適切に設定したからです。
$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
MySQLiは常に真の準備済みステートメントを実行するためです。
まとめ
もし、あんたが:
- MySQLの最新バージョン(後期5.1、すべて5.5、5.6など)および PDOのDSN文字セットパラメータ(PHP≥5.3.6)を使用します
または
- 接続エンコーディングに脆弱な文字セットを使用しないでください(
utf8
/ latin1
/ ascii
/などのみを使用します)
または
NO_BACKSLASH_ESCAPES
SQLモードを有効にする
あなたは100%安全です。
そうしないと、PDOの準備済みステートメントを使用している場合でも、脆弱になります...
補遺
PHPの将来のバージョンの準備をエミュレートしないようにデフォルトを変更するパッチにゆっくり取り組んでいます。私が遭遇している問題は、それを行うと多くのテストが壊れることです。1つの問題は、エミュレートされた準備では実行時に構文エラーのみがスローされますが、真の準備では準備時にエラーがスローされることです。そのため、問題が発生する可能性があります(テストが失敗する理由の一部です)。