簡単な答えははい、はい、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
。実は、MySQL5.6ではデフォルトで次の5つのエンコーディングがサポートされています。big5
、cp932
、gb2312
、gbk
およびsjis
。gbk
を選択します ここ。ここで、
SET NAMES
の使用に注意することが非常に重要です。 ここ。これにより、文字セットがサーバー上に設定されます。 。 CAPI関数mysql_set_charset()
の呼び出しを使用した場合 、問題ありません(2006年以降のMySQLリリース)。しかし、その理由については1分で詳しく説明します... -
ペイロード
このインジェクションに使用するペイロードは、バイトシーケンス
0xbf27
で始まります。 。gbk
で 、これは無効なマルチバイト文字です。latin1
で 、それは文字列¿'
です 。latin1
では注意してください およびgbk
、0x27
それ自体がリテラルの'
キャラクター。addslashes()
を呼び出した場合、このペイロードを選択しました その上に、ASCII\
を挿入します つまり、0x5c
、'
の前 キャラクター。したがって、0xbf5c27
になります。 、これはgbk
2文字のシーケンスです:0xbf5c
続いて0x27
。つまり、有効 文字の後にエスケープされていない'
。ただし、addslashes()
は使用していません 。次のステップに進みます... -
mysql_real_escape_string()
mysql_real_escape_string()
へのCAPI呼び出しaddslashes()
とは異なります 接続文字セットを知っているという点で。そのため、サーバーが期待している文字セットに対して適切にエスケープを実行できます。ただし、この時点まで、クライアントはまだlatin1
を使用していると考えています。 私たちが他の方法でそれを言ったことはなかったので、接続のために。 サーバーに伝えましたgbk
を使用しています 、ただし client まだlatin1
だと思っています 。したがって、
mysql_real_escape_string()
の呼び出し バックスラッシュを挿入すると、自由にぶら下がっている'
「エスケープされた」コンテンツのキャラクター!実際、$var
を見るとgbk
で 文字セット、次のように表示されます:縗' OR 1=1 /*
正確には何 攻撃には必要です。
-
クエリ
この部分は単なる形式ですが、レンダリングされたクエリは次のとおりです:
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
おめでとうございます。mysql_real_escape_string()
を使用してプログラムを攻撃することに成功しました。 ...
悪い
ひどくなる。 PDO
デフォルトはエミュレートです MySQLでプリペアドステートメント。つまり、クライアント側では、基本的にmysql_real_escape_string()
を介してsprintfを実行します。 (Cライブラリ内)。これは、次の結果、注入が成功することを意味します。
$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
しかし、最悪の部分はそのPDO
mysql_set_charset()
のCAPIを公開しませんでした 5.3.6までなので、以前のバージョンではできません 考えられるすべてのコマンドに対してこの攻撃を防止してください!これは、として公開されています。 DSNパラメーター
。
救いの恵み
最初に述べたように、この攻撃が機能するには、脆弱な文字セットを使用してデータベース接続をエンコードする必要があります。 utf8mb4
脆弱ではありません それでもすべてをサポートできます Unicode文字:代わりにそれを使用することを選択できますが、MySQL5.5.3以降でのみ使用可能です。別の方法は、 utf8
、これも脆弱ではありません Unicode全体をサポートできます
または、 NO_BACKSLASH_ESCAPES
を有効にすることもできます。
SQLモード。これは(とりわけ)mysql_real_escape_string()
の動作を変更します。 。このモードを有効にすると、0x27
0x2727
に置き換えられます 0x5c27
ではなく したがって、エスケーププロセスはできません 以前は存在しなかった脆弱なエンコーディングのいずれかで有効な文字を作成します(つまり、0xbf27
まだ0xbf27
など)-サーバーは引き続き文字列を無効として拒否します。ただし、@eggyalの回答
をご覧ください。 このSQLモードの使用から発生する可能性のある別の脆弱性。
安全な例
次の例は安全です:
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()
を使用していても、脆弱になります。 ...