SQL injection w Prisma jest jedną z najczęstszych podatności, które mogą zagrozić integralności danych i poufności aplikacji. W artykule przedstawiamy krótki model zagrożenia, mechanizmy działania Prisma, porównanie kodu podatnego i poprawionego, checklistę mitygacji oraz odwołania do uznanych standardów, takich jak OWASP Top 10, CWE i zasada least‑privilege.
Model zagrożenia – jak powstaje SQL injection
Atakujący wstrzykuje własny kod SQL w miejsce danych wejściowych, które aplikacja przekazuje do bazy bez odpowiedniej walidacji lub parametryzacji. W kontekście Prisma najczęściej dzieje się to przy użyciu metod queryRaw lub executeRaw, które pozwalają na ręczne wstawianie fragmentów zapytań. Jeśli przekazujemy do nich niezweryfikowane ciągi znaków, otwieramy drzwi do manipulacji zapytaniem.
Podstawowy scenariusz:
- Użytkownik podaje wartość w formularzu (np. nazwa produktu).
- Aplikacja buduje zapytanie SQL, wstawiając tę wartość bez parametryzacji.
- Atakujący dodaje instrukcję
OR 1=1lubDROP TABLE, co prowadzi do nieautoryzowanego dostępu lub destrukcji danych.
Mechanizm ochrony w Prisma – parametryzacja i typowanie
Prisma ORM domyślnie generuje zapytania przy użyciu przygotowanych instrukcji (prepared statements). Dzięki temu wartości są przekazywane jako parametry, a nie wstawiane bezpośrednio do tekstu SQL. W TypeScript dodatkowo korzystamy z typów, które ograniczają możliwość wstrzyknięcia niepożądanego kodu.
Kluczowe mechanizmy:
- Query Builder – metody takie jak
findMany,createczyupdatebudują zapytania w sposób bezpieczny. - Parametryzowane RAW –
prisma.$queryRaw`SELECT * FROM User WHERE email = ${email}`używa interpolacji tagowanej, co automatycznie sanitizuje zmienną. - Typy Prisma – definiują strukturę danych i wymuszają poprawny format wejścia.
Przykład: kod podatny vs kod poprawiony
Poniżej porównujemy dwa fragmenty – najpierw niebezpieczny, potem bezpieczny.
// --- Kod podatny ---
const userInput = req.body.username; // dane z formularza
const raw = `SELECT * FROM "User" WHERE username = '${userInput}'`;
const result = await prisma.$queryRaw(raw);
W powyższym przykładzie userInput jest wstawiane bezpośrednio do łańcucha znaków, co umożliwia wstrzyknięcie dowolnego SQL.
// --- Kod poprawiony ---
const userInput = req.body.username;
// użycie tagowanej interpolacji zapewnia parametryzację
const result = await prisma.$queryRaw`SELECT * FROM "User" WHERE username = ${userInput}`;
Interpolacja tagowana (prisma.$queryRaw`...`) zamienia ${userInput} w parametr bindowany, eliminując ryzyko injection.
Checklista mitygacji – co wdrożyć w projekcie
- Używaj wyłącznie metod Prisma API (
findUnique,createitp.) zamiast ręcznych zapytań RAW. - Jeśli RAW jest niezbędny, stosuj tagowaną interpolację lub przekazuj parametry jako drugi argument (
prisma.$queryRaw(sql, params)). - Włącz walidację schematu wejścia (np.
zodlubclass-validator) przed przekazaniem danych do warstwy dostępu do bazy. - Ogranicz uprawnienia konta DB używanego przez aplikację – zasada least‑privilege.
- Monitoruj logi SQL i wykrywaj nieoczekiwane wzorce (np. nagłe użycie
DROP).
Odwołania do standardów – OWASP, CWE i least‑privilege
OWASP Top 10 wymienia A03:2021 – Injection jako jedną z najpoważniejszych kategorii. W katalogu CWE odpowiedzialnym za ten problem jest CWE‑89: SQL Injection. Implementując powyższe praktyki, spełniamy wymogi obu standardów.
Zasada least‑privilege wymaga, aby konto bazy danych używane przez Prisma posiadało jedynie niezbędne uprawnienia (SELECT, INSERT, UPDATE, DELETE na wymaganych tabelach). Unikaj przyznawania roli SUPERUSER lub DBA aplikacji produkcyjnej.
„Bezpieczne zapytania to nie dodatek, to podstawa – kod, który nie może zostać wstrzyknięty, nie wymaga późniejszych poprawek.”
Typowe błędy i kompromisy w praktyce
Najczęstsze pułapki:
- Używanie
prisma.$executeRawbez parametryzacji – łatwo przeoczyć, zwłaszcza przy migracjach. - Dynamiczne budowanie nazw tabel lub kolumn – Prisma nie pozwala na parametryzację identyfikatorów, więc trzeba je walidować w białej liście.
- Rezygnacja z walidacji wejścia na rzecz „zaufania” do frontendu – prowadzi do nieprzewidzianych wektorów ataku.
Kompromisy:
- W niektórych przypadkach konieczne jest użycie
RAWdla skomplikowanych raportów. Wtedy warto wydzielić tę warstwę do osobnego serwisu i otoczyć ją dodatkową warstwą sanitizacji. - Jeśli aplikacja musi obsługiwać zapytania ad‑hoc, rozważ użycie białej listy dozwolonych pól i operatorów zamiast swobodnego wstawiania tekstu.
Praktyczny przykład – checklistę wdrożeniową
// checklista.ts – przykładowa implementacja w TypeScript
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
const prisma = new PrismaClient();
// 1. Schemat walidacji wejścia
const userSchema = z.object({
username: z.string().min(3).max(30),
email: z.string().email(),
});
export async function createUser(payload: unknown) {
// 2. Walidacja
const data = userSchema.parse(payload);
// 3. Bezpieczne użycie Prisma API – nie RAW
return await prisma.user.create({
data: {
username: data.username,
email: data.email,
},
});
}
// 4. Przykład RAW z parametryzacją
export async function findUsersByDomain(domain: string) {
const safeDomain = domain.replace(/[^a-zA-Z0-9.-]/g, ''); // dodatkowa sanitizacja
return await prisma.$queryRaw`
SELECT * FROM "User" WHERE email LIKE ${'%@' + safeDomain}`;
}
Powyższy kod demonstruje, jak połączyć walidację schematu, użycie bezpiecznego API oraz parametryzowane RAW w sytuacjach, które tego wymagają.
Podsumowanie i zaproszenie do współpracy
Stosując opisane praktyki – parametryzację, typowanie, walidację i ograniczenie uprawnień – eliminujesz ryzyko SQL injection w projektach opartych na Prisma i TypeScript, jednocześnie spełniając wymogi OWASP Top 10 oraz CWE‑89. Jeśli potrzebujesz audytu bezpieczeństwa, wsparcia w hardeningu bazy danych lub pomocy przy wdrożeniu najlepszych praktyk, skontaktuj się z Coderia.it – nasz zespół specjalistów pomoże Ci zbudować solidną i bezpieczną architekturę backendową.



