Ethical Hacking Tutorial with Examples.
Ethical hacking tutorial, lab-only: pentest scope, evidence, CVSS findings, reports, retests and disclosure. Hands-on offense lives on TryHackMe and HTB.
Beginner
Start here — no prior experience needed
Scope and Rules of Engagement: A Checker That Refuses
Turn a signed scope into scope.yaml and a Python checker that refuses out-of-scope, excluded and out-of-window targets before any tool runs.
Logging Test Activity and Hashing the Evidence
Keep a pentest activity log in UTC tied to the signed scope, hash every evidence file with SHA-256, and catch a one-byte change with a verify step.
Responsible Disclosure and security.txt (RFC 9116)
Publish an RFC 9116 security.txt, validate it with Python, compute a 90-day disclosure timeline, and read a bug-bounty scope like a scope letter.
Intermediate
For developers with core concepts down
Vulnerability Assessment: Scoring Findings with CVSS v3.1
Score findings with CVSS v3.1 in Python: vector to base score to severity, one metric at a time, the client's context, and a malformed vector.
Writing the Findings Report: From JSON to a Sorted Report
Turn pentest findings JSON into a Markdown report with an executive summary, sorted worst first, after fixing the bug of sorting severity as strings.
Retesting and Verifying Fixes Across Rounds
Compare pentest rounds and classify each finding as fixed, still open, new or regressed, after fixing the bug of matching findings by their title.
Also part of Ethical Hacking (methodology, lab-only)
Taught in other topics. Each link opens the lesson in its home topic.
Port Scanning Your Own Lab with nmap: Open, Closed, Filtered
Scan a container you own with nmap on a private Docker network: what open, closed and filtered mean, the packets behind each, and -sV version banners.
Host Firewalls with nftables: DROP Times Out, REJECT Refuses
Firewall a container's own port with nftables: a drop rule makes clients time out, reject makes them refused, and a source allow-list lets one host in.
Permission Bits, Ownership, and Why passwd Needs setuid
A real permission-denied error from a locked-down file, then stripping the setuid bit from /usr/bin/passwd to see exactly why an ordinary user can't change their own password without it.
Broken Access Control and IDOR: Fix It in Code, Pin It with a Test
A Flask API leaks another user's invoice. Add the ownership check that fixes it, and a pytest sweep that catches the next route to forget the check.
CSRF: Synchronizer Tokens and SameSite Cookies, Fixed and Tested
A logged-in user's cookie lets a forged POST move money. Add a per-session CSRF token, set SameSite=Lax, and pin both with a failing-then-passing pytest.
SSRF: Allowlists, Resolved-IP Checks and No Redirects
A link-preview endpoint fetches an internal admin page for anyone. Fix it with a host allowlist, resolved-IP checks and no redirects, under pytest.
Security Misconfiguration: Error Pages, Debug Mode and Security Headers
A Flask error page leaks a stack trace and a database password. Replace it with a generic handler, add security headers, and pin both with pytest.
Unsafe File Uploads: secure_filename, Allowlists and Size Limits
A Flask avatar upload writes outside its folder and accepts any file. Fix it with secure_filename, an extension allowlist and MAX_CONTENT_LENGTH.
Authentication Failures: Username Enumeration and Login Rate Limiting
A Flask login reveals which usernames exist and allows unlimited guesses. Fix it with one generic error, equal timing and a per-account attempt limit.
Testing Bearer Tokens, Basic Auth, and 401 vs 403
Extend the local test API with two auth schemes and catch the assumption that 'wrong token' and 'no token' return the same status code — they don't, and mixing them up hides real bugs.
Password Hashing Work Factors, Measured
Time SHA-256, PBKDF2, bcrypt, scrypt and argon2id on your own machine to see the guesses per second each allows, then store and upgrade argon2id hashes.
DAST with an OWASP ZAP Baseline Scan Against Your Own App
Run a passive OWASP ZAP baseline scan against a tiny Python app in its own container, read each warning, add the missing headers, and get to exit 0.