mysql_real_escape_string() を回避する SQL インジェクション 質問する

mysql_real_escape_string() を回避する SQL インジェクション 質問する

関数を使用した場合でも SQL インジェクションの可能性はありますかmysql_real_escape_string()?

次のサンプル状況を考えてみましょう。SQL は PHP で次のように構築されます。

$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));

$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";

そのようなコードは依然として危険であり、関数を使用してもハッキングされる可能性があると多くの人が言っているのを聞いたことがありますmysql_real_escape_string()。しかし、可能性のあるエクスプロイトは思いつきません。

典型的な注射は次のようになります:

aaa' OR 1=1 --

動作しない。

上記の PHP コードを通過する可能性のあるインジェクションについてご存知ですか?

ベストアンサー1

簡単に答えると、はい、回避する方法があります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");

状況によっては、1 行以上が返されることがあります。ここで何が起こっているのか分析してみましょう。

  1. 文字セットの選択

    mysql_query('SET NAMES gbk');
    

    この攻撃が成功するには、サーバーが接続時に想定しているエンコーディングが'ASCII でエンコードされていること (つまり ) 0x27 、および最終バイトが ASCII である文字が含まれていること (つまり )\が必要0x5cです。MySQL 5.6 では、デフォルトで 、 、 、 の 5 つのエンコーディングがサポートされています。big5ここcp932gb2312gbk選択sjisしますgbk

    ここで、 の使用に注意することが非常に重要です。これは、サーバー上のSET NAMES文字セットを設定します。C API 関数 の呼び出しを使用すれば、問題ありません (2006 年以降の MySQL リリースの場合)。ただし、その理由については後ほど詳しく説明します...mysql_set_charset()

  2. ペイロード

    このインジェクションに使用するペイロードは、バイト シーケンス で始まります0xbf27。 ではgbk、これは無効なマルチバイト文字です。 ではlatin1、これは文字列 です¿'latin1 および gbkでは、0x27それ自体はリテラル'文字であることに注意してください。

    このペイロードを選択したのは、これを呼び出すと、文字の前にaddslashes()ASCII が挿入されるから\です。その結果、 になり、これは2 つの文字シーケンス の後に が続きます。つまり、有効な文字の後にエスケープされていない が続きます。ただし、 は使用しません。それでは、次のステップに進みます...0x5c'0xbf5c27gbk0xbf5c0x27'addslashes()

  3. mysql_real_escape_string()

    への C API 呼び出しは、接続文字セットを認識している点mysql_real_escape_string()で と異なりますaddslashes()。そのため、サーバーが期待する文字セットに対して適切にエスケープを実行できます。ただし、この時点では、クライアントは接続にまだ を使用していると考えています。これは、他の方法を指示していないためです。サーバーにを使用しているlatin1ことを伝えましたが、クライアントは依然としてであると考えています。gbklatin1

    したがって、 の呼び出しによってmysql_real_escape_string()バックスラッシュが挿入され、「エスケープされた」コンテンツに自由にぶら下がっている文字が存在します。実際、文字セットを'見ると、次のようになります。$vargbk

    縗' OR 1=1 /*

    それはまさに攻撃には必要です。

  4. クエリ

    この部分は単なる形式的なものですが、レンダリングされたクエリは次のとおりです。

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

おめでとうございます。... を使用してプログラムへの攻撃に成功しましたmysql_real_escape_string()

悪い人

さらに悪いことに、 MySQL で準備されたステートメントをエミュレートするPDOのがデフォルトです。つまり、クライアント側では基本的に sprintf が実行されます(C ライブラリ内)。つまり、次の処理はインジェクションに成功します。mysql_real_escape_string()

$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.205.0.22そして5.1.11

しかし最悪なのは、 5.3.6PDOまでC APIが公開されていなかったためmysql_set_charset()、以前のバージョンではあらゆるコマンドに対してこの攻撃を防ぐことができなかったことです。DSNパラメータ

救いの恵み

冒頭で述べたように、この攻撃が機能するには、データベース接続が脆弱な文字セットを使用してエンコードされている必要があります。utf8mb4脆弱性はなくすべてのUnicode文字をサポートしているので、代わりにそれを使用することを選択できますが、MySQL 5.5.3以降でのみ利用可能です。代替案としては、utf8これも脆弱性がなく、Unicode全体をサポートできる。基本的な多言語面

あるいは、NO_BACKSLASH_ESCAPESSQL モードは、(他の機能の中でも) の動作を変更しますmysql_real_escape_string()。このモードを有効にすると、はではなく に0x27置き換えられ、したがって、エスケープ処理では、脆弱なエンコーディングで以前に存在しなかった有効な文字を作成できません(つまり、はまだなど)。そのため、サーバーは文字列を無効として拒否します。ただし、0x27270x5c270xbf270xbf27@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 など) PDO の DSN 文字セット パラメータ (PHP ≥ 5.3.6) をmysql_set_charset()使用します。$mysqli->set_charset()

または

  • 接続エンコーディングに脆弱な文字セットを使用しないでください(utf8/ latin1/ ascii/ などのみを使用します)

あなたは100%安全です。

そうでなければ、使用しているにもかかわらずmysql_real_escape_string()脆弱になります...

おすすめ記事