Zeyomir's Blog Bo piękniej jest wiedzieć coś o wszystkim…

11Sie/12Off
» «

Bezpieczeństwo aplikacji webowych – SQL injection

SQL Injection (SQLi), czyli wstrzykiwanie kodu SQL, jest jednym z najpopularniejszych typów ataku. Razem z innymi atakami typu injection zajmuje pierwsze miejsce na liście OWASP TOP 10. Jest to dość ciekawe, bo zabezpieczenie się przed nim jest banalne- ale o tym na koniec. Najpierw przyjrzyjmy się bliżej samemu atakowi.

Skąd to się bierze?

Podatność na atak tego typu pojawia się wszędzie tam, gdzie doklejamy dane otrzymane od użytkownika do zapytania, tak jak sklejamy ze sobą 2 stringi. Przykład:

query = "select * from users where name = '" + name
      + "' and password = '" + password + "';";

Niby wszystko fajnie, ale co stanie się, kiedy użytkownik jako name poda "admin' --" (dwa myślniki oznaczają rozpoczęcie komentarza)? Z bazy zostanie wyciągnięty użytkownik admin, a część sprawdzająca hasło zostanie potraktowana jako komentarz i nigdy się nie wykona. A gdybyśmy chcieli wyciągnąć z bazy wszystkich użytkowników? Nic prostszego- jako name podajemy "' or 1=1 --".

Jak sprawdzić czy podatność istnieje?

Najprościej przejrzeć kod w poszukiwaniu tego typu potworków ;). Jeżeli jednak chcemy się pobawić w pentestera... wystarczy w każde pole, co do którego mamy podejrzenia, że jego zawartość jest wykorzystywana w zapytaniach do bazy, wpisać pojedynczy cudzysłów ( ' ). Oczywiście to samo tyczy się parametrów przykazywanych przez get (jak np www.vulnerable.com?id=5).

Jeżeli na stronie pojawi się komunikat mówiący o błędnej składni zapytania- jesteśmy w domu ;). Jeżeli nie- możemy spróbować np "www.vulnerable.com?id=5+and+1=1+--" i "www.vulnerable.com?id=5+and+1=2+--".  Jeżeli nadal nic się nie stanie lub pojawi się jakiś komunikat o niedozwolonych znakach- oznacza to, że twórca aplikacji się przed sqli broni, ale wcale nie zanczy, że robi to skutecznie ;). Jeżeli natomiast pierwszy adres wyświetli stronę normalnie, a ten drugi spowoduje pojawienie się błędu lub wyświetlenie strony bez treści, to mamy Blind SQL Injection.

Co to Blind SQL injection i czym się różni?

Blind SQLi jest trochę trudniejszy do przeprowadzenia niż zwykłe SQLi. Przypomina trochę grę w 20 pytań ;). Różni się tym, że nie zobaczymy komunikatów o błędzie, generowanych przez silnik bazy danych bezpośrednio na stronie. Zamiast tego pojawi się strona bez treści lub jakiś ogólny błąd. Możemy uzyskać tylko odpowiedź true (strona bez zmian) lub false (pusta strona, lub strona błędu). Oczywiście w niczym nas to nie ogranicza, ponieważ możemy wpisać np w name "admin and password like 'c%' --" i tym samym zapytać czy pierwszą literką w haśle danego użytkownika jest 'c'.

Co się może stać?

Używając podzapytań i wyrażenia union, jesteśmy w stanie wydobyć dowolne dane z dowolnej tabeli (np loginy, adresy email i hasła użytkowników). Możemy zrobić drop table.. Albo dodać sobie użytkownika z maksymalnymi uprawnieniami. Tak czy siak, nic przyjemnego ;).

OK, jak się bronić?

Po pierwsze obowiązuje tu (jak zresztą wszędzie indziej) złota zasada 'najmniejszego uprzywilejowania', ale obiecałem nie zajmować się sprawami administracyjnymi ;).

Pierwszym odruchem jest filtrowanie danych wysłanych przez użytkownika i zabronienie używania znaków które mogą sprawiać kłopoty w zapytaniach SQL. Nie jest to jednak dobry pomysł- prawie zawsze okazuje się, że o jakimś znaku nie pomyśleliśmy ;).

Drugi pomysł to również filtracja, ale na zasadzie 'białej listy'- definiujemy których znaków wolno używać, a cała reszta jest zabroniona. Ogólnie rzecz biorąc, jest to dużo lepsze podejście do problemu filtracji jako takiego- jest dużo bezpieczniejsze niż podejście 'czarnej listy', ale narzuca na użytkownika ograniczenia. Czasem może się nawet okazać, że jest niewykonalne, ponieważ użytkownik będzie musiał mieć możliwość wprowadzania znaków które z punktu widzenia SQLi są niebezpieczne.

Kolejną możliwością jest zamiana niebezpiecznych znaków na odpowiednie encje (np apostrof na %27 albo & #x27;, dzięki czemu silnik bazy danych zinterpretuje je jako część tekstu. Jest to dość skuteczne rozwiązanie, ale jednocześnie tak jak w podejściu 'czarnej listy'- łatwo tu o przeoczenie jakiegoś znaku.

Na szczęście rozwiązanie problemu SQLi jest dużo prostsze. Wystarczy używać 'prepared statements' i 'parameters binding'. W Javie wygląda to mniej więcej tak (używając jdbc):

Connection conn = null;
conn = DriverManager.getConnection("jdbc:postgresql://localhost:5432", "user", "pass");
String sql = "SELECT * FROM users WHERE name = ? and password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);

pstmt.setString(1, name);
pstmt.setString(2, pass);
ResultSet rset = pstmt.executeQuery();

W ten sposób mówimy silnikowi bazy danych jak wygląda zapytanie, a co jest tylko parametrem.

Używam ORMa, czy jestem bezpieczny?

Zasadniczo nie. ORM to tylko narzędzie, pozwalające na korzystanie z baz danych w bardziej obiektowy sposób. Samo z siebie nie zabroni Ci robić głupot ;). Oznacza to mniej więcej tyle, że zapytanie takie jak to na samym początku wpisu nie stanie się nagle bezpieczne tylko dlatego, że używamy dajmy na to Hibernata. Jedyne w czym używanie ORMa może nam tu pomóc to możliwość skorzystania z 'named parameters', co wygląda tak:

String hql = "from User u where u.name = :username and u.pass = :userpass";

List result = session.createQuery(hql)
.setString("username", name)
.setString("userpass", password)
.list();

i jest po prostu wygodniejsze w użyciu, ale nadal jest to 'prepared statement'.

TL;DR;

Nigdy nie dodawaj parametrów do zapytań sql poprzez sklejanie stringów. Nie baw się w filtrowanie danych od użytkownika. Zawsze używaj 'prepared statements' i 'parameters binding'. Samo używanie ORMa nie wystarczy.

» «
Komentarze (3) Trackbacks (0)
  1. Fajne, w zupełności wystarczające żeby mieć ogólne pojęcie o co biega :)

  2. Odpowiednie Stored procedures i po sprawie :). Zalet jest o wiele więcej niż zapobieżenie SQL Injection.


Trackbacks are disabled.