Hoe werkt de SQL-injectie van de “Bobby Tables” XKCD-strip?

Gewoon kijken naar:


(Bron: https://xkcd.com/327/)

Wat doet deze SQL:

Robert'); DROP TABLE STUDENTS; --

Ik weet dat zowel 'als --voor opmerkingen zijn, maar wordt het woord dropniet ook becommentarieerd omdat het een onderdeel is van dezelfde regel?


Antwoord 1, autoriteit 100%

Het laat de leerlingentafel vallen.

De originele code in het schoolprogramma ziet er waarschijnlijk ongeveer zo uit

q = "INSERT INTO Students VALUES ('" + FNMName.Text + "', '" + LName.Text + "')";

Dit is de naïeve manier om tekstinvoer aan een zoekopdracht toe te voegen, en is erg slecht, zoals je zult zien.

Na de waarden van de voornaam, middelste naam tekstvak FNMName.Text(dat is Robert'); DROP TABLE STUDENTS; --) en de achternaam tekstvak LName.Text(laten we het Derpernoemen) worden samengevoegd met de rest van de zoekopdracht, het resultaat is nu eigenlijk twee zoekopdrachtengescheiden door de statement-terminator(puntkomma). De tweede zoekopdracht is geïnjecteerdin de eerste. Wanneer de code deze query uitvoert op de database, ziet het er als volgt uit

INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --', 'Derper')

wat in gewoon Engels ruwweg vertaald wordt naar de twee zoekopdrachten:

Voeg een nieuw record toe aan de tabel Studenten met de naam ‘Robert’

en

Verwijder de tabel Studenten

Alles voorbij de tweede zoekopdracht is gemarkeerd als commentaar: --', 'Derper')

De 'in de naam van de leerling is geen commentaar, het is de afsluitende tekenreeksscheidingsteken. Aangezien de naam van de student een tekenreeks is, is deze syntactisch nodig om de hypothetische vraag te voltooien. Injectieaanvallen werken alleen wanneer de SQL-query die ze injecteren resulteert in geldige SQL.

opnieuwbewerkt volgens dan04‘s scherpzinnige opmerking


Antwoord 2, autoriteit 55%

Stel dat de naam werd gebruikt in een variabele, $Name.

U voert vervolgens deze queryuit:

INSERT INTO Students VALUES ( '$Name' )

De code plaatst per ongeluk alles wat de gebruiker heeft opgegeven als variabele.

U wilde dat de SQLis:

INSERT INTO Students VALUES (‘Robert Tables` )

Maar een slimme gebruiker kan leveren wat hij wil:

INVOER IN WAARDEN van studenten (‘Robert'); DROP TABEL Studenten; --‘ )

Wat je krijgt is:

INSERT INTO Students VALUES ( 'Robert' );  DROP TABLE STUDENTS; --' )

De --becommentarieert alleen de rest van de regel.


Antwoord 3, autoriteit 14%

Zoals iedereen al heeft opgemerkt, sluit de ');de oorspronkelijke instructie en dan volgt een tweede instructie. De meeste frameworks, inclusief talen als PHP, hebben inmiddels standaard beveiligingsinstellingen die niet meerdere instructies in één SQL-string toestaan. In PHP kun je bijvoorbeeld alleen meerdere instructies in één SQL-string uitvoeren door de functie mysqli_multi_queryte gebruiken.

U kunt echter een bestaande SQL-instructie manipuleren via SQL-injectie zonder dat u een tweede instructie hoeft toe te voegen. Stel dat u een inlogsysteem heeft dat een gebruikersnaam en een wachtwoord controleert met deze simpele selectie:

$query="SELECT * FROM users WHERE username='" . $_REQUEST['user'] . "' and (password='".$_REQUEST['pass']."')";
$result=mysql_query($query);

Als u peteropgeeft als gebruikersnaam en secretals wachtwoord, ziet de resulterende SQL-string er als volgt uit:

SELECT * FROM users WHERE username='peter' and (password='secret')

Alles is in orde. Stel je nu voor dat je deze string opgeeft als het wachtwoord:

' OR '1'='1

De resulterende SQL-string zou dan deze zijn:

SELECT * FROM users WHERE username='peter' and (password='' OR '1'='1')

Zo kun je inloggen op elk account zonder het wachtwoord te kennen. U hoeft dus niet twee instructies te kunnen gebruiken om SQL-injectie te gebruiken, hoewel u meer destructieve dingen kunt doen als u meerdere instructies kunt opgeven.


Antwoord 4, autoriteit 6%

Nee, 'is geen commentaar in SQL, maar een scheidingsteken.

Moeder nam aan dat de databaseprogrammeur een verzoek had gedaan dat er als volgt uitzag:

INSERT INTO 'students' ('first_name', 'last_name') VALUES ('$firstName', '$lastName');

(bijvoorbeeld) om de nieuwe leerling toe te voegen, waarbij de inhoud van de variabele $xxxrechtstreeks uit een HTML-formulier is gehaald, zonder het formaat te controleren of speciale tekens te escapen.

Dus als $firstNameRobert'); DROP TABLE students; --het databaseprogramma zal het volgende verzoek rechtstreeks op de DB uitvoeren:

INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD');

dwz. het zal de insert-instructie vroegtijdig beëindigen, de kwaadaardige code die de cracker wil uitvoeren uitvoeren en vervolgens commentaar geven op de rest van de code die er is.

Mmm, ik ben te traag, ik zie al 8 antwoorden voor de mijne in de oranje band… 🙂 Een populair onderwerp, zo lijkt het.


Antwoord 5, autoriteit 3%

TL;DR

-- De applicatie accepteert invoer, in dit geval 'Nancy', zonder te proberen
-- de invoer opschonen, bijvoorbeeld door speciale tekens te laten ontsnappen
school=> INSERT INTO studenten WAARDEN ('Nancy');
INSERT 0 1
-- SQL-injectie vindt plaats wanneer invoer in een databaseopdracht wordt gemanipuleerd om
-- ervoor zorgen dat de databaseserver willekeurige SQL uitvoert
school=> INSERT INTO studenten WAARDEN ('Robert'); DROP TABLE studenten; --');
INSERT 0 1
DROP TAFEL
-- De studentendossiers zijn nu weg - het had nog erger kunnen zijn!
school=> KIES * VAN studenten;
FOUT: relatie "studenten" bestaat niet
REGEL 1: SELECTEER * VAN studenten;
           ^

Hiermee wordt de studententafel verwijderd (verwijderd).

(Alle codevoorbeelden in dit antwoord zijn uitgevoerd op een PostgreSQL 9.1.2-databaseserver.)

Laten we dit eens proberen met een eenvoudige tabel die alleen het naamveld bevat en een enkele rij toevoegen om duidelijk te maken wat er gebeurt:

school=> MAAK TABEL studenten (naam TEXT PRIMARY KEY);
KENNISGEVING: CREATE TABLE / PRIMARY KEY maakt een impliciete index "students_pkey" voor tabel "students"
MAAK TAFEL
school=> INSERT INTO studenten WAARDEN ('John');
INSERT 0 1

Laten we aannemen dat de toepassing de volgende SQL gebruikt om gegevens in de tabel in te voegen:

PLAATS IN DE WAARDEN van studenten ('foobar');

Vervang foobardoor de echte naam van de leerling. Een normale invoegbewerking ziet er als volgt uit:

-- Invoer: Nancy
school=> INSERT INTO studenten WAARDEN ('Nancy');
INSERT 0 1

Als we de tabel opvragen, krijgen we dit:

school=> KIES * VAN studenten;
 naam
-------
 John
 Nancy
(2 rijen)

Wat gebeurt er als we de naam van Little Bobby Tables in de tabel invoegen?

-- Invoer: Robert'); DROP TAFEL studenten; --
school=> INSERT INTO studenten WAARDEN ('Robert'); DROP TAFEL studenten; --');
INSERT 0 1
DROP TAFEL

De SQL-injectie hier is het resultaat van de naam van de student die de instructie beëindigt en een afzonderlijk DROP TABLE-commando bevat; de twee streepjes aan het einde van de invoer zijn bedoeld als commentaar op eventuele overgebleven code die anders een fout zou veroorzaken. De laatste regel van de uitvoer bevestigt dat de databaseserver de tabel heeft laten vallen.

Het is belangrijk op te merken dat tijdens de bewerking INSERTde toepassing de invoer niet controleert op speciale tekens, en daarom willekeurige invoer toestaat in het SQL-commando. Dit betekent dat een kwaadwillende gebruiker in een veld dat normaal bedoeld is voor gebruikersinvoer, speciale symbolen zoals aanhalingstekens kan invoegen samen met willekeurige SQL-code om het databasesysteem te laten uitvoeren, vandaar SQL injection.

Het resultaat?

school=> KIES * VAN studenten;
FOUT: relatie "studenten" bestaat niet
REGEL 1: SELECTEER * VAN studenten;
           ^

SQL-injectie is het database-equivalent van een externe uitvoering van willekeurige codein een besturingssysteem of toepassing. De potentiële impact van een succesvolle SQL-injectieaanval kan niet worden onderschat: afhankelijk van het databasesysteem en de applicatieconfiguratie kan het door een aanvaller worden gebruikt om gegevensverlies te veroorzaken (zoals in dit geval), ongeautoriseerde toegang tot gegevens te verkrijgen of zelfs uit te voeren willekeurige code op de hostmachine zelf.

Zoals opgemerkt door de XKCD-strip, is een manier om te beschermen tegen SQL-injectie-aanvallen het opschonen van database-invoer, bijvoorbeeld door speciale tekens te laten ontsnappen, zodat ze de onderliggende SQL-opdracht niet kunnen wijzigen en daarom geen uitvoering van willekeurige SQL-code kunnen veroorzaken. Dit kan op applicatieniveau worden gedaan en sommige implementaties van geparametriseerde zoekopdrachten werken door invoer op te schonen.

Het opschonen van invoer op applicatieniveau houdt echter mogelijk niet meer geavanceerde SQL-injectietechnieken tegen. Bijvoorbeeld, er zijn manieren om de mysql_real_escape_stringPHP-functie. Voor extra bescherming ondersteunen veel databasesystemen voorbereide verklaringen. Indien correct geïmplementeerd in de backend, kunnen voorbereide instructies SQL-injectie onmogelijk maken door gegevensinvoer als semantisch gescheiden te behandelen van de rest van de opdracht.


Antwoord 6, autoriteit 3%

Stel dat je naïef een methode voor het maken van leerlingen als volgt hebt geschreven:

void createStudent(String name) {
    database.execute("INSERT INTO students (name) VALUES ('" + name + "')");
}

En iemand typt de naam Robert'); DROP TABLE STUDENTS; --

Wat op de database wordt uitgevoerd, is deze query:

INSERT INTO students (name) VALUES ('Robert'); DROP TABLE STUDENTS --')

De puntkomma beëindigt het insert-commando en begint een ander; de — becommentarieert de rest van de regel. Het DROP TABLE-commando wordt uitgevoerd…

Daarom zijn bindingsparameters een goede zaak.


Antwoord 7, autoriteit 2%

Een enkel aanhalingsteken is het begin en einde van een tekenreeks. Een puntkomma is het einde van een statement. Dus als ze een selectie als deze aan het doen waren:

Select *
From Students
Where (Name = '<NameGetsInsertedHere>')

De SQL zou worden:

Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
--             ^-------------------------------^

Op sommige systemen zou de selecteerst worden uitgevoerd, gevolgd door de drop-instructie! De boodschap is: SLUIT GEEN WAARDEN IN IN UW SQL. Gebruik in plaats daarvan parameters!


Antwoord 8, autoriteit 2%

De ');beëindigt de zoekopdracht, er wordt geen commentaar gestart. Dan laat het de studententabel vallen en becommentarieert de rest van de query die moest worden uitgevoerd.


Antwoord 9, autoriteit 2%

In dit geval is 'geen commentaarteken. Het wordt gebruikt om letterlijke tekenreeksen af te bakenen. De striptekenaar rekent op het idee dat de school in kwestie ergens dynamische sql heeft die er ongeveer zo uitziet:

$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";

Dus nu beëindigt het teken 'de letterlijke tekenreeks voordat de programmeur het verwachtte. Gecombineerd met het teken ;om de instructie te beëindigen, kan een aanvaller nu elke gewenste sql toevoegen (injecteren). De opmerking --aan het einde is om ervoor te zorgen dat eventuele resterende sql in de oorspronkelijke instructie niet verhindert dat de query op de server wordt gecompileerd.

FWIW, ik denk ook dat de strip in kwestie een belangrijk detail verkeerd heeft: als je je database-invoer schoonmaakt, zoals de strip suggereert, doe je het nog steeds mis. In plaats daarvan moet u denken aan het in quarantaine plaatsenvan uw database-invoer, en de juiste manier om dit te doen is via geparametriseerde query’s/voorbereide instructies.


Antwoord 10

De schrijver van de database heeft waarschijnlijk een

sql = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff";
execute(sql);

Als Student_Name de gegeven is, die de selectie doet met de naam “Robert” en vervolgens de tabel laat vallen. Het gedeelte “-” verandert de rest van de opgegeven query in een opmerking.


Antwoord 11

De 'teken in SQL wordt gebruikt voor stringconstanten. In dit geval wordt het gebruikt voor het beëindigen van de reeksconstante en niet voor commentaar.


Antwoord 12

Dit is hoe het werkt:
Laten we stel dat de beheerder op zoek is naar records van de student

Robert'); DROP TABLE STUDENTS; --

Aangezien het admin-account hoge privileges heeft die de tabel uit dit account is verwijderd.

De code om de gebruikersnaam op te halen van aanvraag is

Nu zou de query zoiets zijn (om de studententabel te zoeken)

String query="Select * from student where username='"+student_name+"'";
statement.executeQuery(query); //Rest of the code follows

De resulterende query wordt

Select * from student where username='Robert'); DROP TABLE STUDENTS; --

Aangezien de gebruikersinvoer niet is ontsmet, wordt de bovenstaande query gemanipuleerd in 2 delen

Select * from student where username='Robert'); 
DROP TABLE STUDENTS; --

Het dubbele dashboard (-) geeft commentaar op het resterende deel van de query.

Dit is gevaarlijk omdat het wachtwoordauthenticatie, indien aanwezig

kan nullen

De eerste zal de normale zoekopdracht doen.

De tweede zal de tafelstudent laten vallen als het account voldoende rechten heeft (over het algemeen voert het schoolbeheerdersaccount een dergelijke zoekopdracht uit en heeft het de rechten waarover hierboven is gesproken).


Antwoord 13

U hoeft geen formuliergegevens in te voeren om SQL-injectie uit te voeren.

Niemand heeft hier eerder op gewezen, dus ik kan sommigen van jullie waarschuwen.

Meestal zullen we proberen formulierinvoer te patchen. Maar dit is niet de enige plek waar u kunt worden aangevallen met SQL-injectie. Je kunt heel eenvoudige aanvallen uitvoeren met een URL die gegevens verzendt via een GET-verzoek;
Beschouw het braakliggende voorbeeld:

<a href="/show?id=1">show something</a>

Uw url zou eruit zien
http://yoursite.com/show?id=1

Nu zou iemand zoiets als dit kunnen proberen

http://yoursite.com/show?id=1;TRUNCATE table_name

Probeer table_name te vervangen door de echte tabelnaam. Als hij je tafelnaam goed heeft, zouden ze je tafel leegmaken! (Het is heel gemakkelijk om deze URL bruut te forceren met een eenvoudig script)

Uw zoekopdracht ziet er ongeveer zo uit…

"SELECT * FROM page WHERE id = 4;TRUNCATE page"

Voorbeeld van kwetsbare PHP-code met PDO:

<?php
...
$id = $_GET['id'];
$pdo = new PDO($database_dsn, $database_user, $database_pass);
$query = "SELECT * FROM page WHERE id = {$id}";
$stmt = $pdo->query($query);
$data = $stmt->fetch(); 
/************* You have lost your data!!! :( *************/
...

Oplossing – gebruik PDO prepare() & bindParam() methoden:

<?php
...
$id = $_GET['id'];
$query = 'SELECT * FROM page WHERE id = :idVal';
$stmt = $pdo->prepare($query);
$stmt->bindParam('idVal', $id, PDO::PARAM_INT);
$stmt->execute();
$data = $stmt->fetch();
/************* Your data is safe! :) *************/
...

Other episodes