簡単に言えば、はい、はい、回避する方法があります。mysql_real_escape_string()
。
非常にあいまいなエッジケースの場合!!!
長い答えはそれほど簡単ではありません。ここに示されている攻撃に基づいています。
攻撃
それでは、攻撃を示すことから始めましょう...
mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
特定の状況では、それは複数の行を返します。ここで何が起こっているのかを分析してみましょう:
文字セットの選択
mysql_query('SET NAMES gbk');
仕事へのこの攻撃のために、我々は、サーバーの両方のエンコードへの接続に期待していることエンコーディングを必要とする'
ASCII、すなわちのように0x27
してその最後のバイトASCIIであるいくつかの文字持っている\
すなわち0x5c
。結局のところ、デフォルトでのMySQL 5.6でサポートされている5つのなどのエンコーディングがありますbig5
、cp932
、gb2312
、gbk
とsjis
。gbk
ここで選択します。
ここで、SET NAMES
ここでの使用に注意することが非常に重要です。これにより、サーバー上の文字セットが設定されます。C API関数への呼び出しを使用した場合mysql_set_charset()
、問題ありません(2006年以降のMySQLリリースでは)。しかし、その理由についての詳細...
ペイロード
この注入に使用するペイロードは、バイトシーケンスから始まります0xbf27
。ではgbk
、これは無効なマルチバイト文字です。ではlatin1
、文字列¿'
です。latin1
と gbk
で0x27
は、それ自体がリテラル'
文字であることに注意してください。
呼び出しaddslashes()
た場合、文字の前にASCII \
ieを挿入するため、このペイロードを選択しました。我々が羽目になるだろうので、その中には、2つの文字列です:続きます。または、言い換えると、エスケープされていない有効な文字が続きます。ただし、は使用していません。次のステップに進みます...0x5c
'
0xbf5c27
gbk
0xbf5c
0x27
'
addslashes()
mysql_real_escape_string()
への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
おめでとうございます。あなたは単にmysql_real_escape_string()
... を使用してプログラムを攻撃しました。
悪い人
ひどくなる。MySQLではPDO
デフォルトで準備済みステートメントをエミュレートします。つまり、クライアント側では、基本的にmysql_real_escape_string()
(Cライブラリ内で)sprintfを実行します。つまり、次の結果は正常に注入されます。
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
ここで、エミュレートされた準備済みステートメントを無効にすることでこれを防ぐことができることに注意してください。
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
これは通常、真の準備済みステートメントになります(つまり、データはクエリとは別のパケットで送信されます)。ただし、PDOはMySQLがネイティブで準備できないステートメントのエミュレートに静かにフォールバックすることに注意してください。これはマニュアルにリストされている可能性がありますが、適切なサーバーバージョンを選択するように注意してください)。
ぶさいく
最初に、のmysql_set_charset('gbk')
代わりに使用していれば、これらすべてを防ぐことができたと述べましたSET NAMES gbk
。そして、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パラメータとして公開されています。
救いの恵み
最初に述べたように、この攻撃が機能するには、脆弱な文字セットを使用してデータベース接続をエンコードする必要があります。 utf8mb4
は脆弱ではなく、すべての Unicode文字をサポートできます。そのため、代わりにそれを使用することを選択できますが、MySQL 5.5.3以降でのみ使用可能です。もう1つの方法はですutf8
。これも脆弱ではなく、Unicode Basic Multilingual Plane全体をサポートできます。
または、NO_BACKSLASH_ESCAPES
SQLモードを有効にすることもできます。これにより、(特に)の操作が変更されmysql_real_escape_string()
ます。このモードを有効に0x27
すると、0x2727
ではなくに置き換えられる0x5c27
ため、エスケーププロセスでは、以前は存在しなかった(つまり、まだ存在する)脆弱なエンコーディングで有効な文字を作成できないため、サーバーは文字列を無効として拒否します。 。ただし、このSQLモードの使用により発生する可能性のある別の脆弱性については、@ eggyalの回答を参照してください。0xbf27
0xbf27
安全な例
次の例は安全です。
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などすべて)を使用し、かつ
mysql_set_charset()
/ $mysqli->set_charset()
/ PDOのDSN文字セットパラメータ(PHPで5.3.6以上)
または
- 接続エンコーディングに脆弱な文字セットを使用しないでください(
utf8
/ のみを使用します)latin1
/ ascii
/など)
あなたは100%安全です。
そうでなければ、あなたが使用しているにもかかわらずmysql_real_escape_string()
、あなたは脆弱です ...