Podatności, które opisywałem do tej pory w serii “Geneza podatności…” choć wykryte, zgłoszone i poprawione daaawno temu (Log4j, Struts2 czy SnakeYAML) niestety cały czas są wykrywane w aplikacjach działających w przestrzeni Internetu. Dzisiejszy wpis poświęcę podatności zdecydowanie młodszej bo niespełna kilku tygodniowemu zgłoszeniu w popularnym “connectorze” do bazy danych PostgreSQL
PostgreSQL JDBC Driver
PostgreSQL JDBC Driver to biblioteka w języku Java, służąca do komunikacji aplikacji Java z bazami danych PostgreSQL. Umożliwia ona aplikacjom wykonywanie zapytań SQL, aktualizowanie danych i zarządzanie transakcjami w sposób zoptymalizowany dla PostgreSQL. Dzięki niej, programiści mogą łatwo integrować bazę danych PostgreSQL z aplikacjami napisanymi w Javie, korzystając z standardowego API JDBC.
Prepared Statement
Prepared Statements to zaawansowane narzędzie w popularnych frameworkach, pozwalające na wydajne i bezpieczne wykonywanie zapytań SQL. Umożliwiają one prekompilację zapytania, co znacząco przyspiesza jego wykonanie, szczególnie przy wielokrotnym użyciu z różnymi parametrami. Ponadto, mechanizm ten chroni przed atakami SQL Injection przez separację zapytań od danych wejściowych.
W Javie korzystanie z PreparedStatement wygląda następująco:
String sql = "SELECT * FROM users WHERE email = ?";
PreparedStatement statement = conn.prepareStatement(sql);
statement.setString(1, "[email protected]");
ResultSet resultSet = statement.executeQuery();
W Pythonie, przy użyciu biblioteki psycopg2
dla PostgreSQL, przykład może wyglądać tak:
import psycopg2
conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()
cur.execute("SELECT * FROM users WHERE email = %s", ("[email protected]",))
rows = cur.fetchall()
Niestety pomimo dostępności tego typu funkcjonlności w dalszym ciągu w weryfikowanych aplikacjach spotykam się z taką realizacją komunikacji do bazy danych:
String sql = "SELECT * FROM users WHERE email = '" + userEmail + "'";
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
W tym przypadku, userEmail
jest bezpośrednio włączane do zapytania bez walidacji lub escapowania, co pozwala na potencjalne wstrzyknięcie szkodliwego kodu SQL. Używanie tego podejścia zamiast PreparedStatement
otwiera aplikację na ryzyko ataków SQL Injection, gdyż nie zapewnia żadnych mechanizmów bezpieczeństwa dla danych wejściowych.
CVE-2024-1597
Czy jeśli słuchasz zaleceń takich bezpieczników jak ja, to możesz spać spokojnie? Okazuje się, że nie… Opisana podatność pozwala na wstrzyknięcie kodu SQL do naszej aplikacji pomimo tego, że wszędzie do komunikacji z bazą wykorzystujemy mechanizm `prepared statement`. Oczywiście aby Twoja aplikacja była podatna na tego typu atak musi być spełnionych kilka warunków:
Włączenie prostego sposobu parsowania zapytań
Podczas inicjowania sterownika do bazy danych PSQL mamy do dyspozycji bogaty zestaw ustawień pozwalający na odpowiednie dostosowanie konfiguracji: https://jdbc.postgresql.org/documentation/use/ jedno z tych ustawień dotyczy preferQueryMode

domyślnie ten parametr przyjmuje wartość extended
ale jeśli z jakiegoś powodu programista zdecyduję się na zmienienie tej wartości na preferQueryMode=simple
wyłączona zostaje duża część parsowania i kreator zapytań wchodzi w tryb tekstowy. Samo włączenie tej opcji nie jest specjalnie niebezpieczne jednak może przełożyć się na wydajność komunikacji z bazą danych w kontekście parsowania odpowiedzi na odpowiednie obiekty.
Zapytanie umieszczone w PreparedStatement musi zawierać negatywny parametr “-?”
Używanie negatywnych parametrów takich jak “-?” nie jest standardową praktyką i na szczęście nie znajduje zastosowania w dużej liczbie aplikacji (w zasadzie żaden przypadek aplikacji, który analizowałem w ciągu ostatnich 12 lat nie implementował tego typu funkcjonalności). W rzeczywistości może być jednak różnie. Najlepiej wiesz jak to wygląda w Twojej aplikacji 🙂
Wykorzystanie podatności
Aby zilustrować na czym polega podatność spójrz na ten przykład:
PreparedStatement stmt = conn.prepareStatement("SELECT -?, ?");
stmt.setInt(1, -1);
stmt.setString(2, "\nWHERE false --");
ResultSet rs = stmt.executeQuery();
Zapisano tutaj proste zapytanie w formie select z dwoma atrybutami do pobrania z bazy danych. Następnie umieszczono w dwóch kolejnych placeholderach wartości liczby całkowitej -1
oraz ciąg znaków \nWHERE false --
. Dzięki temu, że w oryginalnym zapytaniu przed pierwszym placeholderem znajduję się -
oraz tym, że -1
jest poprawną liczbą całkowitą, która mieści się w typie Integer
. W rzeczywistości zapytanie, które wykona się na bazie danych będzie wyglądało tak:
SELECT --1,'
WHERE false --'
Zastosowanie negatywnego parametru i specjalnie sformatowanego ciągu tekstu może zmienić strukturę wykonanego zapytania, co pozwala na wstrzyknięcie niezamierzonej logiki. W szczególności, przez manipulację parametrami zapytania, atakujący może zmienić warunki zapytania lub wykonać zupełnie inne polecenia SQL, co może prowadzić do nieautoryzowanego dostępu do danych lub ich modyfikacji.
Usuwanie zagrożenia
Na szczęście, zaraz po opublikowaniu informacji o wykrytym zagrożeniu społeczność wypuściła zaktualizowaną wersje biblioteki. Poprawka zakłada, że nawet przy ustawieniu preferQueryMode=simple
parametry prasowane są poprawnie. W konsekwencji próba stworzenia zapytania, które zostało zaprezentowane powyżej będzie skutkowało wykonianiem:
SELECT -('-1'::int4), ('
WHERE false --')
Czyli – format, który jest odporny na atak SQL Injection.
Wersje biblioteki z wdrożoną poprawką:

Jeśli z jakiegoś powodu nie masz możliwości jednak na “podbicie” wersji org.postgresql:postgresql
zostaje Ci możliwość mitygacji poprzez pilnowanie tego, aby preferQueryMode
nie przyjmował wartości simple
Na koniec przypomne:
Aplikacje starzeją się jak mleko, a nie jak wino - pilnuj tego aby w Twoim kodzie wykorzystywać aktualne wersje bibliotek