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:
-
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
ogsjis
. Vi vælgergbk
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-funktionenmysql_set_charset()
, ville vi klare os (på MySQL-udgivelser siden 2006). Men mere om hvorfor om et øjeblik... -
Nyttelasten
Den nyttelast, vi skal bruge til denne indsprøjtning, starter med bytesekvensen
0xbf27
. Igbk
, det er et ugyldigt multibyte-tegn; ilatin1
, det er strengen¿'
. Bemærk, at ilatin1
oggbk
,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 med0xbf5c27
, som igbk
er en sekvens på to tegn:0xbf5c
efterfulgt af0x27
. Eller med andre ord, en gyldig tegn efterfulgt af en unescaped'
. Men vi bruger ikkeaddslashes()
. Så videre til næste trin... -
mysql_real_escape_string()
C API-kaldet til
mysql_real_escape_string()
adskiller sig fraaddslashes()
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 brugerlatin1
for forbindelsen, for vi har aldrig fortalt det andet. Vi fortalte serveren det vi brugergbk
, men klienten tror stadig, det erlatin1
.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
igbk
tegnsæt, vil vi se:縗' OR 1=1 /*
Hvilket er præcis hvad angrebet kræver.
-
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()
...