Czym jest atak typu SQL injection i jak mu zapobiegać w języku C#?
Wstrzykiwanie SQL to technika wstrzykiwania kodu, która pozwala atakującym na wysyłanie złośliwego kodu SQL do serwera bazy danych przez dane wejściowe użytkownika. W swoim wideo "What Is SQL Injection And How Do I Prevent It in C#?" Tim Corey pokazuje dokładnie, jak pojawiają się luki w zabezpieczeniach kodu SQL injection, przedstawia kilka udanych przykładów ataków SQL injection (w tym ataki oparte na unii i destrukcyjne) oraz omawia praktyczne techniki zapobiegania SQL injection, które można zastosować w C#. Ten artykuł podąża za przewodnikiem Tima, abyś mógł zobaczyć dokładne problemy i sposoby ich rozwiązania, które on pokazuje.
Aplikacja demonstracyjna i dłączego to ma znaczenie
Tim zaczyna od małej aplikacji demonstracyjnej WPF powiązanej z lokalną InjectableDB (tabele People i Secrets). Pole wyszukiwania aplikacji, przypominające formę internetową, przyjmuje dane użytkownika (nazwisko) i buduje zapytanie SQL, aby zwrócić ID, imię i nazwisko. Działa — wpisz Corey i otrzymasz Tim Corey — ale Tim podkreśla kluczowy punkt: "To, że aplikacja działa, nie oznacza, że jest bezpieczna." Działająca aplikacja internetowa może nadal mieć luki SQL injection, gdy dostarczone przez użytkownika dane są bezpośrednio wstawiane do instrukcji SQL za pomocą konkatenacji łańcuchów lub dynamicznego SQL.
Niezabezpieczony kod — konkatenacja łańcuchów i dynamiczny SQL
Tim pokazuje dokładny, niebezpieczny wzorzec używany przez wielu programistów:
var sql = $"SELECT * FROM People WHERE LastName = '{searchText}'";
var results = connection.Query<Person>(sql);
var sql = $"SELECT * FROM People WHERE LastName = '{searchText}'";
var results = connection.Query<Person>(sql);
To oryginalne zapytanie używa konkatenacji łańcuchów do tworzenia instrukcji SQL. Tim ostrzega: jeśli widzisz kod, który wstawia dane użytkownika bezpośrednio do zapytań SQL, przerwij — to jest luka bezpieczeństwa SQL injection. Atakujący mogą stworzyć złośliwe dane wejściowe, które zmieniają strukturę poleceń SQL lub nawet uruchamiają dodatkowe złośliwe instrukcje SQL.
Jak atakujący to wykorzystuje — UNION i DROP
Aby pokazać, jak działa atak SQL injection, Tim odtwarza zapytania w SQL Server, a następnie konstruuje iniekcje z użyciem UNION ALL i komentarzy SQL (--) do ukrywania końcowych znaków. Przykład złośliwych danych demonstracyjnych pokazywanych przez Tima:
-
Iniekcja SQL oparta na unii, aby odczytać inne tabele:
UNION ALL SELECT ID, Username AS FirstName, Password AS LastName FROM Secrets;To łączy wyniki z Secrets z oryginalnym zestawem wyników SELECT, ujawniając dane wrażliwe, takie jak nazwy użytkowników i hasła.
-
Niszcząca iniekcja, aby usunąć tabele:
DROP TABLE DemoTable;To uruchamia drugą instrukcję SQL (DROP TABLE) poprzez zakończenie pierwszego polecenia średnikiem, a następnie dodanie destrukcyjnej komendy. Tim pokazuje, jak tabela znika — baza danych została zmodyfikowana przez złośliwy SQL.
Punkt Tima: atakujący nie muszą znać nazw tabel lub kolumn z góry — mogą wyliczać nazwy tabel lub kolumn z serwerów baz danych, lub po prostu próbować technik typu blind lub opartych na czasie, aby odkryć zachowanie.
Naprawa 1 — Zapytania parametryzowane
Pierwsza i podstawowa obrona Tima polega na zaprzestaniu budowania łańcuchów SQL z danymi użytkownika. Zastąp dynamiczny SQL zapytaniami parametryzowanymi:
string sql = "SELECT * FROM People WHERE LastName = @LastName";
var results = connection.Query<Person>(sql, new { LastName = searchText });
string sql = "SELECT * FROM People WHERE LastName = @LastName";
var results = connection.Query<Person>(sql, new { LastName = searchText });
Tim wyjaśnia, że parametryzacja (użycie w stylu przygotowanej instrukcji) oznacza, że baza traktuje dostarczone przez użytkownika dane ściśle jako dane — złośliwy SQL staje się tylko wartością tekstową i nie może zmienić struktury SQL. To zapobiega wielu powszechnym atakom SQL injection, w tym ładunkom opartym na unii i dołączonym ; poleceniom DROP TABLE.
Zaleca także połączenie parametryzacji z minimalną walidacją danych wejściowych: oczyścić lub zablokować znaki rzadko spotykane w nazwisku (np. średniki lub markery komentarzy --) przy jednoczesnym akceptowaniu prawidłowych znaków, takich jak apostrofy (O'Reilly). Zapytania parametryzowane + oczyszczanie danych wejściowych zapewniają znaczne zabezpieczenie przed atakami SQL injection.
Naprawa 2 — Procedury składowane
Tim pokazuje następnie dwie procedury składowane: niebezpieczną procedurę składowaną, która konkatenizuje SQL wewnątrz procedury i następnie ją wykonuje, oraz bezpieczną procedurę składowaną, która bezpośrednio wykorzystuje parametry.
-
Niebezpieczna procedura składowana buduje łańcuch @sql z parametru i wykonuje go — nadal podatna na iniekcję.
- Bezpieczna procedura składowana wykonuje SELECT ... WHERE LastName = @LastName i wykonuje się z parametrem — bezpieczna.
Tim wyjaśnia: procedury składowane nie są automatycznym rozwiązaniem, jeśli nadal tworzysz dynamiczny SQL w ich ramach. Ale jeśli są używane poprawnie (bez dynamicznego SQL), procedury składowane pomagają centralizować instrukcje SQL, co ułatwia ich parametryzację i audyt zapytań. Procedury składowane mogą również pomóc uprościć zapobieganie SQL injection w twojej aplikacji.
Nie ufaj żadnym danym — nawet z bazy danych
Ważny, często pomijany punkt, który podkreśla Tim: nie możesz ślepo ufać danym pobranym z własnej bazy danych SQL. Atakujący czasem umieszczają złośliwe ładunki w kolumnach ("bomba zegarowa"), które w późniejszym czasie są łączone w dynamiczny SQL przez inny proces. Tim nalega: zawsze używaj parametrów i oczyszczaj dane na każdym etapie — niezależnie czy pochodzą z formularza internetowego, przesyłania plików, czy z własnej bazy danych — aby złośliwe dane nie mogły później stać się drogą do iniekcji.
Porada bonusowa — najmniejsze uprawnienia i ograniczanie uprawnień bazy danych
Poza poprawkami w kodzie, Tim zaleca defensywną konfigurację: ogranicz uprawnienia bazy danych dla kont twoich aplikacji. W swoim demie połączenie używa konta administratora przez zintegrowane zabezpieczenia — niebezpieczne. Zamiast tego używaj zasady najmniejszych uprawnień:
-
Utwórz konto bazy danych dla aplikacji z tylko potrzebnymi mu prawami.
-
Jeśli używasz procedur składowanych, przyznaj temu kontu tylko uprawnienia EXECUTE dla określonych procedur składowanych i nic więcej.
- Nie dawaj kontom aplikacji szerokich uprawnień administratora, które pozwalają na DROP TABLE, wylistowanie wszystkich tabel, czy czytanie innych baz danych.
To zmniejsza skutki udanego ataku SQL injection — nawet jeśli iniekcja jest możliwa, atakujący nie może zrobić więcej niż konto jest dozwolone.
Tim także zauważa, że Entity Framework to komplikuje: EF często wymaga podwyższonych uprawnień (migracje, zmiany schematów). Jeśli używasz EF w produkcji, dokładnie planuj jego uprawnienia i wdrożenie.
Podsumowanie — przestań, parametryzuj, oczyszczaj, ograniczaj
Tim kończy swoje wideo z jasną listą kontrolną do zapobiegania SQL injection w aplikacjach C#:
-
Przestań budować instrukcje SQL z konkatenacją łańcuchów lub dynamicznym SQL zawierającym dane użytkownika.
-
Używaj zapytań parametryzowanych / wzorców przygotowanych instrukcji, aby dane użytkownika były zawsze traktowane jako dane.
-
Oczyszczaj dane wejściowe tam, gdzie to stosowne (blokuj średniki, komentarze SQL, niespodziewane znaki).
-
Preferuj bezpieczne procedury składowane (bez dynamicznego SQL w nich) do centralizacji logiki zapytań.
-
Stosuj zasadę najmniejszych uprawnień do kont bazy danych — ogranicz, co użytkownik bazy danych aplikacji może robić.
- Przeglądaj kod (szczególnie miejsca, które budują SQL dynamicznie) i testuj pod kątem luk SQL injection.
Ostatnia przestroga Tima: niedbałe zarządzanie danymi użytkownika, dynamiczne SQL i nadmiarowe uprawnienia baz danych mogą prowadzić do poważnych naruszeń — wyciek danych wrażliwych, zniszczone tabele czy długotrwałe, niezauważone wycieki. Traktuj zapobieganie SQL injection jako kluczowy wymóg bezpieczeństwa, a nie opcjonalny dodatek.
