sql >> Databáze >  >> RDS >> Mysql

Injekce SQL, která obchází mysql_real_escape_string()

Krátká odpověď je ano, ano, existuje způsob, jak obejít mysql_real_escape_string() .#Pro velmi NEJASNÉ PŘÍPADY EDGE!!!

Dlouhá odpověď není tak snadná. Je založeno na útoku demonstrovaném zde .

Útok

Začněme tedy ukázkou útoku...

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");

Za určitých okolností to vrátí více než 1 řádek. Pojďme si rozebrat, co se tady děje:

  1. Výběr znakové sady

    mysql_query('SET NAMES gbk');
    

    Aby tento útok fungoval, potřebujeme kódování, které server očekává u připojení, aby zakódovalo ' jako v ASCII, tj. 0x27 a mít nějaký znak, jehož konečný bajt je ASCII \ tj. 0x5c . Jak se ukázalo, v MySQL 5.6 je standardně podporováno 5 takových kódování:big5 , cp932 , gb2312 , gbk a sjis . Vybereme gbk zde.

    Nyní je velmi důležité poznamenat použití SET NAMES tady. Tím nastavíte znakovou sadu NA SERVERU . Pokud bychom použili volání funkce C API mysql_set_charset() , byli bychom v pořádku (na vydáních MySQL od roku 2006). Ale více o tom, proč za minutu...

  2. Úžitková zátěž

    Užitná zátěž, kterou pro tuto injekci použijeme, začíná sekvencí bajtů 0xbf27 . V gbk , to je neplatný vícebajtový znak; v latin1 , je to řetězec ¿' . Všimněte si, že v latin1 a gbk , 0x27 sám o sobě je doslovný ' postava.

    Toto užitečné zatížení jsme zvolili, protože pokud jsme zavolali addslashes() na něj bychom vložili ASCII \ tj. 0x5c , před ' charakter. Takže bychom skončili s 0xbf5c27 , což je v gbk je sekvence dvou znaků:0xbf5c následuje 0x27 . Nebo jinými slovy platný znak následovaný neescapovaným ' . Nepoužíváme však addslashes() . Takže k dalšímu kroku...

  3. mysql_real_escape_string()

    Volání C API pro mysql_real_escape_string() se liší od addslashes() v tom, že zná znakovou sadu připojení. Takže může správně provést escapování pro znakovou sadu, kterou server očekává. Až do tohoto okamžiku si však klient myslí, že stále používáme latin1 za spojení, protože jsme to nikdy neřekli jinak. Řekli jsme to serveru používáme gbk , ale klient stále si myslí, že je to latin1 .

    Proto volání mysql_real_escape_string() vloží zpětné lomítko a máme volné ' postava v našem "uniklém" obsahu! Ve skutečnosti, pokud bychom se měli podívat na $var v gbk znakovou sadu, viděli bychom:

    縗' OR 1=1 /*

    Což je přesně co útok vyžaduje.

  4. Dotaz

    Tato část je pouze formalita, ale zde je vykreslený dotaz:

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

Gratulujeme, právě jste úspěšně zaútočili na program pomocí mysql_real_escape_string() ...

Špatný

Zhoršuje se to. PDO výchozí je emulující připravené příkazy s MySQL. To znamená, že na straně klienta v podstatě provádí sprintf prostřednictvím mysql_real_escape_string() (v knihovně C), což znamená, že následující bude mít za následek úspěšnou injekci:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Nyní stojí za zmínku, že tomu můžete zabránit vypnutím emulovaných připravených příkazů:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

To bude obvykle výsledkem je pravdivý připravený výpis (tj. data jsou odesílána v samostatném paketu z dotazu). Mějte však na paměti, že PDO tiše ustoupí k emulaci příkazů, které MySQL neumí nativně připravit:ty, které umí, jsou uvedené v příručce, ale dejte pozor na výběr vhodné verze serveru).

Ošklivý

Hned na začátku jsem řekl, že bychom tomu všemu mohli zabránit, kdybychom použili mysql_set_charset('gbk') místo SET NAMES gbk . A to platí za předpokladu, že používáte verzi MySQL od roku 2006.

Pokud používáte dřívější verzi MySQL, objeví se chyba v mysql_real_escape_string() znamenalo, že neplatné vícebajtové znaky, jako jsou ty v naší užitečné zátěži, byly pro účely escapování považovány za jednotlivé bajty i když byl klient správně informován o kódování připojení a tak by tento útok stále uspěl. Chyba byla opravena v MySQL 4.1.20 , 5.0.22 a 5.1.11 .

Ale nejhorší na tom je, že PDO nezveřejnil C API pro mysql_set_charset() do 5.3.6, takže v předchozích verzích to nemůže zabránit tomuto útoku u každého možného příkazu! Nyní je vystaven jako Parametr DSN .

The Saving Grace

Jak jsme si řekli na začátku, aby tento útok fungoval, musí být připojení k databázi zakódováno pomocí zranitelné znakové sady. utf8mb4 není zranitelný a přesto může podporovat každého Znak Unicode:takže byste se mohli rozhodnout použít tento znak – ale ten je k dispozici teprve od verze MySQL 5.5.3. Alternativou je utf8 , který také není zranitelný a může podporovat celý Unicode Základní vícejazyčná rovina .

Případně můžete povolit NO_BACKSLASH_ESCAPES Režim SQL, který (mimo jiné) mění činnost mysql_real_escape_string() . Pokud je tento režim povolen, 0x27 bude nahrazeno 0x2727 spíše než 0x5c27 a tudíž proces úniku nemůže vytvořit platné znaky v kterémkoli ze zranitelných kódování, kde dříve neexistovaly (tj. 0xbf27 je stále 0xbf27 atd.) – server tedy řetězec stále odmítne jako neplatný. Podívejte se však na odpověď @eggyal pro jinou zranitelnost, která může nastat při použití tohoto režimu SQL.

Bezpečné příklady

Následující příklady jsou bezpečné:

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");

Protože server očekává 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");

Protože jsme správně nastavili znakovou sadu tak, aby se klient a server shodovali.

$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 /*"));

Protože jsme vypnuli emulované připravené příkazy.

$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 /*"));

Protože jsme správně nastavili znakovou sadu.

$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();

Protože MySQLi neustále dělá pravdivá připravená prohlášení.

Zabalení

Pokud:

  • Používejte moderní verze MySQL (pozdější 5.1, všechny 5.5, 5.6 atd.) A mysql_set_charset() / $mysqli->set_charset() / parametr znakové sady DSN PDO (v PHP ≥ 5.3.6)

NEBO

  • Pro kódování připojení nepoužívejte zranitelnou znakovou sadu (používáte pouze utf8 / latin1 / ascii / atd.)

Jste 100% v bezpečí.

Jinak jste zranitelní, i když používáte mysql_real_escape_string() ...



  1. Oracle Cloud:Vytvoření databáze ATP (Autonomous Transaction Processing).

  2. Chyba při převodu XML ze sloupce CLOB na sloupec XMLType

  3. Jak najít port pro MS SQL Server 2008?

  4. Převeďte float na varchar v SQL Server bez vědecké notace