Na atak SQL Injection narażone są programy, w których programista zdecydował się „sklejać” zapytania z fragmentów instrukcji SQL wpisanych przez programistę z fragmentami tekstu wpisywanymi przez użytkowników. Takie zapytanie, powstałe wskutek złączenia kodu programisty są następnie uruchamiane jako tzw. dynamiczny SQL. O ile w przypadku gdy użytkownik podaje „normalne” dane, zapytanie będzie działać zgodnie z założeniami, o tyle, gdy użytkownik odpowiednio spreparuje dane, może doprowadzić do uruchomienia własnych instrukcji. Zobaczmy to na przykładzie:
Najpierw załóżmy prostą tabelę:
USE tempdb CREATE TABLE students (Id INT IDENTITY, Name NVARCHAR(50));
Do tej tabeli można wstawić rekord następującą instrukcją SQL:
INSERT INTO students VALUES('Robert')
Programista nie przewidujący ataku SQL Injection zbuduje swoją procedurę w oparciu o poniższą logikę:
DECLARE @s NVARCHAR(1000) SET @s='Robert' DECLARE @sql NVARCHAR(1000)='INSERT INTO students VALUES('''+@s+''')' PRINT @sql EXEC sp_executesql @statement=@sql
Przeanalizujmy to po kolei:
- Zmienna @s odpowiada parametrowi przekazywanemu do procedury z zewnętrznej aplikacji (np ze strony internetowej. W tym przykładzie do zmiennej @s przypisujemy wartość wprowadzoną przez użytkownika.
- Potem deklarujemy zmienną @sql. Ta zmienna zawiera w sobie instrukcję wstawiania rekordu (polecenie INSERT) jest jednak dynamicznie uzupełaniana o dane przesłane przez użytkownika.
- Polecenie PRINT wyświetla informacyjnie polecenie zawarte w zmiennej @sql
- Na koniec polecnie w zmiennej @sql jest uruchamiane, wykonasz to poleceniem sp_excutesql
Zobaczmy, jak można zmodyfikować zawartość danych przesyłanych przez użytkownika:
DECLARE @s NVARCHAR(1000) SET @s='Robert''); DROP TABLE students;--' DECLARE @sql NVARCHAR(1000)='INSERT INTO students VALUES('''+@s+''')' PRINT @sql EXEC sp_executesql @statement=@sql
Tym razem użytkownik wprowadził tekst: Robert”); DROP TABLE students;–
Przypomnijmy, że jeżeli w zmiennej tekstowej chcesz przesłać ’ (apostrof), który w SQL domyślnie oznacza początek i koniec tekstu, to należy go wpisać podwójnie: ”
Co więc stanie się teraz? Zapytanie, które ostatecznie jest uruchamiane wygląda następująco:
INSERT INTO students VALUES('Robert'); DROP TABLE students;--')
Oto co się stanie:
- Do tabeli studenci zostanie wstawiony rekord z imieniem Robert. Średnik oznacza koniec instrukcji SQL.
- Kolejna instrukcja to DROP TABLE students. To właśnie wstrzyknięty kod! Znak średnika kończy drugie polecenie SQL i przechodzimy do kolejnego.
- Ostatnia instrukcja to komentarz, bo zaczyna się od — . Dzięki temu ciąg dalszy oryginalnej instrukcji SQL nie jest wykonywany, a całe zapytanie nie zgłasza błędu składni.
Jeżeli więc zaryzykowałeś i uruchomiłeś powyższy kod, to… nie masz już tabeli students!
Co zrobić aby zabezpieczyć się przed SQL Injection?
- Nie stosować dynamicznego SQL, o ile można,
- Badać przesyłane zmienne pod kątem obecności w nich znaku ; (koniec instrukcji SQL) — (komentarz) ’ (apostrof – cytowanie)
- Nie wyświetlać zbyt szczegółowych komunikatów o błędzie. Potencjalny haker może wiele dowiedzieć się o strukturze twoich tabel na podstawie szczegółowego komunikatu o błędzie.
- Używać parametrów. Parametry omówimy sobie niżej
Otóż, gdyby powyższy niebezpieczny fragment kodu przepisać następująco, zapytanie nie skończy się już aż tak źle:
DECLARE @s NVARCHAR(1000) SET @s='Robert''); DROP TABLE students;--' DECLARE @sql NVARCHAR(1000)='INSERT INTO students VALUES(@Name)' DECLARE @ParmDefinition NVARCHAR(1000)= N'@Name NVARCHAR(100)'; PRINT @sql EXEC sp_executesql @sql,@ParmDefinition,@Name=@s
Zapytanie wpisane początkowo w zmiennej @sql posługuje się parametrem @Name. Typ tego parametru został określony w zmiennej @ParamDefinition. Dzięki zmiennej i definicji typu tej zmiennej polecenie sp_executesql „wie”, że powinno otrzymać w parametrze @Name wartość tekstową. Tym razem wywołując polecenie sp_executesql nie utracisz tabeli students. Do tabeli zostanie jednak wstawiony student o dziwnym imieniu: