sql >> Databasteknik >  >> RDS >> Mysql

SQL-injektion som tar sig runt mysql_real_escape_string()

Det korta svaret är ja, ja det finns ett sätt att komma runt mysql_real_escape_string() .#För mycket OBSKURA KANTFODRAL!!!

Det långa svaret är inte så lätt. Den är baserad på en attack demonstrerad här .

Attacken

Så låt oss börja med att visa attacken...

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

Under vissa omständigheter kommer det att returnera mer än 1 rad. Låt oss dissekera vad som händer här:

  1. Välja en teckenuppsättning

    mysql_query('SET NAMES gbk');
    

    För att denna attack ska fungera behöver vi kodningen som servern förväntar sig på anslutningen både för att koda ' som i ASCII, dvs 0x27 och att ha något tecken vars sista byte är en ASCII \ dvs 0x5c . Som det visar sig finns det 5 sådana kodningar som stöds i MySQL 5.6 som standard:big5 , cp932 , gb2312 , gbk och sjis . Vi väljer gbk här.

    Nu är det mycket viktigt att notera användningen av SET NAMES här. Detta ställer in teckenuppsättningen PÅ SERVERN . Om vi ​​använde anropet till C API-funktionen mysql_set_charset() , vi skulle klara oss (på MySQL-versioner sedan 2006). Men mer om varför om en minut...

  2. Lasten

    Nyttolasten vi ska använda för denna injektion börjar med bytesekvensen 0xbf27 . I gbk , det är ett ogiltigt multibyte-tecken; i latin1 , det är strängen ¿' . Observera att i latin1 och gbk , 0x27 i sig är en bokstavlig ' tecken.

    Vi har valt denna nyttolast eftersom, om vi anropade addslashes() på den skulle vi infoga en ASCII \ dvs 0x5c , före ' karaktär. Så vi skulle avsluta med 0xbf5c27 , som i gbk är en sekvens med två tecken:0xbf5c följt av 0x27 . Eller med andra ord, en giltig tecken följt av en okodad ' . Men vi använder inte addslashes() . Så vidare till nästa steg...

  3. mysql_real_escape_string()

    C API-anropet till mysql_real_escape_string() skiljer sig från addslashes() genom att den känner till anslutningsteckenuppsättningen. Så den kan utföra escapen korrekt för den teckenuppsättning som servern förväntar sig. Men fram till denna punkt tror klienten att vi fortfarande använder latin1 för anslutningen, eftersom vi aldrig berättade något annat. Vi berättade för servern vi använder gbk , men klienten tror fortfarande att det är latin1 .

    Därför anropet till mysql_real_escape_string() infogar snedstrecket, och vi har en fritt hängande ' karaktär i vårt "rymda" innehåll! Faktum är att om vi skulle titta på $var i gbk teckenuppsättning, skulle vi se:

    縗' OR 1=1 /*

    Vilket är exakt vad attacken kräver.

  4. Frågan

    Den här delen är bara en formalitet, men här är den renderade frågan:

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

Grattis, du har precis attackerat ett program med mysql_real_escape_string() ...

Den dåliga

Det blir värre. PDO standard till emulering förberedda uttalanden med MySQL. Det betyder att på klientsidan gör den i princip en sprintf genom mysql_real_escape_string() (i C-biblioteket), vilket betyder att följande kommer att resultera i en lyckad injektion:

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

Nu är det värt att notera att du kan förhindra detta genom att inaktivera emulerade förberedda uttalanden:

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

Detta kommer vanligtvis resultera i en äkta förberedd sats (dvs. data skickas över i ett separat paket från frågan). Var dock medveten om att PDO tyst kommer att fallback till att emulera uttalanden som MySQL inte kan förbereda inbyggt:de som det kan är listade i manualen, men var försiktig med att välja lämplig serverversion).

Den fula

Jag sa redan i början att vi kunde ha förhindrat allt detta om vi hade använt mysql_set_charset('gbk') istället för SET NAMES gbk . Och det är sant förutsatt att du använder en MySQL-version sedan 2006.

Om du använder en tidigare MySQL-version, då är en bugg i mysql_real_escape_string() innebar att ogiltiga multibyte-tecken som de i vår nyttolast behandlades som enkla byte i flyktsyften även om klienten hade blivit korrekt informerad om anslutningskodningen och så skulle denna attack ändå lyckas. Felet fixades i MySQL 4.1.20 , 5.0.22 och 5.1.11 .

Men det värsta är att PDO exponerade inte C API för mysql_set_charset() fram till 5.3.6, så i tidigare versioner kan det inte förhindra denna attack för alla möjliga kommandon! Den är nu exponerad som en DSN-parameter .

The Saving Grace

Som vi sa i början, för att denna attack ska fungera måste databasanslutningen kodas med en sårbar teckenuppsättning. utf8mb4 är inte sårbar och ändå kan stödja alla Unicode-tecken:så du kan välja att använda det istället – men det har bara varit tillgängligt sedan MySQL 5.5.3. Ett alternativ är utf8 , vilket inte heller är inte sårbart och kan stödja hela Unicode Basic Multilingual Plane .

Alternativt kan du aktivera NO_BACKSLASH_ESCAPES SQL-läge, som (bland annat) ändrar driften av mysql_real_escape_string() . Med detta läge aktiverat, 0x27 kommer att ersättas med 0x2727 istället för 0x5c27 och därför kan inte flyktprocessen skapa giltiga tecken i någon av de sårbara kodningarna där de inte fanns tidigare (t.ex. 0xbf27 är fortfarande 0xbf27 etc.)—så att servern fortfarande avvisar strängen som ogiltig. Se dock @eggyals svar för en annan sårbarhet som kan uppstå vid användning av detta SQL-läge.

Säkra exempel

Följande exempel är säkra:

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

Eftersom servern förväntar sig 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");

Eftersom vi har ställt in teckenuppsättningen korrekt så att klienten och servern matchar.

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

Eftersom vi har stängt av emulerade förberedda uttalanden.

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

Eftersom vi har ställt in teckenuppsättningen korrekt.

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

Eftersom MySQLi gör sanna förberedda uttalanden hela tiden.

Avsluta

Om du:

  • Använd moderna versioner av MySQL (sen 5.1, alla 5.5, 5.6, etc) OCH mysql_set_charset() / $mysqli->set_charset() / PDO:s DSN-teckenuppsättningsparameter (i PHP ≥ 5.3.6)

ELLER

  • Använd inte en sårbar teckenuppsättning för anslutningskodning (du använder bara utf8 / latin1 / ascii / etc)

Du är 100 % säker.

Annars är du sårbar även om du använder mysql_real_escape_string() ...



  1. MySQL-tidszon ändras?

  2. Använder Oracle kortslutningsutvärdering?

  3. OPENROWSET accepterar inte variabler för sina argument (SQL Server)

  4. Anslut till SQL Server med Windows-autentisering från en Linux-maskin via JDBC