Skip to content
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after any top-up. Until Nov 30.

How it works

How to Write a Good Pentest Report

Structure a pentest report and write one finding someone else can reproduce, with evidence, impact, a CVSS v4.0 vector and a fix.

Intermediate8 min readBy NotHoly

What a penetration test report is

A penetration test report is the written record of an authorized security test. It says what was tested, what was found, how a reader can confirm each finding, and what to change. The testing ends when the tester stops sending requests. The report is what the owner keeps, shares with developers, and uses to show that the work stayed inside the agreed scope.

NIST SP 800-115, the technical guide to information security testing and assessment, covers the whole process, including the analysis of findings and the development of mitigation strategies. The OWASP Web Security Testing Guide has a Reporting chapter that starts with a reporting structure and a naming scheme for findings. Read both when you can. This article focuses on the part you will write first: a clear report with one finding that someone else can reproduce.

Why the report matters

A real bug that nobody can reproduce rarely gets fixed. Developers need steps they can run from a clean state. Managers need to know what the risk means for the business. Auditors need proof that the work matched the authorization. One document has to serve all three readers.

The report also protects the tester. A written scope and a dated record of what was tested show that every action fell inside the authorization. Without that record, a tester who found a real flaw can end up arguing about whether the test was allowed at all.

The sections of a report

A practical structure has six parts:

  • Executive summary. One or two pages for readers who will not read the rest. It says what was tested, the overall risk, the most important findings in plain language, and the first actions to take. Write it last, even though it appears first.
  • Scope and authorization. The hosts, URLs and accounts that were in scope, what was excluded, the test dates, who authorized the work, and any limits, such as no denial-of-service testing.
  • Methodology. The approach and the tools, with versions, in enough detail that someone could repeat the work. State which checks were manual.
  • Findings. One entry per issue, ordered by severity. Every entry uses the same template.
  • Remediation. The fixes grouped by owner or theme, with priorities. Each fix has a check that shows it works.
  • Appendix. Raw evidence with secrets removed, full request and response captures, and a glossary for terms the audience may not know.

Writing one finding

Each finding answers the same questions, in the same order.

  • Title. What the flaw is and where it is. "Reflected cross-site scripting in the search parameter" tells a reader more than "XSS vulnerability". The cross-site scripting article explains the class of flaw behind that example.
  • Affected asset. The exact URL, endpoint, parameter, component or version.
  • Steps to reproduce. Numbered steps that a stranger can follow from a clean browser session. Include the exact request wherever a step depends on one.
  • Evidence. Proof that the steps worked: a screenshot, or the output of curl -i, with secrets removed. Number each piece of evidence so the steps can point to it.
  • Impact. What an attacker could do under the conditions you verified.
  • Severity. A CVSS v4.0 vector and its rating, computed from what you verified.
  • Remediation. A specific fix, and a check that shows the fix works.

A finding that the reader cannot reproduce from the page alone is not finished, however good its title looks.

Describe impact without exaggerating

Impact follows evidence. If you showed that a script ran in the browser of a test account in your own lab, write exactly that. A sentence such as "an attacker can take over every account" without a demonstration is an exaggeration, and a developer will notice it. It also costs you credibility on the next finding. When you did not verify something, say so and name the test that would confirm it.

Scanner output is a lead, not a finding

A scanner result tells you where to look. Before it goes into the report, reproduce it by hand, record the request and the response, and confirm the behavior yourself. You can name the scanner as the source of the lead. Do not paste its raw output as proof. It may be wrong, and the reader cannot tell which parts someone checked.

Score severity with CVSS v4.0

The CVSS v4.0 specification defines a Base metric group with two sides. The exploitability metrics are attack vector (AV), attack complexity (AC), attack requirements (AT), privileges required (PR) and user interaction (UI). The impact metrics cover the vulnerable system (VC, VI, VA) and the subsequent systems (SC, SI, SA). A vector string starts with CVSS:4.0 and lists every base metric.

The qualitative severity bands are None at 0.0, Low from 0.1 to 3.9, Medium from 4.0 to 6.9, High from 7.0 to 8.9, and Critical from 9.0 to 10.0.

Scores come in four flavors depending on which metric groups you used: CVSS-B for Base only, CVSS-BT for Base and Threat, CVSS-BE for Base and Environmental, CVSS-BTE for all three. A first report usually carries a Base score, so label it CVSS-B and do not present it as a full risk rating.

Use the official calculator at https://www.first.org/cvss/calculator/4.0 to build the vector and get the score. Put the vector in the report, not only the number, so the reader can see which assumptions you made. The string below shows the format only. It is not a score for any finding in your lab:

text
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

Score what you verified. If the attack depends on a condition you could not reproduce, the metric has to reflect that.

Redact secrets before you share

Evidence often holds session cookies, bearer tokens, passwords and personal data. Remove them before the file leaves your machine. Keep the header name and the shape of the value, so the reader can still follow the request:

http
Cookie: session=<REDACTED>
Authorization: Bearer <REDACTED>

Do not paste a live token into a chat, a ticket or a public repository, and do not commit a file that still contains one. If a secret was exposed in your own lab, rotate it there anyway and note the rotation in the report.

Hands-on: one finding from your own Juice Shop lab

You need the OWASP Juice Shop container from your lab, running on your own machine. This exercise touches only that local instance on 127.0.0.1. Do not run any of these steps against a site you do not own or have written permission to test.

1. Confirm the lab is local

Run:

bash
docker ps

Look at the port column. The mapping should begin with 127.0.0.1, which means only your machine can reach the service. If you see 0.0.0.0 or no address at all, stop the container and start it again with the loopback address in the publish flag.

bash
docker run -d -p 127.0.0.1:3000:3000 bkimminich/juice-shop

Then check that the lab answers:

bash
curl -i http://127.0.0.1:3000

The status line should show a successful response code. Keep the output. It belongs in the Scope section as proof of the target you tested.

2. Pick one issue you can reproduce

Choose one behavior that you tested in the browser or with a single request, and that you can describe in a few steps. Keep the scope small. One well-supported finding is more useful than eight unverified ones.

3. Capture the evidence

Replay the request with curl -i so the headers are visible. Replace the path with the one your finding uses, and save the output to a file:

bash
curl -i "http://127.0.0.1:3000/<path-you-tested>" > evidence-01.txt

The -i flag prints the response headers before the body. Read the status line, the Content-Type header, and any header your finding depends on. Then redact any token or cookie in the file before you commit it.

4. Fill in the template

Create report.md in your lab repository and use this template. Replace each placeholder with what you actually observed:

markdown
## Scope

- Target: http://127.0.0.1:3000, my own OWASP Juice Shop container
- Authorization: I run this lab on a machine I own
- Dates: <start date> to <end date>
- Out of scope: everything else

## Finding title

<Flaw> in <exact location>

## Steps to reproduce

1. <Start from a clean browser session at http://127.0.0.1:3000>
2. <Exact action or request>
3. <What you see>

## Evidence

<Screenshot file name, or the redacted curl -i output file, matched to the steps>

## Impact

<What you verified. What you did not verify, and the test that would check it.>

## CVSS vector

<CVSS:4.0 vector from the calculator, and its rating>

## Remediation

<The specific fix, and how to check that it works>

5. Commit the report

Check what Git will record before you commit, so an unredacted file does not slip in:

bash
git add report.md
git status
git commit -m 'First lab finding'

Confirm the commit with git log --oneline -1. The line should show your message. When you finish, stop the lab container with docker stop <container-name>, then run docker ps to confirm it is no longer listed.

Common mistakes

  • Starting with the impact. Write the steps first, then decide what they prove.
  • Steps that depend on a session the reader does not have. Start from a clean state and say so.
  • Evidence with no link to the steps. Number the evidence and cite the number in each step.
  • Pasting scanner output as proof.
  • Choosing severity by feel. Use the vector, and record the assumptions behind it.
  • Leaking secrets into screenshots, logs or commits.
  • A fix with no way to verify it.
  • Describing a test you did not run. The report says what happened, in order.
  • Spending most of the report on methodology. The findings section should be the largest part.

How to go further

  • Read NIST SP 800-115 in full. The publication is free from NIST.
  • Read the WSTG Reporting chapter, starting with the reporting structure and the naming schemes.
  • Read the CVSS v4.0 specification, metric by metric, before you score any finding that matters.
  • Give your finding to someone who did not write it and ask them to reproduce it from the steps alone. If they cannot, the finding is not done.
  • Read the rules of engagement before you test any system you do not own. The bug bounty rules of engagement article covers how programs set those limits.

Sources

Sources

  1. NIST SP 800-115, Technical Guide to Information Security Testing and Assessment
  2. OWASP Web Security Testing Guide, Reporting
  3. Penetration Testing Execution Standard, Reporting
  4. FIRST, CVSS v4.0 Specification Document
  5. FIRST, CVSS v4.0 Calculator

More in Hacking labs

Hacking labs

How to Build a Legal Home Lab

Build a home lab you are allowed to attack: a scope file, Docker ports bound to loopback, an isolated network, a notebook and a clean teardown.

Beginner4 min read