Hacking labs
How to Create an Isolated Docker Network for Your Lab
Build a Docker network with no internet access, run OWASP Juice Shop on it, and publish a browser copy only on 127.0.0.1. Verify each step.
Structure a pentest report and write one finding someone else can reproduce, with evidence, impact, a CVSS v4.0 vector and a fix.
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.
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.
A practical structure has six parts:
Each finding answers the same questions, in the same order.
curl -i, with secrets removed. Number each piece of evidence so the steps can point to it.A finding that the reader cannot reproduce from the page alone is not finished, however good its title looks.
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.
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.
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:
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:NScore what you verified. If the attack depends on a condition you could not reproduce, the metric has to reflect that.
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:
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.
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.
Run:
docker psLook 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.
docker run -d -p 127.0.0.1:3000:3000 bkimminich/juice-shopThen check that the lab answers:
curl -i http://127.0.0.1:3000The status line should show a successful response code. Keep the output. It belongs in the Scope section as proof of the target you tested.
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.
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:
curl -i "http://127.0.0.1:3000/<path-you-tested>" > evidence-01.txtThe -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.
Create report.md in your lab repository and use this template. Replace each placeholder with what you actually observed:
## 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>Check what Git will record before you commit, so an unredacted file does not slip in:
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.
Hacking labs
Build a Docker network with no internet access, run OWASP Juice Shop on it, and publish a browser copy only on 127.0.0.1. Verify each step.
Hacking labs
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.
Hacking labs
Run OWASP Juice Shop in a Docker container bound to your own machine, confirm it responds, and stop it cleanly.