Jak działa zastrzyk SQL z komiksu „Bobby Tables” XKCD?


1092

Wystarczy spojrzeć na:

Pasek XKCD (Źródło: https://xkcd.com/327/ )

Co robi ten SQL:

Robert'); DROP TABLE STUDENTS; --

Znam oba 'i jestem --do komentowania, ale czy to słowo DROPrównież nie jest komentowane, ponieważ jest częścią tego samego wiersza?


16
Jeśli słuchasz podcastu stosu przepełnienia stosu nr 31 (27 listopada 2008 r.), Faktycznie o tym dyskutują.
EBGreen,

93
W MySQL 'nie służy do komentowania . Nawet gdyby tak było, nie ma przed nim spacji, więc może jedynie zakończyć ciąg znaków, który go poprzedza.
Wyścigi lekkości na orbicie

45
Jeśli chodzi o XKCD, jeśli jest jakieś pytanie dotyczące niektórych komiksów, zawsze możesz przejść do wyjaśnienia XKCD i uzyskać odpowiedź. Istnieje nawet wiki XKCD , która jest bardzo pomocna w przypadku niektórych podstępnych komiksów, takich jak geohashowanie XKCD
Anatoli

13
Uważam, że ten link należy zapisać tutaj: bobby-tables.com
'The

2
beta.companieshouse.gov.uk/company/10542519 to rejestracja dla firmy konsultingowej o nazwie; TABELA DROP „FIRMY”; - LTD
Alex Dupuy

Odpowiedzi:


1115

Upuszcza stół uczniów.

Oryginalny kod w programie szkoły prawdopodobnie wygląda mniej więcej tak

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

Jest to naiwny sposób dodawania tekstu do zapytania, i jak widać , jest bardzo zły .

Po połączeniu wartości z pierwszego imienia, drugiego pola tekstowego FNMName.Text (który jest Robert'); DROP TABLE STUDENTS; --) i ostatniego pola tekstowego LName.Text (nazwijmy to Derper) są połączone z resztą zapytania, wynikiem są teraz właściwie dwa zapytania oddzielone terminator instrukcji (średnik). Drugie zapytanie zostało wprowadzone do pierwszego. Gdy kod wykona to zapytanie względem bazy danych, będzie to wyglądać następująco

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

co w prostym języku angielskim z grubsza przekłada się na dwa zapytania:

Dodaj nowy rekord do tabeli Uczniów o wartości Nazwa „Robert”

i

Usuń tabelę Studentów

Wszystko po drugim zapytaniu jest oznaczone jako komentarz : --', 'Derper')

W 'imieniu ucznia nie jest komentarzem, to ogranicznik ciągu zamykającego . Ponieważ nazwa ucznia jest ciągiem, jest potrzebna składniowo, aby wypełnić hipotetyczne zapytanie. Ataki typu Injection działają tylko wtedy, gdy wprowadzane zapytanie SQL powoduje powstanie prawidłowego kodu SQL .

Edytowane ponownie zgodnie z bystrym komentarzem dan04


3
Mmm, GDZIE z nawiasami wokół argumentów jest raczej niezwykła, ale przynajmniej pozwala uniknąć błędu składniowego ... :-)
PhiLho

60
@PhiLho: Gdyby oryginalne oświadczenie było INSERT, to nawias miałby większy sens. Wyjaśnia to również, dlaczego połączenie z bazą danych nie jest w trybie tylko do odczytu.
dan04,

3
Jak wyjaśnia @ dan04, nawiasy mają większy sens w przypadku INSERT. Myśląc wstecz, i tak SELECTby nie działało, ponieważ wstawka Małych Stół Bobby'ego w tabeli już by upuściła stół.
ypercubeᵀᴹ

10
W rzeczywistości w tym przykładzie pierwsze zapytanie („dodaj nowy rekord ...”) zakończy się niepowodzeniem, ponieważ Studentsoczekuje więcej niż tylko jednej kolumny (oryginalna / poprawna instrukcja zawierała dwie kolumny). To powiedziawszy, obecność drugiej kolumny jest pomocna, aby pokazać, dlaczego komentarz jest wymagany; a ponieważ nie można zmienić nazwiska Bobby'ego, prawdopodobnie najlepiej jest pozostawić „tak jak jest”, mając niewiele więcej niż tę obserwację jako przypis.
eggyal

7
Nazwisko Bobby'ego - a przynajmniej jego matki - to Roberts , zgodnie z wyjaśnieniem XKCD . Nie jestem jednak pewien, czy poprawienie tego poprawiłoby przejrzystość odpowiedzi.
WBT

611

Załóżmy, że nazwa była używana w zmiennej $Name.

Następnie uruchom następujące zapytanie :

INSERT INTO Students VALUES ( '$Name' )

Kod omyłkowo umieszcza wszystko, co użytkownik podał jako zmienną.

Chciałeś, aby SQL był:

WSTAWIĆ DO WARTOŚCI studentów (' Robert Tables')

Ale sprytny użytkownik może dostarczyć wszystko, co chce:

WSTAWIĆ DO WARTOŚCI studentów (' Robert'); DROP TABLE Students; --')

Otrzymujesz:

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

--Komentuje tylko pozostałą część linii.


87
Jest to o wiele lepsze niż najwyższe głosowanie, ponieważ wyjaśnia nawias zamykający.
Tim Büthe,

1
Nawiasem mówiąc, nie ma możliwości, aby dyrektor szkoły w komiksach był świadomy lub XSS, ponieważ tabela ucznia została usunięta, nie może wiedzieć, kto to zrobił.
xryl669

@ xryl669 Dzienniki są bardzo pomocne w sytuacjach takich jak ten ... Czasami wszystkie zapytania są rejestrowane, a czasem inne zarejestrowane informacje mogą pomóc ci wydedukować winowajcę.
inemanja

165

Jak wszyscy już zauważyli, ');zamyka oryginalne oświadczenie, a następnie następuje drugie oświadczenie. Większość frameworków, w tym języki takie jak PHP, ma teraz domyślne ustawienia zabezpieczeń, które nie zezwalają na wiele instrukcji w jednym ciągu SQL. Na przykład w PHP można uruchamiać wiele instrukcji w jednym ciągu SQL za pomocą mysqli_multi_queryfunkcji.

Można jednak manipulować istniejącą instrukcją SQL za pomocą wstrzykiwania SQL bez konieczności dodawania drugiej instrukcji. Załóżmy, że masz system logowania, który sprawdza nazwę użytkownika i hasło za pomocą tego prostego wyboru:

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

Jeśli podasz peterjako nazwę użytkownika i secrethasło, wynikowy ciąg SQL wyglądałby następująco:

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

Wszystko w porządku. Teraz wyobraź sobie, że podajesz ten ciąg jako hasło:

' OR '1'='1

Następnie wynikowy ciąg SQL byłby następujący:

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

Umożliwi to zalogowanie się na dowolne konto bez znajomości hasła. Nie trzeba więc używać dwóch instrukcji, aby użyć wstrzykiwania SQL, chociaż można zrobić bardziej destrukcyjne rzeczy, jeśli można podać wiele instrukcji.


71

Nie, 'nie jest to komentarz w SQL, ale separator.

Mama podejrzewała, że ​​programista bazy danych poprosił o wygląd:

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

(na przykład), aby dodać nowego ucznia, w którym $xxxzawartość zmiennej została pobrana bezpośrednio z formularza HTML, bez sprawdzania formatu ani znaków specjalnych.

Więc jeśli $firstNamezawiera Robert'); DROP TABLE students; --program bazy danych wykona następujące żądanie bezpośrednio na DB:

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

to znaczy. zakończy wcześnie polecenie wstawiania, wykona dowolny złośliwy kod, którego chce cracker, a następnie skomentuje pozostałą część kodu.

Mmm, jestem zbyt wolny, widzę już 8 odpowiedzi przed moją w pomarańczowym zespole ... :-) Wydaje się, że jest to popularny temat.


39

TL; DR

- Aplikacja akceptuje wejście, w tym przypadku „Nancy”, bez próby - zdezynfekować wejście, jak uciekając znaków specjalnych 
szkolnych => INSERT INTO studentów VALUES ( „Nancy” ); WSTAW 0 1
   
  

- SQL Injection występuje, gdy wejście do komendy bazy jest manipulować - przyczyna serwer bazy danych SQL do wykonania dowolna 
szkolne => INSERT INTO studentów VALUES ( „Robert” ); Studenci DROP TABLE ; - '); WSTAW 0 1 TABELA KROPLI
      
  
 

- Dokumenty studenckie zniknęły - mogło być jeszcze gorzej! 
szkoła => WYBIERZ * OD uczniów ; 
BŁĄD :   relacja „studenci” ma nie istnieć   
LINIA 1 : WYBIERZ * OD studentów ; ^   
                      

Spowoduje to upuszczenie (usunięcie) tabeli ucznia.

( Wszystkie przykłady kodu w tej odpowiedzi zostały uruchomione na serwerze bazy danych PostgreSQL 9.1.2. )

Aby wyjaśnić, co się dzieje, spróbujmy tego z prostą tabelą zawierającą tylko pole nazwy i dodaj pojedynczy wiersz:

szkoła => UTWÓRZ uczniów w tabeli ( nazwa TEKST KLUCZ PODSTAWOWY ); 
OGŁOSZENIE : TWORZENIE TABLE / PRIMARY KEY będzie utworzyć niejawny indeksu "students_pkey" na stole "studenci" TWORZENIE TABLE 
szkoły => INSERT INTO studentów VALUES ( 'John' ); WSTAW 0 1             
    
  

Załóżmy, że aplikacja używa następującego kodu SQL do wstawiania danych do tabeli:

WSTAWIĆ DO WARTOŚCI studentów ( „foobar” );  

Zamień foobarna prawdziwe nazwisko ucznia. Normalna operacja wstawiania wyglądałaby następująco:

- Wejście: Nancy 
szkoła => INSERT INTO studentów VALUES ( 'Nancy' ); WSTAW 0 1   
  

Gdy przeszukujemy tabelę, otrzymujemy:

szkoła => WYBIERZ * OD uczniów ;   
 imię
-------
 Jan
 Nancy
( 2 rzędy ) 

Co się stanie, gdy wstawimy nazwę Little Bobby Tables do tabeli?

- Wejście: Robert '); Studenci DROP TABLE; - 
Szkoła => INSERT INTO studentów VALUES ( 'Robert' ); Studenci DROP TABLE ; - '); WSTAW 0 1 TABELA KROPLI      
  
 

Wstrzyknięcie SQL jest wynikiem imienia ucznia kończącego instrukcję i zawierającego osobne DROP TABLEpolecenie; dwa myślniki na końcu danych wejściowych mają na celu skomentowanie pozostałego kodu, który w przeciwnym razie spowodowałby błąd. Ostatni wiersz danych wyjściowych potwierdza, że ​​serwer bazy danych upuścił tabelę.

Ważne jest, aby zauważyć, że podczas INSERToperacji aplikacja nie sprawdza danych wejściowych pod kątem znaków specjalnych, a zatem umożliwia wprowadzanie dowolnych danych wejściowych do polecenia SQL. Oznacza to, że złośliwy użytkownik może wstawić, w polu normalnie przeznaczonym do wprowadzania przez użytkownika, specjalne symbole, takie jak cudzysłowy, wraz z dowolnym kodem SQL, aby spowodować wykonanie go przez system bazy danych, stąd też wstrzyknięcie SQL  .

Wynik?

szkoła => WYBIERZ * OD uczniów ; 
BŁĄD :   relacja „studenci” ma nie istnieć   
LINIA 1 : WYBIERZ * OD studentów ; ^   
                      

Wstrzykiwanie SQL to odpowiednik bazy danych podatności na zdalne wykonanie dowolnego kodu w systemie operacyjnym lub aplikacji. Nie można nie docenić potencjalnego wpływu udanego wstrzyknięcia SQL - w zależności od systemu bazy danych i konfiguracji aplikacji atakujący może go użyć do spowodowania utraty danych (jak w tym przypadku), uzyskania nieautoryzowanego dostępu do danych, a nawet wykonania dowolny kod na maszynie hosta.

Jak zauważono w komiksie XKCD, jednym ze sposobów ochrony przed atakami iniekcyjnymi SQL jest dezynfekcja danych wejściowych bazy danych, na przykład poprzez ucieczkę znaków specjalnych, aby nie mogły modyfikować bazowej komendy SQL, a zatem nie mogły powodować wykonania dowolnego kodu SQL. Jeśli używasz sparametryzowanych zapytań, na przykład za pomocą SqlParameterADO.NET, dane wejściowe zostaną co najmniej automatycznie oczyszczone, aby zabezpieczyć się przed wstrzyknięciem SQL.

Jednak odkażanie danych wejściowych na poziomie aplikacji może nie zatrzymać bardziej zaawansowanych technik wstrzykiwania SQL. Na przykład istnieją sposoby na obejście mysql_real_escape_stringfunkcji PHP . W celu dodatkowej ochrony wiele systemów baz danych obsługuje przygotowane instrukcje . Jeśli poprawnie zaimplementowane w backendie, przygotowane instrukcje mogą uniemożliwić wstrzykiwanie SQL, traktując dane wejściowe jako semantycznie oddzielone od reszty polecenia.


30

Powiedzmy, że naiwnie napisałeś taką metodę tworzenia uczniów:

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

I ktoś wpisuje imię Robert'); DROP TABLE STUDENTS; --

W bazie danych uruchamiane jest to zapytanie:

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

Średnik kończy polecenie wstawiania i uruchamia kolejne; the - komentuje resztę linii. Polecenie DROP TABLE jest wykonywane ...

Dlatego parametry wiązania są dobrą rzeczą.


26

Pojedynczy cytat to początek i koniec łańcucha. Średnik jest końcem instrukcji. Więc jeśli dokonali takiego wyboru:

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

SQL stałby się:

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

W niektórych systemach selecturuchamiany byłby pierwszy, a następnie dropinstrukcja! Komunikat brzmi: NIE WBUDUJ WARTOŚCI W SWOIM SQL. Zamiast tego użyj parametrów!


18

Te ');końce zapytanie, to nie rozpocznie komentarz. Następnie upuszcza tabelę studentów i komentuje resztę zapytania, które miało zostać wykonane.


17

Autor bazy danych prawdopodobnie zrobił to

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

Jeśli podano nazwę ucznia, to dokonuje wyboru o nazwie „Robert”, a następnie upuszcza tabelę. Część „-” zmienia resztę podanego zapytania w komentarz.


To była moja pierwsza myśl, ale pojawia się błąd składniowy z końcowym nawiasem zamykającym, prawda?
PhiLho,

3
Dlatego na końcu jest - wskazujący, że pozostały tekst jest komentarzem i należy go zignorować.

17

W tym przypadku „nie jest znakiem komentarza. Służy do ograniczania literałów łańcuchowych. Artysta komiksowy opiera się na pomyśle, że dana szkoła ma dynamiczny sql gdzieś, który wygląda mniej więcej tak:

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

Teraz znak „kończy literał ciągów, zanim programista się tego spodziewał. W połączeniu z; Aby zakończyć instrukcję, osoba atakująca może teraz dodać dowolną nazwę SQL. - - komentarz na końcu ma na celu upewnienie się, że wszelkie pozostałe pliki SQL w oryginalnej instrukcji nie uniemożliwiają kompilacji zapytania na serwerze.

FWIW, myślę też, że w tym komiksie jest ważny ważny błąd: jeśli myślisz o oczyszczeniu danych wejściowych do bazy danych, jak sugeruje komiks, nadal robisz to źle. Zamiast tego powinieneś pomyśleć o kwarantannie danych wejściowych do bazy danych, a poprawnym sposobem na to są sparametryzowane zapytania.


16

'Znaków w SQL służy do stałych łańcuchowych. W tym przypadku służy do zakończenia stałej łańcucha, a nie do komentarza.


7

Oto jak to działa: Załóżmy, że administrator szuka rekordów ucznia

Robert'); DROP TABLE STUDENTS; --

Ponieważ konto administratora ma wysokie uprawnienia, możliwe jest usunięcie tabeli z tego konta.

Kod do pobrania nazwy użytkownika z żądania to

Teraz zapytanie byłoby mniej więcej takie (aby przeszukać tabelę uczniów)

String query="Select * from student where username='"+student_name+"'";

statement.executeQuery(query); //Rest of the code follows

Wynikowe zapytanie staje się

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

Ponieważ dane wejściowe użytkownika nie są dezynfekowane, powyższe zapytanie zostało zmanipulowane na 2 części

Select * from student where username='Robert'); 

DROP TABLE STUDENTS; --

Podwójny myślnik (-) po prostu skomentuje pozostałą część zapytania.

Jest to niebezpieczne, ponieważ może unieważnić uwierzytelnianie hasła, jeśli jest obecne

Pierwszy wykona normalne wyszukiwanie.

Drugi zrzuci ucznia z tabeli, jeśli konto ma wystarczające uprawnienia (ogólnie konto szkolnego administratora uruchomi takie zapytanie i będzie miało wyżej wymienione uprawnienia).


SELECT* FROM sutdents ...- zapomniałeś „s”. Właśnie to upuszczasz. DROP TABLE STUDENTS;
DevWL

4

Nie trzeba wprowadzać danych formularza, aby wykonać iniekcję SQL.

Nikt wcześniej tego nie zauważył, więc mogę zaalarmować niektórych z was.

Przeważnie spróbujemy załatać dane wejściowe formularzy. Ale to nie jedyne miejsce, w którym możesz zostać zaatakowany przez wstrzyknięcie SQL. Możesz wykonać bardzo prosty atak za pomocą adresu URL, który wysyła dane za pośrednictwem żądania GET; Rozważ następujący przykład:

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

Twój adres URL wyglądałby http://yoursite.com/show?id=1

Teraz ktoś może spróbować czegoś takiego

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

Spróbuj zastąpić nazwę tabeli rzeczywistą nazwą tabeli. Jeśli poprawnie poda nazwę twojego stołu, opróżnią twój stół!(Bardzo łatwo jest brutalnie wymusić ten adres URL za pomocą prostego skryptu)

Twoje zapytanie wyglądałoby mniej więcej tak:

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

Przykład wrażliwego kodu PHP używającego 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!!! :( *************/
...

Rozwiązanie - użyj metod PDO preparat () i bindParam ():

<?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! :) *************/
...
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.