sql >> Database teknologi >  >> RDS >> Mysql

SQL-injektion, der kommer omkring mysql_real_escape_string()

Det korte svar er ja, ja, der er en måde at komme uden om mysql_real_escape_string() .#For meget OBSKURE KANTSAGER!!!

Det lange svar er ikke så let. Det er baseret på et angreb demonstreret her .

Angrebet

Så lad os starte med at vise angrebet...

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 visse omstændigheder vil det returnere mere end 1 række. Lad os dissekere, hvad der foregår her:

  1. Valg af et tegnsæt

    mysql_query('SET NAMES gbk');
    

    For at dette angreb skal fungere, har vi brug for den kodning, som serveren forventer på forbindelsen både for at kode ' som i ASCII, dvs. 0x27 og at have et eller andet tegn, hvis sidste byte er en ASCII \ dvs. 0x5c . Som det viser sig, er der 5 sådanne kodninger understøttet i MySQL 5.6 som standard:big5 , cp932 , gb2312 , gbk og sjis . Vi vælger gbk her.

    Nu er det meget vigtigt at bemærke brugen af ​​SET NAMES her. Dette indstiller tegnsættet PÅ SERVEREN . Hvis vi brugte kaldet til C API-funktionen mysql_set_charset() , ville vi klare os (på MySQL-udgivelser siden 2006). Men mere om hvorfor om et øjeblik...

  2. Nyttelasten

    Den nyttelast, vi skal bruge til denne indsprøjtning, starter med bytesekvensen 0xbf27 . I gbk , det er et ugyldigt multibyte-tegn; i latin1 , det er strengen ¿' . Bemærk, at i latin1 og gbk , 0x27 i sig selv er en bogstavelig ' tegn.

    Vi har valgt denne nyttelast, fordi hvis vi kaldte addslashes() på den ville vi indsætte en ASCII \ dvs. 0x5c , før ' Karakter. Så vi ender med 0xbf5c27 , som i gbk er en sekvens på to tegn:0xbf5c efterfulgt af 0x27 . Eller med andre ord, en gyldig tegn efterfulgt af en unescaped ' . Men vi bruger ikke addslashes() . Så videre til næste trin...

  3. mysql_real_escape_string()

    C API-kaldet til mysql_real_escape_string() adskiller sig fra addslashes() ved at den kender forbindelsestegnsættet. Så den kan udføre escapen korrekt for det tegnsæt, som serveren forventer. Men indtil dette tidspunkt tror klienten, at vi stadig bruger latin1 for forbindelsen, for vi har aldrig fortalt det andet. Vi fortalte serveren det vi bruger gbk , men klienten tror stadig, det er latin1 .

    Derfor kaldet til mysql_real_escape_string() indsætter skråstreget, og vi har en frithængende ' karakter i vores "undslupne" indhold! Faktisk, hvis vi skulle se på $var i gbk tegnsæt, vil vi se:

    縗' OR 1=1 /*

    Hvilket er præcis hvad angrebet kræver.

  4. Forespørgslen

    Denne del er kun en formalitet, men her er den gengivne forespørgsel:

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

Tillykke, du har lige angrebet et program med mysql_real_escape_string() ...

Den dårlige

Det bliver værre. PDO standard til emulering udarbejdet erklæringer med MySQL. Det betyder, at den på klientsiden dybest set laver en sprintf gennem mysql_real_escape_string() (i C-biblioteket), hvilket betyder, at følgende vil resultere i en vellykket 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 er det værd at bemærke, at du kan forhindre dette ved at deaktivere emulerede forberedte udsagn:

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

Dette vil normalt resultere i en ægte forberedt sætning (dvs. dataene sendes i en separat pakke fra forespørgslen). Vær dog opmærksom på, at PDO stille vil faldback til at efterligne udsagn, som MySQL ikke kan forberede indbygget:dem, det kan, er opført i manualen, men pas på at vælge den passende serverversion).

Den Grimme

Jeg sagde i begyndelsen, at vi kunne have forhindret alt dette, hvis vi havde brugt mysql_set_charset('gbk') i stedet for SET NAMES gbk . Og det er sandt, forudsat at du har brugt en MySQL-udgivelse siden 2006.

Hvis du bruger en tidligere MySQL-udgivelse, er der en fejl i mysql_real_escape_string() betød, at ugyldige multibyte-tegn, såsom dem i vores nyttelast, blev behandlet som enkeltbytes til escape-formål selvom klienten var blevet korrekt informeret om forbindelseskodningen og så dette angreb ville stadig lykkes. Fejlen blev rettet i MySQL 4.1.20 , 5.0.22 og 5.1.11 .

Men det værste er den PDO afslørede ikke C API for mysql_set_charset() indtil 5.3.6, så i tidligere versioner kan det ikke forhindre dette angreb for enhver mulig kommando! Den er nu afsløret som en DSN-parameter .

Den frelsende nåde

Som vi sagde i starten, for at dette angreb skal virke, skal databaseforbindelsen være kodet ved hjælp af et sårbart tegnsæt. utf8mb4 er ikke sårbar og alligevel kan understøtte hver Unicode-tegn:så du kunne vælge at bruge det i stedet - men det har kun været tilgængeligt siden MySQL 5.5.3. Et alternativ er utf8 , som heller ikke er sårbar og kan understøtte hele Unicode Basic Multilingual Plane .

Alternativt kan du aktivere NO_BACKSLASH_ESCAPES SQL-tilstand, som (blandt andet) ændrer driften af ​​mysql_real_escape_string() . Med denne tilstand aktiveret, 0x27 vil blive erstattet med 0x2727 i stedet for 0x5c27 og dermed kan undslippeprocessen ikke oprette gyldige tegn i enhver af de sårbare kodninger, hvor de ikke fandtes tidligere (f.eks. 0xbf27 er stadig 0xbf27 osv.) – så serveren vil stadig afvise strengen som ugyldig. Se dog @eggyals svar for en anden sårbarhed, der kan opstå ved brug af denne SQL-tilstand.

Sikkere eksempler

Følgende eksempler er sikre:

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

Fordi serveren forventer 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");

Fordi vi har indstillet tegnsættet korrekt, så klienten og serveren matcher.

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

Fordi vi har slået emulerede forberedte udsagn fra.

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

Fordi vi har indstillet tegnsættet 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();

Fordi MySQLi laver ægte forberedte udsagn hele tiden.

Afslutning

Hvis du:

  • Brug moderne versioner af MySQL (sen 5.1, alle 5.5, 5.6 osv.) OG mysql_set_charset() / $mysqli->set_charset() / PDO's DSN-tegnsætparameter (i PHP ≥ 5.3.6)

ELLER

  • Brug ikke et sårbart tegnsæt til forbindelseskodning (du bruger kun utf8 / latin1 / ascii / osv.)

Du er 100 % sikker.

Ellers er du sårbar selvom du bruger mysql_real_escape_string() ...



  1. Hvordan vælger man fra to tabeller i MySQL, selvom ikke alle rækker i den ene tabel har korrespondenter i den anden?

  2. Sådan bruges STRCMP() til at sammenligne 2 strenge i MySQL

  3. Brug af SQL til at bestemme ordtællingsstatistikker for et tekstfelt

  4. Er det muligt at bruge brugerdefinerede aggregater (clr) med vinduesfunktioner (over)?