sql >> Database >  >> RDS >> Mysql

SQL-injectie die mysql_real_escape_string() omzeilt

Het korte antwoord is ja, ja, er is een manier om mysql_real_escape_string() te omzeilen .#Voor zeer OBSCURE EDGE GEVALLEN!!!

Het lange antwoord is niet zo eenvoudig. Het is gebaseerd op een aanval die hier wordt gedemonstreerd .

De aanval

Dus laten we beginnen met het tonen van de aanval...

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

In bepaalde omstandigheden levert dat meer dan 1 rij op. Laten we ontleden wat hier aan de hand is:

  1. Een tekenset selecteren

    mysql_query('SET NAMES gbk');
    

    Om deze aanval te laten werken, hebben we de codering nodig die de server op de verbinding verwacht om beide ' te coderen. zoals in ASCII, d.w.z. 0x27 en om een ​​karakter te hebben waarvan de laatste byte een ASCII is \ bijv. 0x5c . Het blijkt dat er standaard 5 van dergelijke coderingen worden ondersteund in MySQL 5.6:big5 , cp932 , gb2312 , gbk en sjis . We selecteren gbk hier.

    Nu is het erg belangrijk om te letten op het gebruik van SET NAMES hier. Dit zet de tekenset OP DE SERVER . Als we de aanroep van de C API-functie mysql_set_charset() , zouden we in orde zijn (op MySQL-releases sinds 2006). Maar meer over waarom zo...

  2. De lading

    De payload die we voor deze injectie gaan gebruiken, begint met de bytereeks 0xbf27 . In gbk , dat is een ongeldig multibyte-teken; in latin1 , het is de string ¿' . Merk op dat in latin1 en gbk , 0x27 op zichzelf is een letterlijke ' karakter.

    We hebben deze payload gekozen omdat, als we addslashes() aanroepen, erop zouden we een ASCII \ . invoegen bijv. 0x5c , voor de ' karakter. Dus we zouden eindigen met 0xbf5c27 , die in gbk is een reeks van twee tekens:0xbf5c gevolgd door 0x27 . Of met andere woorden, een geldige teken gevolgd door een unescaped ' . Maar we gebruiken geen addslashes() . Dus op naar de volgende stap...

  3. mysql_real_escape_string()

    De C API-aanroep naar mysql_real_escape_string() verschilt van addslashes() in die zin dat het de verbindingstekenset kent. Dus het kan de escape goed uitvoeren voor de tekenset die de server verwacht. Tot nu toe denkt de klant echter dat we nog steeds latin1 . gebruiken voor de connectie, want we hebben het nooit anders verteld. We hebben het de server wel verteld we gebruiken gbk , maar de klant denkt nog steeds dat het latin1 is .

    Daarom is de aanroep naar mysql_real_escape_string() voegt de backslash in, en we hebben een vrijhangende ' karakter in onze "ontsnapte" inhoud! Sterker nog, als we zouden kijken naar $var in de gbk tekenset, we zouden zien:

    縗' OR 1=1 /*

    Dat is precies wat de aanval vereist.

  4. De zoekopdracht

    Dit deel is slechts een formaliteit, maar hier is de weergegeven vraag:

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

Gefeliciteerd, je hebt zojuist een programma met succes aangevallen met mysql_real_escape_string() ...

Het Slechte

Het wordt erger. PDO standaard ingesteld op emuleren verklaringen voorbereid met MySQL. Dat betekent dat het aan de clientzijde in feite een sprintf doet via mysql_real_escape_string() (in de C-bibliotheek), wat betekent dat het volgende zal resulteren in een succesvolle injectie:

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

Nu is het vermeldenswaard dat u dit kunt voorkomen door geëmuleerde voorbereide verklaringen uit te schakelen:

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

Dit zal meestal resulteren in een echte voorbereide verklaring (d.w.z. de gegevens worden verzonden in een apart pakket van de query). Houd er echter rekening mee dat PDO stilletjes terugvalt om verklaringen na te bootsen die MySQL niet native kan voorbereiden:degenen die het wel kan zijn vermeld in de handleiding, maar let op om de juiste serverversie te selecteren).

De lelijke

Ik zei helemaal aan het begin dat we dit allemaal hadden kunnen voorkomen als we mysql_set_charset('gbk') hadden gebruikt in plaats van SET NAMES gbk . En dat is waar, op voorwaarde dat u sinds 2006 een MySQL-release gebruikt.

Als je een eerdere MySQL-release gebruikt, dan is een bug in mysql_real_escape_string() betekende dat ongeldige multibyte-tekens zoals die in onze payload werden behandeld als enkele bytes voor ontsnappingsdoeleinden zelfs als de klant correct was geïnformeerd over de verbindingscodering en dus zou deze aanval toch slagen. De bug is opgelost in MySQL 4.1.20 , 5.0.22 en 5.1.11 .

Maar het ergste is dat PDO heeft de C API voor mysql_set_charset() niet vrijgegeven tot 5.3.6, dus in eerdere versies kan het niet voorkom deze aanval voor elk mogelijk commando! Het wordt nu weergegeven als een DSN-parameter .

De reddende genade

Zoals we aan het begin al zeiden, moet deze aanval alleen werken als de databaseverbinding is gecodeerd met een kwetsbare tekenset. utf8mb4 is niet kwetsbaar en toch kan elke . ondersteunen Unicode-teken:dus je zou ervoor kunnen kiezen om dat in plaats daarvan te gebruiken, maar het is pas beschikbaar sinds MySQL 5.5.3. Een alternatief is utf8 , wat ook niet kwetsbaar . is en kan het hele Unicode Basic Multilingual Plane ondersteunen .

Als alternatief kunt u de NO_BACKSLASH_ESCAPES inschakelen SQL-modus, die (onder andere) de werking van mysql_real_escape_string() wijzigt . Met deze modus ingeschakeld, 0x27 wordt vervangen door 0x2727 in plaats van 0x5c27 en dus kan het ontsnappingsproces niet maak geldige tekens in een van de kwetsbare coderingen waar ze voorheen niet bestonden (d.w.z. 0xbf27 is nog steeds 0xbf27 etc.)—zodat de server de string nog steeds als ongeldig zal afwijzen. Zie echter @eggyal's antwoord voor een andere kwetsbaarheid die kan ontstaan ​​door het gebruik van deze SQL-modus.

Veilige voorbeelden

De volgende voorbeelden zijn veilig:

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

Omdat de server utf8 . verwacht ...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Omdat we de tekenset zo hebben ingesteld dat de client en de server overeenkomen.

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

Omdat we geëmuleerde voorbereide verklaringen hebben uitgeschakeld.

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

Omdat we de tekenset goed hebben ingesteld.

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

Omdat MySQLi de hele tijd waarheidsgetrouwe uitspraken doet.

Afronden

Als u:

  • Gebruik moderne versies van MySQL (eind 5.1, alle 5.5, 5.6, enz.) EN mysql_set_charset() / $mysqli->set_charset() / PDO's DSN-tekensetparameter (in PHP ≥ 5.3.6)

OF

  • Gebruik geen kwetsbare tekenset voor verbindingscodering (u gebruikt alleen utf8 / latin1 / ascii / enz.)

Je bent 100% veilig.

Anders ben je kwetsbaar ook al gebruik je mysql_real_escape_string() ...



  1. Geen SPU meer

  2. ERROR 1148:Het gebruikte commando is niet toegestaan ​​met deze MySQL-versie

  3. Hoe databasemetagegevens te verkrijgen

  4. Schatting van deelname aan SQL Server met behulp van grove uitlijning van histogram