Rapportage
Charles Darwin keerde in 1836 terug van zijn vijfjarige reis met de HMS Beagle. Hij had twintig notitieboeken vol observaties, honderden specimens, en een hoofd boordevol inzichten die de wetenschap voorgoed zouden veranderen. Maar eerst moest hij gaan zitten en het allemaal opschrijven. Het kostte hem meer dan twintig jaar om On the Origin of Species te publiceren. Niet omdat hij twijfelde aan zijn theorie, maar omdat hij wist dat de manier waarop hij het vertelde net zo belangrijk was als wat hij vertelde. Als niemand het begreep, had het geen zin.
Een penetratietest is, in zekere zin, net zo’n expeditie. Je trekt weken of maanden door onbekend digitaal terrein. Je vindt dingen die niemand eerder heeft gezien. Je maakt aantekeningen, verzamelt bewijsmateriaal, en bouwt langzaam een beeld op van hoe het systeem er werkelijk uitziet – niet zoals het in de architectuurtekeningen staat, maar zoals het echt is, met al zijn scheuren en lekkages. En dan moet je het opschrijven. Voor mensen die er niet bij waren. Voor mensen die misschien niet eens begrijpen wat een SQL injection is.
Dat is het moment waarop de echte uitdaging begint.
Waarom rapportage ertoe doet
Laten we eerlijk zijn: de meeste pentesters zijn niet in dit vak gestapt omdat ze dol zijn op schrijven. Ze zijn erin gestapt omdat ze dol zijn op breken. Op puzzels oplossen. Op dat moment waarop je na uren proberen eindelijk die shell krijgt en even heel stil wordt, gevolgd door een onderdrukt “yes” dat door een stille werkkamer galmt.
Maar hier is het ongemakkelijke feit: je rapport is het enige wat overblijft. Als je vertrokken bent, als je laptop is opgeruimd en je VPN-verbinding is verbroken, is dat rapport het enige bewijs dat je er bent geweest. Het is tegelijkertijd je visitekaartje, je factuur, en je nalatenschap.
En toch – en hier begint het pijn te doen – behandelen de meeste pentesters hun rapport alsof het een verplicht bijproduct is. Een vervelend formulier dat moet worden ingevuld voordat je aan de volgende klus kunt beginnen. Ze schrijven het in de laatste twee uur van het project, met een kop koude koffie en een vage herinnering aan wat ze drie weken geleden gevonden hebben.
Het resultaat? Rapporten die lezen alsof ze zijn geschreven door iemand die liever ergens anders was. Wat ook zo is.
De pentester als vertaler
Hier is iets wat niemand je vertelt op je OSCP-cursus: de belangrijkste vaardigheid van een pentester is niet technisch. Het is vertalen. Je bent een vertaler tussen twee werelden die elkaars taal niet spreken.
Aan de ene kant heb je de technische realiteit: CVE-nummers, CVSS vectors, stack traces, en hex dumps. Aan de andere kant heb je het management: mensen die beslissingen nemen over budgetten, prioriteiten, en risico’s, maar die niet weten wat een buffer overflow is en dat ook niet hoeven te weten.
Jouw rapport moet beide werelden bedienen. Het moet technisch genoeg zijn voor de developers om het probleem te vinden en te fixen. En het moet begrijpelijk genoeg zijn voor het management om te beslissen hoeveel geld en tijd ze eraan willen besteden.
Dat is geen gemakkelijke evenwichtsoefening. Het is eigenlijk twee rapporten schrijven in een. En de meeste pentesters kunnen precies een van de twee.
Je kunt de hele dag hacken, maar als je rapport waardeloos is, dan is het alsof je de mooiste foto ter wereld hebt gemaakt met de lensdop erop. Het bewijs bestaat, ergens in je hoofd, maar niemand anders zal het ooit zien. En als niemand het ziet, word het ook niet gefixt. En als het niet gefixt wordt, wat was dan het hele punt?
Precies.
IB Findings Management
Incompetent Bastard heeft een volledig findings management systeem ingebouwd. Niet omdat het leuk was om te bouwen, maar omdat het alternatief – bevindingen bijhouden in een spreadsheet, of erger nog, in je hoofd – het soort professionele nalatigheid is waarvoor dit boek is vernoemd.
De findings management blueprint (findings_bp) in IB is
het centrale zenuwstelsel van je rapportage. Hier komen alle bevindingen
samen, krijgen ze structuur, worden ze geclassificeerd, en worden ze
uiteindelijk omgezet in een rapport.
Het findings overzicht
Navigeer naar /dashboard/findings en je ziet het
overzicht. Bovenaan staan vier kaarten met statistieken: het totaal
aantal findings, het aantal templates, het aantal scoped items, en een
knop om het rapport te genereren. Simpel, overzichtelijk, en precies wat
je nodig hebt.
/dashboard/findings
Het scherm is opgedeeld in twee kolommen. Links: de beschikbare templates waarmee je nieuwe findings kunt aanmaken. Rechts: de findings die je al hebt vastgelegd, met hun ID, naam, host, en acties om te bewerken of te verwijderen.
Finding aanmaken: de formuliervelden
Elke finding in IB wordt vastgelegd met een formulier dat precies de juiste balans vindt tussen volledigheid en bruikbaarheid. Laten we de velden doorlopen:
Naam (naam): De titel van je finding.
Dit is wat in het rapport verschijnt als kop. Maak het beschrijvend maar
beknopt. “SQL Injection in login form” is goed. “Bug gevonden” is dat
niet.
Host (locatie): Het IP-adres of de
hostname waar de kwetsbaarheid is gevonden. Dit is cruciaal voor scoping
– je wilt weten welke systemen geraakt zijn.
CVSS 4.0 Vector (cvss): De CVSS 4.0
vector string die de ernst van de kwetsbaarheid beschrijft. Hierover
later meer.
CVSS Basescore (basescore): De
numerieke score die uit de vector wordt berekend. IB’s ingebouwde
calculator vult dit automatisch in.
User flag (gebruikersvlag): Voor
CTF-achtige engagements of als bewijs dat je user-level access hebt
bereikt.
Root flag (rootvlag): Idem, maar dan
voor root/admin-level access. Het bewijs dat je helemaal bovenaan de
berg stond.
Hoe kwam de bevinding tot stand?
(invoegen): Hier beschrijf je de aanvalsketen. Hoe ben je
van punt A naar punt B gekomen? Welke stappen heb je genomen? Dit is het
narratief van je aanval.
Werk de bevinding uit (uitwerken): Het
veld voor je gedetailleerde technische uitwerking. Dit is waar je
LaTeX-opmaak kunt gebruiken voor het rapport. IB biedt een toolbar met
LaTeX-shortcuts boven dit veld.
Het formulier is bewust compact gehouden. Geen vijftig velden waarvan je er dertig nooit invult. Geen verplichte dropdown-menu’s voor zaken die je niet weet. Gewoon de essentie.
IB Tip: Het
uitwerken-veld ondersteunt LaTeX-opmaak. Gebruik de toolbar boven het veld voor veelgebruikte commando’s zoals\begin{lstlisting}voor codeblokken en\plaatje{bestandsnaam}{caption}voor screenshots.
De workflow: van template naar finding
De workflow in IB werkt als volgt:
- Je selecteert een template uit de lijst links op het findings-overzicht
- Je klikt op “Add” naast de template
- Het formulier opent, met de template-referentie al ingevuld
- Je vult de specifieke details in: naam, host, evidence, flags
- Je slaat op en de finding verschijnt in het overzicht rechts
Dit template-systeem is de kern van efficiënte rapportage. In plaats van elke finding helemaal from scratch te beschrijven – de beschrijving, de impact, de aanbeveling – gebruik je een template die al het generieke werk bevat. Jij voegt alleen de specifieke context toe: waar vond je het, hoe vond je het, en wat is het bewijs.
# De route voor het toevoegen van een finding op basis van een template
@findings_bp.route('/dashboard/findings/add/<bevinding_id>', methods=['GET', 'POST'])
def bevinding_toevoegen(bevinding_id):
form = BevindingForm(ref=bevinding_id)
# ...Het ref-veld koppelt je finding aan de template. Wanneer
het rapport wordt gegenereerd, haalt IB de beschrijving, impact,
aanbeveling, en referenties uit de template, en combineert die met jouw
specifieke uitwerking.
Standaard finding templates
IB wordt geleverd met een set standaard finding templates. Deze
worden geladen vanuit standard_findings.json en bevatten
veertien veelvoorkomende kwetsbaarheidscategorieen:
| # | Template | OWASP | CWE |
|---|---|---|---|
| 1 | OS Command Injection | A03 - Injection | CWE-78 |
| 2 | Cross-Site Scripting (XSS) | – | – |
| 3 | XML External Entity (XXE) | – | – |
| 4 | SQL Injection (SQLi) | – | – |
| 5 | Path Traversal | – | – |
| 6 | Server-Side Template Injection (SSTI) | – | – |
| 7 | CORS Misconfiguration | – | – |
| 8 | Server-Side Request Forgery (SSRF) | – | – |
| 9 | Insecure Direct Object References (IDOR) | – | – |
| 10 | Security Headers | – | – |
| 11 | Vulnerable and Outdated Components | – | – |
| 12 | Broken Access Control | – | – |
| 13 | Cryptographic Failures | – | – |
| 14 | Insecure Design | – | – |
Elke template bevat:
- Beschrijving: Een uitgebreide uitleg van de kwetsbaarheid
- Impact: Wat een aanvaller ermee kan bereiken
- Aanbeveling: Concrete stappen om het te verhelpen
- Referenties: Links naar relevante bronnen
- OWASP classificatie: Mapping naar de OWASP Top 10
- CWE nummer: Common Weakness Enumeration identifier
- MITRE ATT&CK: Technique ID voor threat mapping
Dit zijn niet zomaar skeletjes. De templates bevatten complete, professioneel geschreven beschrijvingen met gestructureerde aanbevelingen in LaTeX-formaat. Neem bijvoorbeeld de OS Command Injection template: die bevat niet alleen een uitleg van wat command injection is, maar ook concrete aanbevelingen voor inputvalidatie, parameterized API’s, het least privilege principle, en meer.
IB Tip: Gebruik de “Laad standaard bevindingen” knop op het findings-overzicht om alle 227 standaard templates te laden vanuit het hacksec-patched project. Let op: dit overschrijft bestaande templates.
Het template datamodel
Onder de motorkap slaat IB templates op in de
db_bevindingen_templates tabel:
class db_bevindingen_templates(db.Model):
__tablename__ = 'db_bevindingen_templates'
id = db.Column(db.Integer, primary_key=True)
titel = db.Column(db.String(255))
bevtype = db.Column(db.String(255))
cwe = db.Column(db.String(5))
owasp = db.Column(db.String(255))
mitre = db.Column(db.String(10))
cvss = db.Column(db.String(255))
basescore = db.Column(db.String(10))
nlbeschrijving = db.Column(db.Text()) # Nederlandse beschrijving
enbeschrijving = db.Column(db.Text()) # Engelse beschrijving
nlimpact = db.Column(db.Text()) # Impact (NL)
enimpact = db.Column(db.Text()) # Impact (EN)
nlaanbeveling = db.Column(db.Text()) # Aanbeveling (NL)
enaanbeveling = db.Column(db.Text()) # Aanbeveling (EN)
referenties = db.Column(db.Text()) # ReferentielinksMerk op dat de templates tweetalig zijn. Elk template heeft velden voor zowel Nederlands als Engels. Het rapport wordt momenteel in het Nederlands gegenereerd, maar de structuur is er al voor meertalige ondersteuning.
OWASP classificatie
Elke finding in IB kan worden geclassificeerd volgens de OWASP Top 10 (2021 editie):
| Code | Categorie |
|---|---|
| A1 | Broken Access Control |
| A2 | Cryptographic Failures |
| A3 | Injection |
| A4 | Insecure Design |
| A5 | Security Misconfiguration |
| A6 | Vulnerable and Outdated Components |
| A7 | Identification and Authentication Failures |
| A8 | Software and Data Integrity Failures |
| A9 | Security Logging and Monitoring Failures |
| A10 | Server Side Request Forgery |
De OWASP classificatie wordt opgeslagen als een nummer (1-10) in de template en vertaald via een template filter naar de volledige beschrijving:
owasptop10 = [
'A1 - Broken Access Control',
'A2 - Crypthographic Failures',
'A3 - Injection',
# ... enzovoort
]
@app.template_filter('owaspcategorie')
def owaspcategorie(num):
if num:
return owasptop10[int(num)-1]
else:
return 'A5 - Security Misconfiguration'Dit is een elegant mechanisme. De template slaat alleen het nummer op, maar overal in de interface en het rapport verschijnt de volledige categorie met code.
Overigens: als je geen OWASP classificatie opgeeft, defaultt IB naar A5 – Security Misconfiguration. Dat is een aardige filosofische keuze. Als je niet eens de moeite neemt om een kwetsbaarheid te classificeren, dan is het per definitie een configuratie- probleem. Van jouw kant.
Status tracking
Het finding-model in IB is bewust minimalistisch gehouden. Er is geen apart status- veld in de database – de status van een finding wordt impliciet bepaald door het bestaan ervan. Een finding is “open” zodra die is aangemaakt, en “verwijderd” zodra je op delete klikt. Voor de tussenliggende statussen (verified, fixed, accepted) kun je de naam of het uitwerkveld gebruiken.
Dit is een bewuste keuze. In een pentest-tool die bedoeld is voor de tester zelf is een complex status-systeem overkill. Je bent geen Jira aan het bouwen. Je bent een rapport aan het schrijven.
Wil je toch statussen bijhouden, dan kan dat via de export/import
functionaliteit. Bij export naar JSON wordt elk finding object voorzien
van een status-veld:
{
"title": "SQL Injection in login form",
"status": "open",
"cvss_v4_vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"cvss_v4_score": 9.3,
"standard_code": "4",
"location": "192.168.1.100"
}CVSS 4.0 scoring
Als je ooit hebt geprobeerd om de ernst van een kwetsbaarheid uit te leggen aan een manager, dan weet je dat woorden tekort schieten. “Het is erg” is niet overtuigend genoeg. “Het is heel erg” is dat evenmin. Je hebt een getal nodig. Mensen houden van getallen. Getallen zijn objectief, meetbaar, en passen in een spreadsheet.
Dat getal is de CVSS score.
CVSS – het Common Vulnerability Scoring System – is een open raamwerk voor het communiceren van de ernst van softwarekwetsbaarheden. Versie 4.0, uitgebracht door FIRST in 2023, is de nieuwste incarnatie en brengt significante verbeteringen ten opzichte van versie 3.1.
De CVSS 4.0 vector string
Een CVSS 4.0 score wordt uitgedrukt als een vector string. Dit is een compacte textrepresentatie van alle metrics die samen de score bepalen. Hij ziet er zo uit:
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Laten we dat ontleden. De vector begint altijd met
CVSS:4.0/ als prefix, gevolgd door elf base metrics
gescheiden door slashes. Elke metric bestaat uit een afkorting en een
waarde, gescheiden door een dubbele punt.
De elf base metrics
CVSS 4.0 definieert elf verplichte base metrics, verdeeld over drie groepen:
Exploitability Metrics – Hoe makkelijk is de aanval?
| Metric | Naam | Waarden | Beschrijving |
|---|---|---|---|
| AV | Attack Vector | N/A/L/P | Vanwaar kan de aanval worden uitgevoerd? |
| AC | Attack Complexity | L/H | Hoe complex is de aanval? |
| AT | Attack Requirements | N/P | Zijn er voorwaarden nodig? |
| PR | Privileges Required | N/L/H | Welk toegangsniveau is nodig? |
| UI | User Interaction | N/P/A | Is gebruikersinteractie nodig? |
Vulnerable System Impact – Wat is de impact op het kwetsbare systeem?
| Metric | Naam | Waarden | Beschrijving |
|---|---|---|---|
| VC | Confidentiality | N/L/H | Impact op vertrouwelijkheid |
| VI | Integrity | N/L/H | Impact op integriteit |
| VA | Availability | N/L/H | Impact op beschikbaarheid |
Subsequent System Impact – Wat is de impact op andere systemen?
| Metric | Naam | Waarden | Beschrijving |
|---|---|---|---|
| SC | Confidentiality | N/L/H | Impact op vertrouwelijkheid van andere systemen |
| SI | Integrity | N/L/H | Impact op integriteit van andere systemen |
| SA | Availability | N/L/H | Impact op beschikbaarheid van andere systemen |
Dit onderscheid tussen “Vulnerable System Impact” en “Subsequent System Impact” is nieuw in CVSS 4.0 en lost een van de grootste frustraties van versie 3.1 op. In CVSS 3.1 had je een “Scope” metric die probeerde om in een enkel veld vast te leggen of een kwetsbaarheid impact heeft op andere systemen. Het was verwarrend, inconsistent, en de bron van eindeloze discussies.
CVSS 4.0 vervangt dat met zes aparte impact-metrics: drie voor het kwetsbare systeem zelf (VC, VI, VA) en drie voor downstream systemen (SC, SI, SA). Dat is veel preciezer.
Attack Vector in detail
De Attack Vector (AV) metric verdient speciale aandacht, want hij heeft de grootste invloed op de score:
- Network (N): De aanval kan over het netwerk, typisch internet. Dit is het ergste scenario. Een unauthenticated remote code execution met AV:N is het soort ding waar CISO’s wakker van liggen.
- Adjacent (A): De aanval vereist toegang tot hetzelfde netwerksegment. Denk aan ARP spoofing of aanvallen op Wi-Fi.
- Local (L): De aanvaller moet al lokale toegang hebben tot het systeem. Privilege escalation kwetsbaarheden vallen typisch in deze categorie.
- Physical (P): De aanvaller moet het apparaat fysiek kunnen aanraken. USB-based aanvallen, cold boot attacks, en dergelijke.
Attack Requirements: nieuw in 4.0
Een andere belangrijke toevoeging in CVSS 4.0 is Attack Requirements (AT). Deze metric vangt voorwaarden die buiten de controle van de aanvaller liggen:
- None (N): Geen speciale voorwaarden nodig. De aanval werkt altijd.
- Present (P): Er zijn voorwaarden nodig die de aanval niet altijd laten slagen. Bijvoorbeeld: een specifieke configuratie moet aanwezig zijn, of een race condition moet worden gewonnen.
IB’s ingebouwde CVSS 4.0 calculator
IB heeft een volledig functionele CVSS 4.0 calculator ingebouwd in het finding- formulier. Je hoeft geen externe website te openen of handmatig een vector te construeren.
De calculator verschijnt als een inklapbaar paneel in het finding-formulier. Klik op “CVSS 4.0 Calculator” om het paneel te openen. Je ziet drie secties met dropdown- menu’s voor elke metric:
+-----------------+ +--------------------+ +---------------------+
| Exploitability | | Vulnerable System | | Subsequent System |
| | | Impact | | Impact |
| Attack Vector | | | | |
| [N - Network ] | | Confidentiality | | Confidentiality |
| | | [H - High ] | | [N - None ] |
| Attack Complex. | | | | |
| [L - Low ] | | Integrity | | Integrity |
| | | [H - High ] | | [N - None ] |
| Attack Require. | | | | |
| [N - None ] | | Availability | | Availability |
| | | [H - High ] | | [N - None ] |
| Privileges Req. | | | | |
| [N - None ] | +--------------------+ +---------------------+
| |
| User Interact. |
| [N - None ] |
+-----------------+
Zodra je een metric wijzigt, stuurt de calculator een request naar de
server-side API (/api/cvss4/calculate) die de Python
cvss library gebruikt voor de berekening:
@findings_bp.route('/api/cvss4/calculate', methods=['GET'])
def cvss4_calculate():
CVSS4 = _get_cvss4_class()
vector = request.args.get('vector', '').strip()
# Validatie van prefix en verplichte metrics...
c = CVSS4(vector)
score = c.base_score
# Severity mapping
if score == 0: severity = 'none'
elif score < 4.0: severity = 'low'
elif score < 7.0: severity = 'medium'
elif score < 9.0: severity = 'high'
else: severity = 'critical'
return jsonify({'ok': True, 'score': score, 'severity': severity})De score en severity worden realtime teruggegeven en zichtbaar gemaakt in het formulier met een kleurgecodeerde badge.
Severity mapping
IB gebruikt de standaard FIRST severity schaal:
| Score | Severity | Badge kleur |
|---|---|---|
| 0.0 | None | Grijs |
| 0.1 - 3.9 | Low | Blauw |
| 4.0 - 6.9 | Medium | Oranje |
| 7.0 - 8.9 | High | Donker oranje |
| 9.0 - 10.0 | Critical | Rood |
Practical scoring: wanneer is iets Critical vs High?
De theorie van CVSS is helder. De praktijk is dat niet.
Hier is het probleem: CVSS is een technische score. Het meet de technische ernst van een kwetsbaarheid. Het meet niet de business impact. Een SQL injection in een testomgeving die geen echte data bevat, scoort technisch hetzelfde als een SQL injection in de productiedatabase van een bank. Maar het risico is uiteraard compleet anders.
Enkele richtlijnen voor het scoren in de praktijk:
Critical (9.0-10.0): Reserveer dit voor kwetsbaarheden die een aanvaller in staat stellen om zonder authenticatie, over het netwerk, volledige controle over het systeem te krijgen. Unauthenticated remote code execution. Pre-auth SQL injection die de hele database blootstelt. Dit zijn de dingen waarvoor je midden in de nacht belt.
High (7.0-8.9): Kwetsbaarheden die serieuze impact hebben maar enige beperking kennen. Misschien is authenticatie nodig, of werkt de aanval alleen onder bepaalde omstandigheden. Authenticated RCE. SSRF die toegang geeft tot cloud metadata.
Medium (4.0-6.9): Kwetsbaarheden die op zichzelf beperkte impact hebben, maar in combinatie met andere bevindingen gevaarlijk kunnen worden. Stored XSS. CSRF. Open redirects.
Low (0.1-3.9): Informatielekken, ontbrekende headers, verbose error messages. Dingen die een aanvaller helpen maar niet direct schade veroorzaken.
Het is verleidelijk om alles als Critical te scoren – het maakt je rapport indrukwekkender en je opdrachtgever nerveuzer. Maar als alles Critical is, is niets Critical. Het is de beveiligingsversie van het jongetje dat “wolf” riep. Uiteindelijk stopt iedereen met luisteren.
Evidence verzamelen
Een finding zonder bewijs is een mening. En meningen zijn niet wat je opdrachtgever betaalt. Ze betalen voor feiten, gedocumenteerd, reproduceerbaar, en zo onweerlegbaar dat niemand kan zeggen “dat kan bij ons niet, want wij hebben een firewall”.
Screenshots
Screenshots zijn het meest directe bewijs, maar ze zijn ook het makkelijkst om slecht te doen. Een paar regels:
Timing: Maak screenshots op het moment dat je de kwetsbaarheid vindt. Niet achteraf, wanneer je het rapport schrijft en de omgeving misschien al gewijzigd is.
Context: Een screenshot van een shell prompt zonder context bewijst niets. Zorg dat de screenshot laat zien waar je bent (welk systeem), hoe je daar gekomen bent (het commando dat je uitvoerde), en wat het resultaat is.
Annotatie: Markeer relevante delen van je screenshot. Een screenshot van een HTTP response van tweehonderd regels is waardeloos als je niet aangeeft welke drie regels ertoe doen.
Bestandsnamen: Gebruik beschrijvende namen.
screenshot_2024_01_15_sqli_login.png is bruikbaar.
img_234897.png is dat niet.
Request/response logs
Voor web-kwetsbaarheden zijn request/response logs vaak overtuigender dan screenshots. Ze laten exact zien wat je gestuurd hebt en wat je terugkreeg.
Bewaar altijd:
- Het volledige HTTP request (methode, URL, headers, body)
- Het volledige HTTP response (status code, headers, body)
- Relevante headers (met name
Cookie,Authorization,Content-Type) - Timestamps
In IB kun je deze logs opnemen in het uitwerken-veld van
je finding, met LaTeX- opmaak voor codeblokken:
\begin{lstlisting}
POST /login HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
username=admin' OR '1'='1&password=anything
\end{lstlisting}Proof of concept code
Voor complexere kwetsbaarheden is een proof-of-concept script het sterkste bewijs. Het stelt je opdrachtgever in staat om de kwetsbaarheid zelf te reproduceren en te verifieren dat een fix werkt.
Houd je PoC-code:
- Minimaal: Alleen wat nodig is om het probleem aan te tonen
- Gedocumenteerd: Met comments die uitleggen wat elke stap doet
- Veilig: Geen destructieve acties. Lees, maar schrijf niet
- Reproduceerbaar: Met duidelijke instructies voor het uitvoeren
IB Evidence upload
IB biedt een evidence-systeem waarmee je bestanden direct aan findings kunt koppelen. De evidence API ondersteunt uploaden, downloaden, en verwijderen:
POST /api/findings/<finding_id>/evidence # Upload een bestand
GET /api/findings/<finding_id>/evidence # Lijst alle evidence
GET /api/findings/evidence/<id>/download # Download evidence
DELETE /api/findings/evidence/<id> # Verwijder evidence
Ondersteunde bestandstypen zijn bewust breed gehouden:
- Afbeeldingen: PNG, JPEG, GIF, WebP
- Documenten: PDF, plain text, HTML, CSV
- Data: JSON, XML
- Overig: ZIP, binaire bestanden
De maximale bestandsgrootte is 10 MB per upload. Bestanden worden
opgeslagen in meuk/flask/db/evidence/<finding_id>/
met een UUID als bestandsnaam om conflicten te voorkomen:
_EVIDENCE_DIR = os.path.join(os.path.dirname(__file__), 'db', 'evidence')
_MAX_EVIDENCE_SIZE = 10 * 1024 * 1024 # 10 MB
stored_name = uuid.uuid4().hex + ext
dest_dir = _evidence_path(finding_id)
f.save(os.path.join(dest_dir, stored_name))De originele bestandsnaam wordt bewaard in de database zodat je bij het downloaden een herkenbaar bestand terugkrijgt, niet een string van 32 hexadecimale karakters.
Je kunt ook alle evidence in een keer exporteren als ZIP-archief via
/api/findings/evidence/export. Handig voor archivering of
als je de evidence wilt overdragen aan een collega.
IB Tip: Upload je evidence zo vroeg mogelijk. Doe het niet pas bij het schrijven van het rapport. Op dat moment ben je vergeten welke screenshot bij welke finding hoorde, en dan begin je te gokken. En gokken in een pentest-rapport is zoiets als gokken met je belastingaangifte: het kan goed gaan, maar als het misgaat is het echt vervelend.
Notes
Naast findings heeft IB een apart notitiesysteem. Notes zijn bedoeld voor lopende observaties, aantekeningen, en informatie die niet direct een kwetsbaarheid is maar wel relevant voor het rapport.
Denk aan:
- Opmerkingen over de scope en beperkingen
- Beschrijvingen van de testmethodologie
- Observaties over de algehele beveiligingshouding
- Verkenningsnotities die context bieden voor je findings
De Notes interface
De notes-pagina (/dashboard/notes) is opgedeeld in twee
secties:
Links: Een formulier om nieuwe notes toe te voegen. Elke note heeft een naam (titel) en een inhoudsveld met LaTeX-toolbar ondersteuning.
Rechts: Een lijst van bestaande notes met drag-and-drop functionaliteit om de volgorde te bepalen.
@notes_bp.route("/dashboard/notes", methods=["GET"])
def notes_page():
notes = db_notes.query.order_by(
db_notes.volgorde.asc(),
db_notes.id.desc()
).all()
return render_template("notes_overview.html", notes=notes)Notes in het rapport
Hier is het slimme deel: elke note heeft een “Rapport” toggle. Als je deze aanzet, wordt de note opgenomen in het gegenereerde rapport onder de sectie “Verkenning & ontdekking”.
Dit is een checkbox naast elke note in de lijst. De status wordt bewaard via een API-endpoint:
@notes_bp.route("/api/notes/<int:note_id>/toggle-rapport", methods=["POST"])
def notes_toggle_rapport(note_id):
note = db_notes.query.get_or_404(note_id)
note.rapport = not (note.rapport or False)
db.session.commit()
return jsonify({"ok": True, "rapport": note.rapport})Notes die in het rapport staan worden visueel gemarkeerd met een groene rand en een andere achtergrondkleur, zodat je in een oogopslag kunt zien welke notes mee worden genomen.
Volgorde bepalen
De volgorde van notes in het rapport wordt bepaald door drag-and-drop. Sleep een note omhoog of omlaag in de lijst, en de volgorde wordt automatisch opgeslagen:
@notes_bp.route("/api/notes/reorder", methods=["POST"])
def notes_reorder():
data = request.get_json(silent=True)
order = data["order"]
for idx, note_id in enumerate(order):
note = db_notes.query.get(note_id)
if note:
note.volgorde = idx
db.session.commit()
return jsonify({"ok": True})Dit is een kleine functie die een groot verschil maakt. In plaats van je rapport handmatig te herschikken nadat het is gegenereerd, bepaal je de volgorde vooraf.
IB Tip: Gebruik notes om de “verkenning”-sectie van je rapport op te bouwen terwijl je test. Maak een note voor elke fase: scoping, reconnaissance, enumeration, exploitation. Op het moment dat je het rapport genereert, hoef je alleen nog de “Rapport” toggles aan te zetten en de volgorde te bepalen.
Rapport generatie
Nu komen we bij het deel waar het allemaal samenkomt. Je hebt je findings vastgelegd, je evidence geupload, je notes geschreven. Het is tijd om er een rapport van te maken.
IB’s pandoc/LaTeX pipeline
IB gebruikt een pipeline die er in theorie eenvoudig uitziet maar in de praktijk behoorlijk ingenieus is:
Findings + Notes + Templates
|
v
LaTeX output (findings_nl.tex)
|
v
Regex transformatie (LaTeX -> HTML)
|
v
HTML tussenbestand (tex.html)
|
v
Pandoc conversie (HTML -> Markdown)
|
v
Markdown rapport (tex.md)
De route /dashboard/findings/rapport triggert het hele
proces. Laten we stap voor stap doorlopen wat er gebeurt.
Stap 1: Data verzamelen
bevindingen = db_bevindingen.query.group_by(db_bevindingen.ref).all()
notities = db_notes.query.filter_by(rapport=True).order_by(db_notes.volgorde.asc()).all()IB haalt alle findings op, gegroepeerd per template-referentie. Als je drie SQL injection findings hebt die allemaal naar dezelfde template verwijzen, worden ze samengevoegd onder een enkele sectie. Daarnaast worden alle notes opgehaald die als “rapport” zijn gemarkeerd, gesorteerd op volgorde.
Stap 2: Notes als LaTeX subsecties
De notes worden als eerste verwerkt. Elke note wordt een
\subsection in het rapport:
for notitie in notities:
notes = notes + '\\subsection{' + (notitie.naam or '') + '}\n' \
+ (notitie.uitwerken or '') + '\n\n'Stap 3: Findings renderen via Jinja templates
Elke groep findings wordt gerenderd via de
findings_nl.html template. Dit is een Jinja2 template die
LaTeX output genereert:
\subsection{SQL Injection in login form}
\label{bev001}
\bevindingkop{001}{A3 - Injection}{78}
[beschrijving uit template]
[uitwerking van de specifieke finding]
\subsubsection{Risico}
Risico inschatting
\subsubsection{impact}
[impact uit template]
\subsubsection{aanbeveling}
[aanbeveling uit template]
\subsubsection{Referenties}
[referenties uit template]
\newpage
Dit is de kern van het template-systeem. De generieke informatie (beschrijving, impact, aanbeveling) komt uit de template. De specifieke informatie (naam, evidence, uitwerking) komt uit de finding. Samen vormen ze een complete finding-sectie.
Stap 4: LaTeX naar HTML via regex
Nu komt het interessante deel. IB heeft een eigen LaTeX-naar-HTML converter geschreven met regex. Geen externe LaTeX compiler nodig. De code doorloopt het LaTeX-document en vervangt LaTeX-commando’s door HTML-equivalenten:
# Secties
tex = tex.replace('\\section{'+str(x)+'}', '<h1>'+str(x)+'</h1>')
tex = tex.replace('\\subsection{'+str(x)+'}', '<h2>'+str(x)+'</h2>')
tex = tex.replace('\\subsubsection{'+str(x)+'}', '<h3>'+str(x)+'</h3>')
# Lijsten
tex = tex.replace('\\begin{description}', '<ul>')
tex = tex.replace('\\end{description}', '</ul>')
tex = tex.replace('\\item', '<li>')
# Code
tex = tex.replace('\\begin{lstlisting}', '<pre>')
tex = tex.replace('\\end{lstlisting}', '</pre>')
# Afbeeldingen
tex = tex.replace('\\plaatje{'+x[0]+'}{'+x[1]+'}',
'<img src="../raw/screenshots/'+str(x[0])+'" alt="'+str(x[1])+'" />')De converter handelt ook cross-referenties af. Het
\uitwerking{bevN} commando wordt vertaald naar een
HTML-link die verwijst naar de bijbehorende finding, en het
\bevinding{bevN} commando doet hetzelfde. Dit betekent dat
je in je notes kunt verwijzen naar specifieke findings, en die
verwijzingen worden automatisch werkende links in het rapport.
Voetnoten worden eveneens geconverteerd. Elk
\footnote{tekst} wordt een genummerde referentie met de
voettekst onderaan het document.
Stap 5: HTML naar Markdown via pandoc
Het HTML-tussenbestand wordt opgeslagen als
rapport/tex.html. Vervolgens gebruikt IB pandoc om dit om
te zetten naar Markdown:
md = pandoc('rapport/tex.html', '-o', 'rapport/tex.md')Het Markdown-bestand krijgt een YAML front matter header:
---
title: "Pentest Rapport"
subtitle: "Rapportage"
author: Incompetent Bastard
date: today
...Stap 6: Het eindresultaat
Het proces levert drie bestanden op:
rapport/findings_nl.tex– De ruwe LaTeX outputrapport/tex.html– De HTML-versie (ook direct zichtbaar in de browser)rapport/tex.md– De Markdown-versie met YAML front matter
De HTML-versie wordt direct in de browser getoond na het genereren. De Markdown- versie kan vervolgens met pandoc worden omgezet naar PDF, DOCX, of welk formaat je maar wilt:
pandoc rapport/tex.md -o rapport/rapport.pdf \
--pdf-engine=xelatex \
--template=rapport/template.texWalkthrough: rapport genereren in IB
De complete flow in de praktijk:
- Ga naar
/dashboard/findings - Controleer of al je findings er staan en correct zijn
- Ga naar
/dashboard/notes - Zet de “Rapport” toggle aan voor alle relevante notes
- Bepaal de volgorde met drag-and-drop
- Ga terug naar
/dashboard/findings - Klik op “Generate report”
- Het rapport opent in een nieuw tabblad als HTML
- De bestanden staan klaar in de
rapport/directory
IB Tip: Het rapport wordt elke keer opnieuw gegenereerd. Er is geen caching. Dit betekent dat je gerust wijzigingen kunt aanbrengen aan je findings en notes, en simpelweg opnieuw op “Generate report” kunt klikken om de nieuwste versie te krijgen.
Import en export
IB ondersteunt het importeren en exporteren van findings als JSON. Dit is handig voor:
- Overdracht: Stuur je findings naar een collega of een ander systeem
- Archivering: Bewaar een snapshot van je findings buiten de database
- Hergebruik: Laad findings van een eerder project als startpunt
De export-route (/api/findings/export) genereert een
JSON-bestand met de volledige structuur:
{
"schema_version": "1.0",
"exported_at": "2026-02-23T14:30:00Z",
"project": { "name": "Incompetent Bastard" },
"catalogs": {
"ref_owasp_top10": [...],
"ref_cwe": [...],
"ref_mitre_attack": [...],
"standard_findings": [...]
},
"project_findings": [...]
}Dit formaat bevat niet alleen de findings zelf, maar ook de bijbehorende catalogi (OWASP, CWE, MITRE ATT&CK) en de standaard finding-definities. Dat maakt het een op zichzelf staand document dat je kunt importeren in een andere IB-instantie zonder dat er informatie verloren gaat.
Best practices rapportage
Nu we weten hoe IB’s rapportage werkt, laten we het hebben over hoe je een rapport schrijft dat daadwerkelijk gelezen wordt. Want dat is het doel, uiteindelijk. Niet het genereren van een PDF. Het schrijven van iets dat iemand leest, begrijpt, en naar handelt.
De executive summary
De executive summary is het belangrijkste onderdeel van je rapport. Het is ook het enige onderdeel dat gegarandeerd wordt gelezen. De rest van je rapport is voor de technische mensen. De executive summary is voor de mensen die beslissingen nemen.
Regels voor een goede executive summary:
- Maximaal een pagina. Als je meer nodig hebt, ben je niet beknopt genoeg.
- Geen jargon. Geen CVE-nummers, geen CVSS vectors, geen afkortingen zonder uitleg.
- Begin met het eindresultaat. “Wij hebben vastgesteld dat een aanvaller zonder voorkennis volledige toegang kan krijgen tot de database met klantgegevens.” Dat is een opening die aandacht trekt.
- Kwantificeer. “We hebben 12 kwetsbaarheden gevonden, waarvan 3 kritiek.” Getallen maken het concreet.
- Eindig met een aanbeveling. “Wij adviseren om de drie kritieke kwetsbaarheden binnen twee weken te verhelpen.”
Risico communicatie
Het communiceren van risico is een kunst die de meeste pentesters nooit leren. Ze beschrijven technische details en verwachten dat de lezer zelf de vertaling maakt naar business impact. Dat doet de lezer niet. Die heeft daar noch de tijd noch de kennis voor.
Goede risico-communicatie vertaalt techniek naar consequenties:
Niet: “De applicatie is kwetsbaar voor SQL injection
via de parameter id in het endpoint
/api/users.”
Wel: “Een aanvaller kan de volledige klantenbase downloaden, inclusief namen, emailadressen, en wachtwoorden. Gezien de 50.000 geregistreerde gebruikers, zou dit leiden tot een meldplicht datalekken bij de Autoriteit Persoonsgegevens.”
Het eerste is technisch correct. Het tweede is bruikbaar.
Remediation advies formuleren
Het verschil tussen een goed en een slecht rapport zit vaak in de aanbevelingen. Slechte aanbevelingen zeggen wat er mis is. Goede aanbevelingen zeggen wat er gedaan moet worden.
Slecht: “Fix de SQL injection.”
Beter: “Gebruik parameterized queries in plaats van string concatenation voor alle database-queries.”
Best: “Vervang de string concatenation op regel 47
van UserController.java door een PreparedStatement.
Voorbeeld:
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?"); ps.setInt(1, userId);”
Hoe specifieker je aanbeveling, hoe groter de kans dat die wordt uitgevoerd. Een developer die precies weet welke regel code hij moet wijzigen, doet dat eerder dan een developer die eerst moet uitzoeken waar het probleem zit.
Tijdlijnen en prioritering
Niet alle kwetsbaarheden zijn gelijk, en dat moet je rapport weerspiegelen. Geef elke finding een aanbevolen tijdlijn:
| Severity | Aanbevolen termijn |
|---|---|
| Critical | Binnen 24-48 uur |
| High | Binnen 1-2 weken |
| Medium | Binnen 1-3 maanden |
| Low | Bij volgende release |
Wees realistisch. Een organisatie met honderd developers kan drie critical findings in een week fixen. Een startup met twee developers kan dat niet. Pas je tijdlijnen aan aan de context.
De structuur van een goed rapport
Een compleet penetratietest-rapport bevat typisch de volgende secties:
- Executive Summary – Voor management. Maximaal een pagina.
- Scope en methodologie – Wat is er getest, hoe, en wanneer.
- Samenvatting bevindingen – Overzichtstabel met alle findings.
- Verkenning en ontdekking – Hoe je te werk bent gegaan (IB Notes).
- Gedetailleerde bevindingen – Elke finding met beschrijving, evidence, impact, en aanbeveling (IB Findings).
- Bijlagen – Screenshots, PoC-code, ruwe data.
IB’s rapportgeneratie dekt secties 4 en 5 automatisch. De executive summary en scope-beschrijving voeg je toe via notes.
De rapporten die niemand leest
We moeten het erover hebben. Want het is de olifant in de kamer van elke pentest-praktijk.
Je besteedt dagen, soms weken aan een rapport. Je formuleert elke zin zorgvuldig. Je maakt screenshots, schrijft PoC-code, berekent CVSS scores tot op een decimaal. Je levert een document af van vijftig pagina’s dat een volledig, reproduceerbaar beeld geeft van de beveiligingsstatus van de applicatie.
En dan leest niemand het.
Oh, ze zeggen dat ze het gelezen hebben. In de meeting knikken ze en zeggen dingen als “ja, we nemen dit zeer serieus”. Maar als je drie maanden later terugkomt voor een hertest, zijn dezelfde kwetsbaarheden er nog. Allemaal. Als trouwe huisdieren die geduldig op je terugkeer hebben gewacht.
De reden is simpel: rapporten die lezen als technische documentatie worden behandeld als technische documentatie. Ze worden opgeslagen in een map genaamd “Security Reports 2026” op een SharePoint die niemand ooit opent. Ze zijn het digitale equivalent van die stapel papieren op je bureau die je elke week van links naar rechts verplaatst en dan weer terug.
De oplossing is niet om betere rapporten te schrijven. Niet alleen, althans. De oplossing is om het rapport niet als eindproduct te zien, maar als het begin van een gesprek. Presenteer je bevindingen. Loop er doorheen. Laat het zien. Demonstreer de SQL injection live, terwijl de developers meekijken. Dat vergeten ze niet.
Een rapport is een geheugensteun voor een gesprek dat je al hebt gehad. Als je het gesprek overslaat, is het rapport slechts papier.
Of, in de context van IB, een hoop HTML in een browsertabblad dat iemand sluit tussen de vijftien andere tabbladen die hij toch al niet aan het gebruiken was.
Referentietabel
| Topic | IB Feature | Route/Bestand |
|---|---|---|
| Findings | Findings Management blueprint | /dashboard/findings |
| Templates | Standaard finding templates (14 stuks) | standard_findings.json |
| CVSS | CVSS 4.0 calculator | /api/cvss4/calculate |
| Evidence | Evidence upload/download/export | /api/findings/<id>/evidence |
| Notes | Notes blueprint met rapport-toggle | /dashboard/notes |
| Rapport | pandoc/LaTeX pipeline | /dashboard/findings/rapport |
| Export | JSON export van alle findings | /api/findings/export |
| Import | JSON import van findings | /api/findings/import |
| Evidence ZIP | Export alle evidence als archief | /api/findings/evidence/export |
Samenvatting
Rapportage is niet het saaie sluitstuk van een pentest. Het is het enige deel dat voortleeft nadat je bent vertrokken. Het is het verschil tussen een kwetsbaarheid die wordt gevonden en een kwetsbaarheid die wordt verholpen.
IB geeft je de gereedschappen om die rapportage efficient en gestructureerd te doen: templates die het generieke werk uit handen nemen, een CVSS 4.0 calculator die scoring objectief maakt, evidence management dat je bewijsmateriaal koppelt aan je findings, notes voor je verkenningsverhaal, en een rapport-pipeline die alles samenvoegt tot een document.
Maar het gereedschap is slechts het begin. Het verschil zit in hoe je het gebruikt. In de zorgvuldigheid waarmee je je findings formuleert. In de helderheid waarmee je risico’s communiceert. In het empathische vermogen om je te verplaatsen in de lezer die niet weet wat jij weet, en toch moet begrijpen waarom het belangrijk is.
Darwin had dat begrepen. Hij schreef niet voor biologen. Hij schreef voor iedereen. En daarom veranderde hij de wereld.
Jouw rapport hoeft de wereld niet te veranderen. Maar het moet wel een applicatie veiliger maken. En daarvoor moet iemand het lezen. Dus zorg dat het de moeite waard is.