Database og applikationssikkerhed
SQL-injection: hold brugerdata ude af databasekoden
SQL-injection opstår, når data får lov at ændre selve databasekommandoen. Løsningen er ikke at forbyde apostroffer, men at holde SQL-tekst og dataværdier adskilt, begrænse dynamiske valg og kontrollere de databasekald, som appen faktisk bruger.
En fungerende formular beviser ikke, at SQL-kaldet er sikkert
Browseren kan kontrollere, om et felt ligner en email eller et ordrenummer. En bruger, integration eller ændret klient kan stadig kalde API'et direkte. Hvis serveren derefter bygger SQL med tekstsammenkædning eller interpolation, kan en dataværdi blive fortolket som SQL-syntaks i stedet for som data.
| Vej til databasen | Ubetroet værdi | Det der skal kontrolleres |
|---|---|---|
| Søgning og filtre | Søgetekst, status, dato og sortering | Parametre til værdier og faste valg til SQL-struktur |
| Rapporter og adminværktøjer | Kolonner, perioder og virksomhed | Samme kontrol som i den almindelige app |
| Import, webhook og API | Data fra filer og andre systemer | Ekstern data er stadig ubetroet input |
| Gemt indhold | Tekst, der tidligere er lagt i databasen | Bind værdien igen, når den bruges i et nyt SQL-kald |
Find alle steder, hvor appen bygger eller sender SQL
Gennemgangen skal følge data helt frem til database-driveren. Det er ikke nok at søge efter ordet SELECT, fordi SQL også kan ligge i et ORM-kald, en databasefunktion, et rapportværktøj eller et bibliotek, som sammensætter queryen.
- Driverkald: Find alle
query-,execute- og tilsvarende kald og se, om SQL og værdier sendes separat. - Raw SQL i ORM: Kortlæg sikre og usikre raw-metoder, tagged templates og hjælpefunktioner i den version, projektet bruger.
- Databasekode: Gennemgå funktioner, procedurer, triggers og dynamiske
EXECUTE-kommandoer. - Værktøjer uden for hovedappen: Medtag importjobs, planlagte opgaver, supportværktøjer, scripts og rapporter.
- Datakilder: Spor formularer, URL-parametre, headers, filer, webhooks, API-svar og allerede gemt tekst.
Bind dataværdier som parametre
En parameteriseret forespørgsel sender en fast SQL-tekst og værdier hver for sig. Driveren og databasen behandler derfor værdien som data. Den konkrete placeholder er forskellig mellem teknologier, men adskillelsen er den samme.
Usikkert: værdi bygges ind i SQL
const sql =
`SELECT id, name
FROM customers
WHERE name = '${name}'`;
await db.query(sql);Sikkert mønster: tekst og værdier adskilles
const sql =
`SELECT id, name
FROM customers
WHERE tenant_id = $1
AND name ILIKE $2`;
await db.query(sql, [tenantId, `%${name}%`]);tenantId skal komme fra den servervaliderede bruger- og virksomhedskontekst, ikke blindt fra request-body. Parameterisering gør queryen sikker mod SQL-injection, men giver ikke i sig selv brugeren adgang til den rigtige virksomhed eller handling.
Kolonnenavne og sortering kræver faste valg
Parametre repræsenterer dataværdier, ikke tabelnavne, kolonnenavne, sorteringsretning eller hele SQL-fragmenter. Hvis brugeren må vælge sortering eller rapportfelt, skal appen oversætte valget til en lille, fast liste af kodeejede SQL-fragmenter.
const columns = {
name: 'name',
created: 'created_at',
};
const directions = { asc: 'ASC', desc: 'DESC' };
const column = columns[request.query.sort];
const direction = directions[request.query.direction];
if (!column || !direction) throw new Error('Ugyldig sortering');
const sql = `SELECT id, name
FROM customers
WHERE tenant_id = $1
ORDER BY ${column} ${direction}`;
await db.query(sql, [tenantId]);Den dynamiske del kommer her kun fra faste værdier i kildekoden. En generisk “rensning” af et frit kolonnenavn er vanskelig at vurdere og bør ikke erstatte den eksplicitte mapping. Brug driverens dokumenterede identifier-funktion, når en legitim databaseopgave kræver dynamiske identifikatorer, og hold stadig valgmulighederne så afgrænsede som muligt.
Hold fire kontroller adskilt
Flere kontroller arbejder sammen, men de løser forskellige problemer. En grøn validering er ikke bevis for sikker SQL, og en parameteriseret query er ikke bevis for korrekt adgang.
- Validering
- Kontrollerer type, længde, interval og forretningsregel.
- Parameterisering
- Holder dataværdier ude af SQL-syntaksen.
- Autorisation
- Afgør om brugeren må udføre handlingen på de konkrete data.
- Mindste rettighed
- Begrænser hvad appens databasekonto kan gøre, hvis anden kode fejler.
Valider også betydningen af wildcard-tegn i søgninger. En parameteriseretLIKE-værdi kan være sikker mod injection og stadig give en meget bred eller dyr søgning, hvis appen ikke har en bevidst regel for jokertegn, længde, indekser og resultatgrænse.
ORM og databasefunktioner er ikke automatisk sikre
Et ORM eller en query builder parameteriserer ofte almindelige modelkald, men raw-API'er kan have andre regler. Prisma skelner eksempelvis mellem raw-metoder og advarer om de varianter, der accepterer usikker tekst. Følg dokumentationen for præcis den metode og version, projektet bruger; navnet “ORM” er ikke en sikkerhedskontrol.
- Raw query: Brug bibliotekets dokumenterede binding eller sikre tagged-template-mønster uden forudgående tekstsammenkædning.
- Stored procedure: Parameteriser kaldet og gennemgå procedurekoden; dynamisk SQL inde i proceduren kan stadig være sårbar.
- PostgreSQL-funktion: Brug
EXECUTE ... USINGtil dataværdier og kontroller identifikatorer særskilt. - Hjælpefunktion: En fælles wrapper skal bevare adskillelsen mellem querytekst og værdier, ikke skjule interpolation.
Test koden uden at eksperimentere i produktion
- 1. Søg i koden. Find SQL i template literals, sammenkædning, raw-metoder og dynamiske databasefunktioner. Et scanningsværktøj kan hjælpe, men beviser ikke fravær.
- 2. Følg værdierne. Dokumentér hvor hver dynamisk værdi kommer fra, og om den bliver bundet, mappet, valideret og autoriseret.
- 3. Test almindelige grænser. Brug apostroffer, Unicode, tomme værdier, lange men tilladte tekster, ukendte sorteringsvalg og forkert virksomhed.
- 4. Kør negative sikkerhedstests. Brug et afgrænset testmiljø med syntetiske data og en databasekonto, der ligner produktionens rettigheder.
- 5. Kontrollér svar og logs. Brugeren må ikke få SQL-tekst, tabellenavne, forbindelsesdata eller stack traces. Logs må ikke indeholde secrets eller unødvendige persondata.
- 6. Gem regressionstests. Test det konkrete endpoint og den konkrete query igen ved fremtidige ændringer.
Begræns konsekvensen med databaserettigheder
Appens normale runtime-konto bør ikke være databaseejer eller kunne ændre schemaet. Giv den kun nødvendige handlinger på nødvendige tabeller eller views. Brug særskilte, kontrollerede roller til migrering, administration og backup.
- Adskil produktion, test og udvikling med forskellige databaser, roller og secrets.
- Begræns læse- og skriverettigheder til de tabeller og handlinger, appen behøver.
- Brug views eller afgrænsede funktioner, når en rolle kun skal se bestemte felter.
- Overvåg databasefejl og afviste kald uden at logge rå credentials eller følsomme værdier.
- Rotér en lækket databaseadgang og undersøg både logs, dataintegritet og berørte kopier.
Typiske fejl
- Apostroffer fjernes: Gyldige navne ødelægges, mens andre måder at ændre queryen kan bestå.
- Queryen “forberedes” efter interpolation: Den usikre værdi er allerede blevet SQL-tekst, før driveren modtager den.
- Kun login gennemgås: Søgning, rapporter, import og adminfunktioner har samme risiko.
- Frit sorteringsfelt sættes ind: Værdiparametre kan ikke erstatte et kolonnenavn eller
ASC/DESC. - ORM antages sikkert: En raw- eller unsafe-metode omgår de normale modelkalds beskyttelse.
- Stored procedure får fripas: Proceduren bygger selv dynamisk SQL med sammenkædning.
- Parameterisering kaldes adgangskontrol: Queryen er syntaktisk sikker, men mangler stadig virksomhed eller ejer i filteret.
- Runtime bruger ejerrollen: En enkelt kodefejl får unødigt stor rækkevidde.
Tjekliste til sikre SQL-forespørgsler
- ☐ Alle driverkald, raw queries, databasefunktioner, scripts og rapportværktøjer er kortlagt.
- ☐ Dataværdier sendes separat fra SQL-teksten med den valgte drivers dokumenterede parameterbinding.
- ☐ Ingen ubetroet værdi sammenkædes eller interpoleres ind i SQL før parameteriseringen.
- ☐ Tabel, kolonne, sortering og andre SQL-fragmenter vælges fra faste, kodeejede mappings.
- ☐ Input valideres for type, længde, interval og forretningsregel uden at forbyde gyldige tegn som forsvar.
- ☐ Identitet, virksomhed, rolle og datapost autoriseres server-side for hver relevant handling.
- ☐ ORM'ets raw-metoder og eventuelle unsafe-varianter er gennemgået mod den aktuelle officielle dokumentation.
- ☐ Dynamisk SQL i funktioner og procedurer bruger parameterbinding til værdier.
- ☐ Appens runtime-rolle er adskilt fra ejer-, migration- og administrationsroller.
- ☐ Negative tests kører i et kontrolleret miljø med syntetiske data og realistiske rettigheder.
- ☐ Fejlsvar og logs afslører ikke SQL-tekst, schema, credentials eller unødvendige persondata.
- ☐ Kritiske queries har regressionstests, som følger med fremtidige ændringer.
Relaterede guides
Officielle kilder
Placeholder-syntaks, raw-metoder og sikker håndtering af dynamiske identifikatorer afhænger af database, driver og ORM. Kontrollér altid den aktuelle dokumentation for de konkrete biblioteker og versioner i projektet.