🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

SQL Injection i parametryzacja w .NET — bezpieczne zapytania

SQL Injection — sklejanie stringów kontra bezpieczna parametryzacja zapytania w .NET

Sklejanie zapytania SQL ze stringów to najczęstsza krytyczna podatność w aplikacjach (OWASP od lat trzyma injection w czołówce) i pewne pytanie na każdej rozmowie dotykającej bazy danych. Wystarczy jeden apostrof w danych od użytkownika, żeby zamienić „zaloguj tego użytkownika” w „wypisz wszystkie hasła”. Umiesz już pisać zapytania — teraz naucz się wysyłać je z aplikacji tak, żeby dane użytkownika nigdy nie stały się kodem.

Jak działa SQL injection

Załóżmy logowanie, które skleja zapytanie ze stringów:

// ❌ NIGDY tak nie rób
var sql = "SELECT * FROM Users WHERE Login = '" + login + "'";

Dla normalnego loginu anna powstaje poprawne zapytanie. Ale gdy użytkownik wpisze w pole login:

' OR '1'='1

zapytanie staje się:

SELECT * FROM Users WHERE Login = '' OR '1'='1'

'1'='1' jest zawsze prawdziwe — warunek przepuszcza wszystkich użytkowników. Bardziej agresywny ładunek ('; DROP TABLE Users;--) potrafi usunąć dane. Problem jest jeden: dane użytkownika zostały wklejone jako kod SQL, zamiast być traktowane jako wartość.

Injection nie bierze się z „złego użytkownika”. Bierze się z jednego znaku + sklejającego dane z zapytaniem. Usuń sklejanie, a atak znika u źródła.

Parametryzacja — dane osobno od zapytania

Rozwiązanie to parametry: wysyłasz szkielet zapytania z symbolami zastępczymi i osobno wartości. Baza traktuje wartości wyłącznie jako dane — apostrof w nich jest zwykłym znakiem, nie końcem stringa.

### ADO.NET (surowo)

// ✅ parametr, nie sklejanie
var cmd = new SqlCommand(
    "SELECT * FROM Users WHERE Login = @login", connection);
cmd.Parameters.AddWithValue("@login", login);

@login to placeholder; login jedzie osobno. Nawet ' OR '1'='1 zostanie potraktowane jako dosłowna wartość do porównania — i nic nie znajdzie.

### Dapper

Dapper parametryzuje automatycznie na podstawie obiektu — dlatego jest domyślnie bezpieczny:

var user = connection.QuerySingleOrDefault<User>(
    "SELECT * FROM Users WHERE Login = @Login",
    new { Login = login });   // wartość jako parametr, nie string

Więcej o tym podejściu w budowaniu API na Dapperze.

### Entity Framework Core

LINQ jest bezpieczny z definicji — EF Core zawsze parametryzuje wygenerowany SQL:

var user = db.Users.FirstOrDefault(u => u.Login == login); // parametr pod spodem

Poza bezpieczeństwem parametryzacja daje bonus wydajnościowy: baza cache’uje plan wykonania dla szkieletu zapytania i używa go ponownie dla różnych wartości, zamiast kompilować każde sklejone zapytanie od nowa.

Transakcje — wszystko albo nic

Bezpieczeństwo to nie tylko injection — to też integralność przy wielu zapisach. Przelew to odjęcie z jednego konta i dodanie do drugiego; jeśli drugi krok padnie, pierwszy nie może zostać. Służą do tego transakcje (ACID — atomowość, spójność, izolacja, trwałość):

BEGIN TRANSACTION;
UPDATE Konta SET Saldo = Saldo - 100 WHERE Id = 1;
UPDATE Konta SET Saldo = Saldo + 100 WHERE Id = 2;
COMMIT;              -- oba naraz; przy błędzie ROLLBACK cofa całość

W EF Core SaveChanges() jest domyślnie transakcyjny — wszystkie zmiany z jednego wywołania zapisują się atomowo albo wcale. Jawną transakcję obejmującą wiele operacji tworzysz przez db.Database.BeginTransaction().

Pułapki rekrutacyjne

Pułapka #1 — FromSqlRaw z interpolacją. EF Core jest bezpieczny, dopóki nie zejdziesz do surowego SQL sklejanego stringiem:

// ❌ podatne — interpolacja wkleja wartość do stringa
db.Users.FromSqlRaw($"SELECT * FROM Users WHERE Login = '{login}'");

// ✅ bezpieczne — FromSqlInterpolated zamienia {login} na parametr
db.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE Login = {login}");

Te dwie linie wyglądają niemal identycznie, a różnią się wszystkim. FromSqlRaw z $"..." to fałszywe poczucie bezpieczeństwa — ORM nie chroni, jeśli sam skleisz string.

Pułapka #2 — „ORM załatwia bezpieczeństwo”. Nieprawda w ogólności. LINQ owszem, ale każda ścieżka raw SQL (FromSqlRaw, ExecuteSqlRaw, SqlCommand ze sklejeniem) omija tę ochronę. Bezpieczeństwo daje parametryzacja, nie samo użycie ORM.

Pułapka #3 — AddWithValue i typy. AddWithValue bywa wygodne, ale pozwala bazie zgadywać typ parametru, co czasem psuje użycie indeksu (niejawna konwersja) i wydajność. W kodzie produkcyjnym lepiej podać typ jawnie (Add("@login", SqlDbType.NVarChar)). Bezpieczeństwo to nie osłabia — to kwestia wydajności.

Pułapka #4 — brak transakcji przy wielu zapisach. Dwa osobne SaveChanges() (albo dwa UPDATE) bez wspólnej transakcji mogą zostawić dane w stanie połowicznym, gdy drugi padnie. Operacje, które muszą się udać razem, obejmij jedną transakcją.

Bezpieczne vs niebezpieczne — ściąga

PodejścieBezpieczne?
Sklejanie stringów ("... '" + x + "'")❌ nigdy
$"... {x}" w FromSqlRaw / ExecuteSqlRaw❌ podatne
SqlCommand + Parameters✅ tak
Dapper z obiektem parametrów✅ tak
LINQ w EF Core✅ tak
FromSqlInterpolated✅ tak

Podsumowanie

SQL injection bierze się z jednej rzeczy — sklejania danych użytkownika z tekstem zapytania — i znika, gdy zamiast tego użyjesz parametrów: baza traktuje wtedy wartości wyłącznie jako dane, nigdy jako kod. W .NET masz to za darmo w LINQ (EF Core), w Dapperze (obiekt parametrów) i w ADO.NET (Parameters); jedyne miejsca podatne to ręcznie sklejany raw SQL, w tym zdradliwe FromSqlRaw($"..."). Do tego dochodzi integralność: operacje, które muszą się udać razem, obejmujesz transakcją, a SaveChanges() w EF Core jest transakcyjny domyślnie. Parametryzuj zawsze — to jednocześnie ochrona przed atakiem i szybszy, cache’owany plan zapytania.

Co dalej

Masz komplet fundamentów SQL dla .NET developera: zapytania, JOIN-y, indeksy i bezpieczeństwo. Teraz zobacz, jak Entity Framework Core generuje ten SQL za Ciebie (i jak podejrzeć, co naprawdę wysyła), oraz jak Dapper pozwala pisać go ręcznie — już z parametrami, bez ryzyka injection.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →