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:
-
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.0x27a 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,gbkasjis. Vyberemegbkzde.Nyní je velmi důležité poznamenat použití
SET NAMEStady. Tím nastavíte znakovou sadu NA SERVERU . Pokud bychom použili volání funkce C APImysql_set_charset(), byli bychom v pořádku (na vydáních MySQL od roku 2006). Ale více o tom, proč za minutu... -
Úžitková zátěž
Užitná zátěž, kterou pro tuto injekci použijeme, začíná sekvencí bajtů
0xbf27. Vgbk, to je neplatný vícebajtový znak; vlatin1, je to řetězec¿'. Všimněte si, že vlatin1agbk,0x27sá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 s0xbf5c27, což je vgbkje sekvence dvou znaků:0xbf5cnásleduje0x27. Nebo jinými slovy platný znak následovaný neescapovaným'. Nepoužíváme všakaddslashes(). Takže k dalšímu kroku... -
mysql_real_escape_string()
Volání C API pro
mysql_real_escape_string()se liší odaddslashes()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ámelatin1za spojení, protože jsme to nikdy neřekli jinak. Řekli jsme to serveru používámegbk, ale klient stále si myslí, že je tolatin1.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$varvgbkznakovou sadu, viděli bychom:縗' OR 1=1 /*
Což je přesně co útok vyžaduje.
-
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() ...