Secure Development Lifecycle
Websecurity Zonder Nablussen
Webrisico is zelden mysterieus. Het zit meestal in voorspelbare fouten die onder tijdsdruk blijven staan.
Bij Secure Development Lifecycle zit de meeste winst in veilige defaults die in elke release automatisch worden afgedwongen.
Dat maakt security minder een losse controle achteraf en meer een standaardkwaliteit van je product.
Directe maatregelen (15 minuten)
Waarom dit telt
De kern van Secure Development Lifecycle is risicoreductie in de praktijk. Technische context ondersteunt de maatregelkeuze, maar implementatie en borging staan centraal.
Shift Left: Beveiliging in het Ontwikkelproces
De term “shift left” verwijst naar het verschuiven van beveiligingsactiviteiten naar eerdere fasen in de ontwikkelcyclus. In een traditioneel watervalmodel zit beveiliging rechts – een penetratietest vlak voor de release. In een SDL-model zit beveiliging in elke fase, vanaf de eerste designbeslissing.
| SDL-fase | Activiteiten | Tools / Deliverables |
|---|---|---|
| Training | Security-awareness voor ontwikkelaars, OWASP Top 10, secure coding | OWASP WebGoat, Juice Shop, interne workshops |
| Requirements | Security requirements definiëren, privacy-eisen, compliance-mapping | Abuse cases, misuse cases, data classification |
| Design | Threat modeling, security architectuur review, crypto-keuzes | STRIDE, Attack Trees, security design patterns |
| Implementatie | Secure coding standaarden, peer review, SAST, pre-commit hooks | Semgrep, SonarQube, gitleaks, IDE-plugins |
| Verificatie | DAST, SCA, fuzzing, penetratietests, code review | OWASP ZAP, Nuclei, Trivy, pip-audit |
| Release | Final security review, SBOM genereren, incident response plan klaarzetten | CycloneDX, release gates in CI/CD |
| Respons | Vulnerability disclosure, patch management, post-incident analysis | PSIRT-proces, CVE-aanvraag, lessons learned |
De economie is meedogenloos duidelijk. IBM’s Systems Sciences Institute berekende dat een defect gevonden in de onderhoudsfase tot honderd keer duurder is om op te lossen dan hetzelfde defect gevonden tijdens het ontwerp. Voor beveiligingsdefecten is die factor nog hoger, omdat je daar de kosten van incident response, reputatieschade en eventuele boetes bovenop moet tellen.
Static Application Security Testing (SAST)
SAST analyseert broncode zonder de applicatie uit te voeren. Het vindt patronen die bekend staan als kwetsbaar: SQL-concatenatie (zie Web 01), ongevalideerde input in templates (zie Web 05), hardcoded credentials, onveilige cryptografische functies. Het is de digitale equivalent van een bouwkundig inspecteur die de bouwtekening controleert voordat er een steen gelegd is.
Tools per taal
| Tool | Taal / Framework | Open Source | Bijzonderheden |
|---|---|---|---|
| Semgrep | Multi-taal (Python, JS, Java, Go, Ruby, …) | Ja | Regelgebaseerd, snel, custom rules |
| SonarQube | Multi-taal | Community Edition gratis | Quality gates, technische schuld tracking |
| Bandit | Python | Ja | Specifiek voor Python security |
| SpotBugs + Find Security Bugs | Java | Ja | Bytecode-analyse |
| gosec | Go | Ja | Go-specifieke checks |
| Brakeman | Ruby on Rails | Ja | Framework-aware analyse |
| CodeQL | Multi-taal | Gratis voor open source | GitHub-integratie, dataflow-analyse |
Semgrep: custom rules
Semgrep is bijzonder krachtig vanwege de mogelijkheid om eigen regels te schrijven die specifiek zijn voor je codebase. Een regel die controleert of Flask-routes input valideren (zie Web 12 voor invoervalidatie):
# .semgrep/flask-sql-injection.yml
rules:
- id: flask-raw-sql-query
patterns:
- pattern: |
cursor.execute(f"...", ...)
- pattern-not: |
cursor.execute("...", (...))
message: >
Mogelijke SQL injection: gebruik parameterized queries
in plaats van f-strings. Zie Web 01.
languages: [python]
severity: ERROR
metadata:
cwe: "CWE-89"
owasp: "A03:2021 Injection"
confidence: HIGH
- id: flask-unvalidated-redirect
pattern: |
redirect(request.args.get(...))
message: >
Open redirect: valideer de redirect-URL tegen een allowlist.
languages: [python]
severity: WARNING
metadata:
cwe: "CWE-601"Lokaal draaien:
# Scan de hele codebase met je eigen regels
semgrep --config .semgrep/ --config p/owasp-top-ten .
# Alleen gewijzigde bestanden scannen (handig in CI)
semgrep --config .semgrep/ --config p/python \
$(git diff --name-only HEAD~1 -- '*.py')SAST in GitHub Actions
Een complete SAST-workflow die bij elke pull request draait:
# .github/workflows/sast.yml
name: SAST
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- name: Semgrep scan
run: |
semgrep ci \
--config p/owasp-top-ten \
--config p/python \
--config .semgrep/ \
--sarif --output semgrep.sarif
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
bandit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Bandit scan
run: |
pip install bandit
bandit -r src/ -f sarif -o bandit.sarif \
-ll # alleen medium en hoger
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: bandit.sarifLet op: SAST produceert false positives. Een SAST-tool die alles rapporteert is net zo nutteloos als een die niets rapporteert. Tune je regels. Begin met hoge severity, los die op, en verlaag geleidelijk de drempel. Een team dat wordt bedolven onder vijfhonderd SAST-waarschuwingen gaat ze negeren. Net als een autoalarm dat te vaak afgaat.
Dynamic Application Security Testing (DAST)
Waar SAST naar de broncode kijkt, test DAST de draaiende applicatie. Het stuurt verzoeken, observeert de reacties en zoekt naar kwetsbaarheden die alleen zichtbaar zijn in een live omgeving: verkeerde HTTP-headers (zie Web 11), ontbrekende CSP (zie Web 09), open redirects, en meer.
Tools
| Tool | Type | Open Source | Gebruik |
|---|---|---|---|
| OWASP ZAP | Full DAST | Ja | Actieve en passieve scanning |
| Nuclei | Template-based scanner | Ja | Snelle checks met community templates |
| Burp Suite Pro | Full DAST | Nee (commercieel) | Handmatige + geautomatiseerde tests |
| Nikto | Webserver scanner | Ja | Snelle config-checks |
ZAP in CI/CD
OWASP ZAP biedt een Docker-image dat direct in je CI/CD-pipeline past:
# .github/workflows/dast.yml
name: DAST
on:
workflow_run:
workflows: ["Deploy Staging"]
types: [completed]
jobs:
zap-scan:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.14.0
with:
target: "https://staging.example.com"
rules_file_name: ".zap/rules.tsv"
cmd_options: >
-a
-j
-config api.disablekey=true
-config scanner.threadPerHost=2
- name: Upload ZAP report
if: always()
uses: actions/upload-artifact@v4
with:
name: zap-report
path: report_html.htmlRegels configureren via een TSV-bestand om false positives te onderdrukken:
# .zap/rules.tsv
# Rule ID Action Description
10035 IGNORE Strict-Transport-Security Header Not Set (staging has no TLS)
10096 IGNORE Timestamp Disclosure
90033 WARN Loosely Scoped Cookie
Nuclei voor snelle checks
Nuclei is lichter dan ZAP en bijzonder geschikt voor geautomatiseerde checks op bekende misconfiguraties:
# Installeer nuclei
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
# Scan met OWASP-gerelateerde templates
nuclei -u https://staging.example.com \
-t http/misconfiguration/ \
-t http/exposures/ \
-t http/vulnerabilities/ \
-severity medium,high,critical \
-o nuclei-results.txtSoftware Composition Analysis (SCA)
Volgens schattingen bestaat 70 tot 90 procent van moderne applicaties uit open-source componenten. Je schrijft misschien tien procent van je code zelf; de rest is npm-packages, PyPI-libraries en Maven-dependencies die je installeert met een enkel commando en vervolgens nooit meer aankijkt. Tot iemand een CVE publiceert voor die ene logging-library die in je hele stack zit – Log4Shell, iemand?
SCA scant je dependencies op bekende kwetsbaarheden en licentie-issues.
Tools
| Tool | Ecosysteem | Open Source | Bijzonderheden |
|---|---|---|---|
| npm audit | Node.js | Ja (ingebouwd) | npm audit --omit=dev |
| pip-audit | Python | Ja | Gebruikt OSV-database |
| Trivy | Multi (OS, containers, code) | Ja | All-in-one scanner |
| Dependabot | Multi | Gratis (GitHub) | Automatische PR’s voor updates |
| Snyk | Multi | Gratis tier | Fix-suggesties, container scanning |
| Grype | Multi | Ja | SBOM-compatible, snel |
SBOM: Software Bill of Materials
Een SBOM is een ingrediëntenlijst voor je software. Twee standaarden:
- CycloneDX (OWASP) – XML of JSON, beveiligingsgericht
- SPDX (Linux Foundation) – ISO-standaard, licentiegericht
Genereren met Trivy:
# SBOM genereren in CycloneDX-formaat
trivy fs --format cyclonedx --output sbom.json .
# SBOM scannen op kwetsbaarheden
trivy sbom sbom.json --severity HIGH,CRITICALTrivy in GitHub Actions
# .github/workflows/sca.yml
name: SCA
on:
pull_request:
paths:
- "requirements*.txt"
- "package*.json"
- "go.sum"
- "Dockerfile"
jobs:
trivy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy vulnerability scan
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: "fs"
scan-ref: "."
format: "sarif"
output: "trivy-results.sarif"
severity: "MEDIUM,HIGH,CRITICAL"
ignore-unfixed: true
- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
pip-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: pip-audit
run: |
pip install pip-audit
pip-audit -r requirements.txt \
--desc \
--format json \
--output pip-audit-results.jsonSecret Scanning & Pre-commit Hooks
Er is een speciaal hoekje in de hel gereserveerd voor ontwikkelaars die AWS-keys committen naar een publieke repository. En dat hoekje is overvol. GitGuardian rapporteerde in 2024 meer dan 12,8 miljoen secrets gedetecteerd in publieke Git-commits. API-keys, database-wachtwoorden, private keys, OAuth client secrets – je kunt er een heel assessment mee vullen zonder ooit een exploit te hoeven schrijven.
Tools
| Tool | Aanpak | Open Source |
|---|---|---|
| gitleaks | Regex-patronen op Git-history | Ja |
| truffleHog | Entropie + regex, scant history | Ja |
| detect-secrets | Baseline-model, plugin-architectuur | Ja (Yelp) |
| GitHub Secret Scanning | Ingebouwd in GitHub (publieke repos gratis) | N.v.t. |
| GitGuardian | SaaS + CLI, real-time | Gratis tier |
Pre-commit configuratie met gitleaks
Pre-commit hooks draaien voordat code wordt gecommit. Als de scan een secret vindt, wordt de commit geblokkeerd. Installeer eenmalig en je voorkomt een categorie incidenten die je carrière kan ruïneren.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
- id: gitleaks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-added-large-files
args: ["--maxkb=500"]
- id: detect-private-key
- id: check-merge-conflict
- repo: https://github.com/PyCQA/bandit
rev: 1.8.3
hooks:
- id: bandit
args: ["-ll", "-q"]Installatie:
# Pre-commit framework installeren
pip install pre-commit
# Hooks activeren in je repository
pre-commit install
# Eenmalig alle bestanden scannen
pre-commit run --all-files
# Gitleaks los draaien op de hele Git-history
gitleaks detect --source . --verboseGitleaks custom config
Configureer gitleaks om specifieke patronen te detecteren of false positives te negeren:
# .gitleaks.toml
title = "Custom gitleaks config"
[extend]
useDefault = true
[[rules]]
id = "internal-api-key"
description = "Internal API key pattern"
regex = '''(?i)x-api-key\s*[:=]\s*['"]?[a-z0-9]{32,}['"]?'''
tags = ["api", "internal"]
[allowlist]
paths = [
'''tests/fixtures/''',
'''docs/examples/''',
]
regexTarget = "line"
regexes = [
'''EXAMPLE_KEY''',
'''test-api-key-not-real''',
]Code Review voor Security
Automated tooling vangt veel, maar niet alles. Business-logica-kwetsbaarheden – een IDOR waarbij gebruiker A de data van gebruiker B kan opvragen (zie Web 14 voor API-beveiliging), een race condition in een betalingsflow, een autorisatiecheck die ontbreekt op een admin-endpoint – ontsnappen aan elke scanner. Daar heb je menselijke reviewers voor nodig.
Security-gerichte review-checklist
Bij het reviewen van code, controleer minimaal het volgende:
| Categorie | Checkpunten |
|---|---|
| Authenticatie | Wachtwoorden gehasht met bcrypt/argon2? Geen hardcoded credentials? Brute-force bescherming? (Zie Web 10) |
| Autorisatie | Wordt autorisatie gecheckt op elke endpoint? Geen directe object-referenties zonder ownership-check? |
| Input validatie | Alle input gevalideerd en gesanitized? Parameterized queries? Template-variabelen? (Zie Web 01, Web 12) |
| Output encoding | Context-aware output encoding voor HTML, JS, URL? (Zie Web 02) |
| Cryptografie | Geen zelfgebouwde crypto? Actuele algoritmes (AES-256-GCM, Ed25519)? Geen ECB-mode? (Zie Web 13) |
| Error handling | Geen stack traces in productie? Geen gevoelige data in error messages? |
| Logging | Worden security-events gelogd? Geen wachtwoorden of tokens in logs? |
| Dependencies | Nieuwe dependencies gecontroleerd? Bekende kwetsbaarheden? Licentie-compatible? |
| Secrets | Geen API-keys, wachtwoorden of tokens in de code? Configuratie via environment variabelen? |
| HTTP-headers | Security headers aanwezig? CORS correct geconfigureerd? (Zie Web 11) |
Pull request template
Gebruik een PR-template dat reviewers dwingt om over security na te denken:
<!-- .github/pull_request_template.md -->
## Beschrijving
<!-- Wat doet deze wijziging? -->
## Security checklist
- [ ] Geen hardcoded credentials of secrets
- [ ] Input wordt gevalideerd en gesanitized
- [ ] Autorisatie is gecontroleerd op alle nieuwe endpoints
- [ ] Geen gevoelige data in logs of error messages
- [ ] Nieuwe dependencies gecontroleerd op CVEs
- [ ] SAST/SCA-pipeline is groen
## Test evidence
<!-- Hoe heb je getest dat dit veilig is? -->Security Champions Programma
Je kunt de beste tools installeren, de strakste CI/CD-pipelines bouwen en de meest volledige checklists schrijven – als geen enkele ontwikkelaar begrijpt waarom die maatregelen er zijn, worden ze als obstakel gezien en omzeild. Een Security Champions programma verankert beveiligingskennis direct in de ontwikkelteams.
Wat is een Security Champion?
Een Security Champion is een ontwikkelaar die – naast zijn reguliere werk – fungeert als aanspreekpunt voor beveiliging binnen zijn team. Het is geen voltijds security-engineer. Het is iemand die:
- De OWASP Top 10 kan uitleggen aan collega’s
- Security-relevante code review doet
- Nieuwe kwetsbaarheden vertaalt naar impact voor het team
- Escaleert naar het security-team wanneer nodig
- Deelneemt aan maandelijkse Security Champions meetups
Opzet
| Aspect | Invulling |
|---|---|
| Selectie | Vrijwilligers, minimaal 1 per team van 5-8 ontwikkelaars |
| Tijdsinvestering | 10-15% van de werktijd (4-6 uur per week) |
| Training | Initieel: 2-daagse workshop. Doorlopend: maandelijkse sessie |
| Tooling | Toegang tot SAST/DAST-dashboards, threat modeling tools, interne wiki |
| Erkenning | Opname in functioneringsgesprekken, security-certificeringen, conferentiebudget |
| Community | Maandelijkse meetup, Slack/Teams-kanaal, gezamenlijke CTF-deelnames |
Training-onderwerpen
- OWASP Top 10 en hoe je ze herkent in code (zie alle Web-hoofdstukken)
- Threat modeling in de praktijk (zie sectie 17.8)
- Secure coding patterns per taal/framework
- SAST/DAST-resultaten interpreteren en triagen
- Incident response basics: wat doe je als het misgaat
- Supply chain security en SCA
Threat Modeling
Threat modeling is het gestructureerd nadenken over wat er mis kan gaan, voordat het misgaat. Het is alsof je de route voor een autorit uitstippelt en van tevoren bedenkt waar de banden lek zouden kunnen gaan, in plaats van te wachten tot je met een klapband langs de snelweg staat.
Wanneer in de SDLC?
Threat modeling hoort in de design-fase, voordat er code geschreven is. Maar het is ook waardevol bij significante wijzigingen aan bestaande systemen – een nieuwe API, een extra integratielaag, een migratie naar de cloud.
Methodologieën
| Methode | Aanpak | Geschikt voor |
|---|---|---|
| STRIDE | Per component: Spoofing, Tampering, Repudiation, Information Disclosure, DoS, Elevation of Privilege | Systematische analyse van individuele componenten |
| PASTA | 7 stappen, risico-gebaseerd, business-context centraal | Complexe systemen met veel stakeholders |
| Attack Trees | Hiërarchische decompositie van aanvalsdoelen | Specifieke scenario’s diepgaand analyseren |
| LINDDUN | Privacy-gericht threat model | Systemen met persoonsgegevens (AVG/GDPR) |
STRIDE in de praktijk
Voor elke component in je architectuur stel je zes vragen:
Component: REST API voor gebruikersbeheer
[S] Spoofing - Kan iemand zich voordoen als een andere gebruiker?
→ Mitigatie: JWT-validatie, mutual TLS (zie Web 14)
[T] Tampering - Kan iemand data onderweg wijzigen?
→ Mitigatie: TLS 1.3, request signing (zie Web 13)
[R] Repudiation - Kan iemand ontkennen een actie uitgevoerd te hebben?
→ Mitigatie: Audit logging met tamper-evident storage
[I] Info Disclosure - Kan gevoelige data lekken?
→ Mitigatie: Output filtering, foutafhandeling (zie Web 12)
[D] Denial of Service - Kan iemand de service onbeschikbaar maken?
→ Mitigatie: Rate limiting, input-validatie (zie Web 14)
[E] Elevation - Kan een gebruiker admin-rechten verkrijgen?
→ Mitigatie: RBAC, principle of least privilege
Threat model als code
Voor teams die liever in code dan in diagrammen werken, biedt pytm (Python Threat Modeling) de mogelijkheid om threat models als code te schrijven:
# threat_model.py
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor
tm = TM("Webapplicatie Threat Model")
tm.description = "Threat model voor klantportaal"
internet = Boundary("Internet")
dmz = Boundary("DMZ")
internal = Boundary("Intern netwerk")
user = Actor("Gebruiker")
user.inBoundary = internet
web = Server("Webapplicatie")
web.inBoundary = dmz
web.protocol = "HTTPS"
web.sanitizesInput = True
web.encodesOutput = True
db = Datastore("Database")
db.inBoundary = internal
db.isEncryptedAtRest = True
user_to_web = Dataflow(user, web, "HTTPS requests")
user_to_web.protocol = "HTTPS"
web_to_db = Dataflow(web, db, "SQL queries")
web_to_db.protocol = "TLS"
tm.process()# Genereer threat model rapport
python threat_model.py --dfd | dot -Tpng -o threat_model.png
python threat_model.py --report threats.mdSamenvatting
- Shift left: hoe eerder je kwetsbaarheden vindt, hoe goedkoper de fix. Integreer beveiliging in elke fase van de SDLC, niet alleen aan het einde.
- SAST (Semgrep, Bandit, SonarQube) analyseert broncode op bekende kwetsbare patronen. Draai het in je CI/CD-pipeline bij elke pull request.
- DAST (OWASP ZAP, Nuclei) test je draaiende applicatie. Gebruik het tegen staging-omgevingen na elke deploy.
- SCA (Trivy, pip-audit, npm audit) scant je dependencies op bekende CVEs. Genereer een SBOM voor compliance en inzicht.
- Secret scanning (gitleaks, truffleHog) voorkomt dat credentials in je Git-history belanden. Pre-commit hooks zijn je eerste verdedigingslinie.
- Code review vangt business-logica-kwetsbaarheden die geen tool kan detecteren. Gebruik checklists en PR-templates om reviewers te sturen.
- Security Champions verankeren beveiligingskennis in ontwikkelteams. Zonder menselijke betrokkenheid worden tools genegeerd.
- Threat modeling (STRIDE, PASTA) dwingt je om voor het bouwen na te denken over aanvalsscenario’s. Doe het in de designfase, herhaal bij grote wijzigingen.
- De beste beveiliging is geen tool of configuratie – het is een cultuur waarin ontwikkelaars beveiliging als hun verantwoordelijkheid beschouwen, niet als het probleem van iemand anders.
Verder lezen in de kennisbank
Deze artikelen in het portaal geven je meer achtergrond en praktische context:
- API's — de onzichtbare lijm van het internet
- SSL/TLS — waarom dat slotje in je browser ertoe doet
- Encryptie — de kunst van het onleesbaar maken
- Wachtwoord-hashing — hoe websites je wachtwoord opslaan
- Penetratietesten vs. vulnerability scans
Je hebt een account nodig om de kennisbank te openen. Inloggen of registreren.
Gerelateerde securitymaatregelen
Deze artikelen bieden aanvullende context en verdieping: