SQL injection w Pythonie - jak zabezpieczyć aplikację?

Tymoteusz Kowalski • 15 października 2026
Abstrakcyjna tarcza w odcieniach niebieskiego i zieleni, symbolizująca bezpieczeństwo. Dowiedz się, czym jest sql injection i jak się przed nim chronić.

Spis treści

Formularz logowania działa, wyszukiwarka produktów przyjmuje nietypowy znak, a aplikacja nagle pokazuje błąd bazy danych. W takich sytuacjach problemem może być SQL injection, czyli podatność pozwalająca potraktować dane użytkownika jak fragment polecenia SQL. Wyjaśniam, jak działa ten atak, jakie szkody powoduje oraz jak zabezpieczyć aplikację, także pisaną w Pythonie.

Najważniejsza zasada polega na oddzieleniu danych od kodu zapytania

  • SQL injection wykorzystuje sytuację, w której dane wejściowe zostają połączone z tekstem zapytania SQL.
  • Atak może prowadzić do odczytu, zmiany lub usunięcia danych, a czasem także do dalszego przejęcia systemu.
  • Najskuteczniejszą ochroną są zapytania parametryzowane, a nie ręczne filtrowanie znaków.
  • W Pythonie parametry przekazuje się osobno, na przykład przez placeholdery biblioteki sqlite3 lub sterownika PostgreSQL.
  • Dodatkową warstwę bezpieczeństwa zapewniają minimalne uprawnienia, bezpieczne komunikaty błędów i testy.

Haker wykorzystuje sql injection co to jest, by ominąć logowanie i uzyskać dostęp do strony admina.

SQL injection w praktyce oznacza pomieszanie danych z kodem

SQL injection to atak na aplikację, która komunikuje się z relacyjną bazą danych, na przykład MySQL, PostgreSQL, Microsoft SQL Server albo SQLite. Podatność pojawia się wtedy, gdy aplikacja buduje zapytanie przez doklejanie danych użytkownika do tekstu SQL.

Wyobraźmy sobie prostą funkcję logowania. Program pobiera login i hasło, a potem tworzy zapytanie w rodzaju:

SELECT id FROM users
WHERE username = 'wpisany_login'
AND password = 'wpisane_haslo';

Samo zapytanie nie jest problemem. Ryzyko pojawia się wtedy, gdy zawartość pól zostaje wstawiona do niego bez odpowiedniego rozdzielenia. Wtedy specjalnie przygotowany tekst może zmienić znaczenie całego polecenia, zamiast pozostać zwykłą wartością wyszukiwaną w bazie.

Najprościej porównać to do formularza, w którym użytkownik powinien podać adres, ale aplikacja pozwala mu dopisać instrukcję dla kuriera. Baza danych nie rozumie intencji programisty. Wykonuje otrzymane polecenie zgodnie ze składnią i uprawnieniami konta, z którego korzysta aplikacja.

Jak powstaje podatność w kodzie aplikacji

Najczęstszy błąd wygląda niewinnie. Programista łączy stały fragment zapytania z wartością pobraną z formularza, parametru URL albo nagłówka HTTP:

user_id = request.args.get("id")
query = "SELECT * FROM users WHERE id = " + user_id
cursor.execute(query)

Problem nie znika tylko dlatego, że aplikacja oczekuje liczby. Jeśli wartość nie jest bezpiecznie obsłużona, napastnik może próbować wpłynąć na strukturę zapytania. Walidacja typu pomaga, ale sama nie zastępuje parametryzacji, zwłaszcza gdy dane mają postać tekstu.

Niebezpieczne są także zapytania dynamiczne

Ryzyko dotyczy nie tylko logowania. Podatność może pojawić się w wyszukiwaniu produktów, filtrowaniu zamówień, raportach, sortowaniu i panelach administracyjnych. Szczególnie ostrożnie traktuję kod, który składa zapytania z wielu fragmentów tekstu zależnych od użytkownika.

Popularny ORM, czyli narzędzie mapujące obiekty programu na tabele, nie daje automatycznej gwarancji bezpieczeństwa. Bezpieczne są zwykle jego standardowe metody filtrowania, ale użycie surowego SQL z konkatenacją może ponownie otworzyć tę samą lukę. ORM nie zastępuje zasad bezpiecznego programowania.

Czego nie należy mylić z ochroną

  • Usuwanie pojedynczych znaków, takich jak apostrof, nie jest pełną ochroną.
  • Ukrywanie komunikatu błędu nie naprawia podatności, tylko utrudnia jej zauważenie.
  • Filtrowanie słów typu SELECT lub UNION można ominąć i nie powinno być główną metodą zabezpieczenia.
  • Szyfrowanie bazy danych nie zapobiega wstrzyknięciu, jeśli aplikacja po odszyfrowaniu wykonuje niebezpieczne zapytanie.

Najczęściej widzę tu błędne założenie, że „zwykły formularz” nie jest atrakcyjny dla atakującego. Tymczasem automatyczne skanery sprawdzają tysiące aplikacji, a podatność może zostać wykryta bez ręcznego analizowania kodu.

Co może osiągnąć napastnik

Skutki zależą od konstrukcji zapytania, rodzaju bazy, uprawnień konta i dodatkowych zabezpieczeń. W łagodnym scenariuszu aplikacja zwróci błąd lub nieprawidłowy wynik. W gorszym przypadku atakujący uzyska dostęp do danych, których nie powinien widzieć.

Wariant ataku Możliwy skutek Dlaczego jest groźny
Odczyt danych Pobranie rekordów użytkowników, zamówień lub konfiguracji Może naruszyć poufność i ujawnić dane osobowe
Modyfikacja danych Zmiana adresów, cen, uprawnień albo statusów Wpływa na działanie biznesowe aplikacji
Usunięcie danych Skasowanie rekordów lub części tabel Może spowodować przestój i utratę informacji
Ominięcie autoryzacji Dostęp do konta lub zasobów bez prawidłowego uwierzytelnienia Atakujący może wejść w rolę innego użytkownika
Rozpoznanie systemu Ustalenie wersji bazy, tabel lub sposobu działania aplikacji Ułatwia przygotowanie kolejnych etapów ataku

Wyróżnia się między innymi ataki error-based, które wykorzystują komunikaty błędów, oraz blind SQL injection, gdzie aplikacja nie pokazuje danych bezpośrednio, ale zdradza informacje przez różnice w odpowiedziach albo czasie reakcji. Istnieją też techniki wykorzystujące łączenie wyników zapytań, lecz ich szczegóły powinny być testowane wyłącznie w autoryzowanym środowisku.

Największe szkody nie zawsze wynikają z samej bazy. Jeśli konto aplikacji ma zbyt szerokie uprawnienia, luka może zostać użyta do odczytu tabel administracyjnych, danych klientów czy sekretów zapisanych w bazie. Dlatego zasada najmniejszych uprawnień ogranicza zasięg incydentu, choć nie usuwa źródła problemu.

Jak zabezpieczyć aplikację Python

Podstawowa reguła jest prosta: kod zapytania definiujemy osobno, a wartości przekazujemy jako parametry. Sterownik bazy sam zajmuje się ich właściwym przekazaniem, dzięki czemu tekst użytkownika nie staje się instrukcją SQL.

Niebezpieczny wariant:

email = request.form["email"]
query = "SELECT id FROM users WHERE email = '" + email + "'"
cursor.execute(query)

Bezpieczniejszy wariant dla SQLite:

email = request.form["email"]
query = "SELECT id FROM users WHERE email = ?"
cursor.execute(query, (email,))

W sterownikach PostgreSQL składnia placeholdera może wyglądać inaczej, na przykład:

query = "SELECT id FROM users WHERE email = %s"
cursor.execute(query, (email,))

Nie należy kopiować znaku zapytania między bibliotekami bez sprawdzenia dokumentacji sterownika. Sama idea pozostaje jednak taka sama: parametr nie jest sklejany z zapytaniem, tylko przekazywany osobnym argumentem.

Co zrobić z nazwą kolumny lub sortowaniem

Parametryzacja dobrze obsługuje wartości, ale zwykle nie pozwala bezpośrednio podstawić nazwy tabeli, kolumny czy kierunku sortowania. W takim przypadku używam listy dozwolonych wartości, a nie danych wklejanych bezpośrednio do SQL:

allowed_sort = {
    "name": "name",
    "price": "price",
    "newest": "created_at"
}

sort_key = request.args.get("sort", "newest")
sort_column = allowed_sort.get(sort_key, "created_at")

query = f"SELECT id, name, price FROM products
         ORDER BY {sort_column}"
cursor.execute(query)

Użytkownik wybiera tu jeden z wcześniej zdefiniowanych kluczy, a aplikacja sama przypisuje mu bezpieczną nazwę kolumny. To dobre miejsce na allowlistę, czyli listę akceptowanych opcji. Nie należy próbować rozwiązywać tego przez ręczne usuwanie podejrzanych znaków.

Warstwy, które warto dołożyć

  • Używaj konta bazy z uprawnieniami ograniczonymi do operacji potrzebnych aplikacji.
  • Nie zapisuj haseł użytkowników w postaci jawnego tekstu. Przechowuj ich bezpieczne skróty z użyciem odpowiedniego mechanizmu do haseł.
  • Pokazuj użytkownikowi ogólny komunikat błędu, a szczegóły zapisuj w kontrolowanych logach.
  • Aktualizuj sterowniki, biblioteki i system bazy danych.
  • Nie umieszczaj haseł do bazy w kodzie źródłowym ani w repozytorium.
  • Testuj także ścieżki administracyjne, API i zadania wykonywane w tle.

Ręczne escapowanie znaków traktuję najwyżej jako rozwiązanie awaryjne dla bardzo konkretnego, starego interfejsu. Zapytania parametryzowane są prostsze do utrzymania i znacznie trudniej przypadkowo zepsuć je podczas późniejszej rozbudowy aplikacji.

Jak wykrywać SQL injection i reagować na problem

Wykrywanie powinno zacząć się od przeglądu kodu. Szukam miejsc, w których do metod wykonujących zapytania trafia tekst z formularza, adresu URL, ciasteczka lub nagłówka. Podejrzane są szczególnie operatory konkatenacji, f-stringi i formatowanie łańcuchów używane bezpośrednio przy budowaniu SQL.

Drugim krokiem są testy w środowisku testowym. Można sprawdzać, czy nietypowe znaki powodują błędy, różnice w odpowiedziach lub nieoczekiwane wyniki, ale nie wolno testować cudzych systemów bez zgody. Do kontrolowanych audytów służą między innymi narzędzia analizujące aplikacje webowe, skanery DAST oraz testy integracyjne.

Przeczytaj również: Plik CSV - Jak działa, dlaczego sprawia problemy i jak go używać?

Co sprawdzić po znalezieniu luki

  1. Ogranicz dostęp do podatnego endpointu, jeśli jest to możliwe bez większej awarii.
  2. Zidentyfikuj wszystkie miejsca korzystające z tego samego wzorca kodu.
  3. Zastąp konkatenację parametryzacją i dodaj test regresyjny.
  4. Sprawdź logi pod kątem nietypowych zapytań, błędów i dużej liczby odrzuconych żądań.
  5. Zweryfikuj, czy konto bazy nie ma nadmiarowych uprawnień.
  6. Oceń, czy mogło dojść do odczytu lub zmiany danych, i uruchom procedurę reagowania na incydenty.

Sam komunikat „błąd SQL” nie jest jeszcze dowodem udanego ataku. Jest jednak sygnałem, że aplikacja może ujawniać zbyt dużo informacji albo nieprawidłowo obsługuje dane wejściowe. Błąd widoczny dla użytkownika powinien prowadzić do analizy, a nie tylko do wyciszenia wyjątku.

Co sprawdzić przed wdrożeniem aplikacji do produkcji

Przed publikacją aplikacji robię krótki przegląd wszystkich miejsc, w których kod dotyka bazy. Najważniejsze pytanie brzmi nie „czy filtrujemy wejście?”, lecz „czy dane są wszędzie przekazywane jako parametry?”.

  • Przeszukaj kod pod kątem execute(), f-stringów i konkatenacji przy zapytaniach.
  • Sprawdź formularze, API, importy plików i zadania cykliczne.
  • Dodaj testy dla pustych wartości, znaków specjalnych i nieprawidłowych typów.
  • Zweryfikuj uprawnienia konta aplikacji oraz dostęp sieciowy do bazy.
  • Wyłącz szczegółowe błędy SQL na produkcji.
  • Skonfiguruj monitoring, kopie zapasowe i alerty dotyczące nietypowych zapytań.

SQL injection nie jest problemem, który rozwiązuje pojedynczy filtr albo instalacja dodatkowego modułu. Najlepsze efekty daje połączenie parametryzacji, ograniczonych uprawnień, testów i świadomego przeglądu kodu. W Pythonie podstawowa poprawka zwykle jest niewielka, ale trzeba zastosować ją konsekwentnie w całej aplikacji.

Jeżeli zapamiętasz jedną rzecz, niech będzie nią rozdzielenie kodu SQL od danych użytkownika. To prosta praktyka, która znacząco zmniejsza ryzyko, a przy okazji sprawia, że kod łatwiej testować, rozwijać i utrzymywać.

FAQ - Najczęstsze pytania

Podatność pojawia się, gdy dane użytkownika są doklejane do tekstu zapytania SQL, na przykład przez konkatenację lub f-string. W takiej sytuacji specjalnie przygotowany tekst może zmienić znaczenie całego polecenia zamiast pozostać zwykłą wartością.

Należy oddzielić kod zapytania od danych i przekazywać wartości jako parametry. W SQLite używa się między innymi placeholdera ?, a w sterownikach PostgreSQL często %s; składnię trzeba sprawdzić w dokumentacji konkretnej biblioteki.

Parametryzacja zwykle nie służy do podstawiania nazw tabel, kolumn ani kierunku sortowania. Trzeba zastosować allowlistę, czyli mapowanie kluczy wybranych przez użytkownika na wcześniej zdefiniowane, bezpieczne nazwy kolumn.

Należy ograniczyć dostęp do podatnego endpointu, sprawdzić podobne miejsca w kodzie, zastąpić konkatenację parametryzacją i dodać test regresyjny. Warto też przeanalizować logi, zweryfikować uprawnienia konta bazy oraz ocenić, czy doszło do odczytu lub zmiany danych.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

sql injection
parametryzacja
sqlite
postgresql
python
Autor Tymoteusz Kowalski
Tymoteusz Kowalski
Nazywam się Tymoteusz Kowalski i od 7 lat zajmuję się programowaniem, ze szczególnym uwzględnieniem Pythona oraz nowoczesnych technologii. Moja przygoda z programowaniem zaczęła się od fascynacji możliwościami, jakie daje kod, a z czasem przerodziła się w pasję do dzielenia się wiedzą i pomagania innym w zrozumieniu złożonych zagadnień. Interesuje mnie, jak można uprościć trudne tematy, aby były bardziej przystępne dla każdego, niezależnie od poziomu zaawansowania. W moich tekstach staram się dostarczać rzetelne, aktualne i zrozumiałe informacje, a także porównywać różne źródła, aby zapewnić czytelnikom szeroki kontekst. Piszę o praktycznych zastosowaniach Pythona, nowinkach technologicznych oraz najlepszych praktykach programistycznych. Moim celem jest nie tylko przedstawienie teorii, ale także pokazanie, jak można ją zastosować w praktyce, co mam nadzieję, uczyni moją twórczość użyteczną dla każdego, kto pragnie rozwijać swoje umiejętności w programowaniu.

Udostępnij artykuł

Napisz komentarz