Building a Disposable Web App Vuln Lab
Lab / Educational
Learning objectives
By the end of this lab you will be able to stand up a throwaway, network-isolated environment for reproducing a web application issue; confirm whether a finding is actually exploitable as deployed; observe the telemetry the activity generates; and destroy the whole environment cleanly so nothing lingers. The point is not any single exploit — it is building the safe workbench that every honest assessment depends on.
Required knowledge & prerequisites
- Comfort with a Linux shell and basic networking (IP addressing, DNS, HTTP).
- A host with a hypervisor: VirtualBox, VMware, KVM/libvirt, or Hyper-V.
- Roughly 8 GB free RAM and 30 GB disk for two small VMs.
- Installation media for a minimal Linux server (examples below use Debian 12).
Versions used in this write-up (illustrative). Debian 12.5, Docker 24.x, and a deliberately vulnerable training application (e.g. OWASP Juice Shop or DVWA) pulled from its official image. Verify current versions and image digests yourself before running — this guide was written without live access to check today's releases.
Lab topology & isolation
Two VMs on a single host-only (or “internal”) network. No NAT, no bridged adapter, no default gateway to the internet. This is the single most important control in the whole lab: if the vulnerable app cannot reach the internet, a mistake cannot escape into it.
# Conceptual layout (host-only network, e.g. 10.10.10.0/24)
# [ attacker-vm 10.10.10.10 ] <-- your tooling (curl, browser, scanner)
# [ target-vm 10.10.10.20 ] <-- the deliberately vulnerable app
#
# Host adapter is NOT attached to these VMs for internet access.
# Only attach a temporary NAT adapter to install packages, then REMOVE it.
The staged trick: attach a temporary NAT/bridged adapter only long enough to install packages and pull images, then detach it and switch to host-only before any testing begins. Document this as a deliberate step so it is repeatable and auditable.
Build steps
1. Target VM — install the vulnerable app in a container (keeps it contained and trivially resettable):
# On target-vm, with the temporary NAT adapter attached:
sudo apt-get update && sudo apt-get install -y docker.io
sudo systemctl enable --now docker
# Pull an OFFICIAL deliberately-vulnerable training image.
# (Confirm the current image name/tag from its official project page first.)
sudo docker pull bkimminich/juice-shop:latest # illustrative tag
# Now DETACH the NAT adapter and switch the VM to host-only networking.
# Run the app bound to the host-only interface only:
sudo docker run -d --name juice -p 10.10.10.20:3000:3000 bkimminich/juice-shop
2. Attacker VM — minimal tooling. You do not need a giant distro; a plain
Debian with curl, a browser, and one scanner is enough to practice the
triage workflow from the
finding vs.
exploitability article.
# On attacker-vm:
curl -s http://10.10.10.20:3000/ -o /dev/null -w "%{http_code}\n" # expect 200
Expected observations
These outputs are illustrative, not captured from a live run. A reachable target returns HTTP 200 on its landing page. A scanner will flag a list of candidate issues. Your job is then to pick one and actually prove it — e.g. submit a crafted input and confirm the application returns the result that a real vulnerability would produce, rather than trusting the scanner's signature match.
Why it works
Containment comes from two independent layers: the network (host-only, no egress) and the process (the app runs in a container you can discard). Neither alone is sufficient — a container with internet access can still exfiltrate or be abused as a pivot, and a host-only VM running the app directly is hard to reset cleanly. Together they give you a workbench that is both isolated and disposable, which is exactly what distinguishes a lab from a liability.
Defensive visibility
Offensive lab activity is also a telemetry generator. If you run a log pipeline (this is a natural place to use the Wazuh/OSSEC rule lookup tool), watch what your own attacks produce: spikes in 4xx/5xx responses, anomalous user-agents, repeated auth failures, and unusual request patterns. Knowing the signature of an attack from the inside is what lets you later recognize it in production logs.
Troubleshooting
- Target unreachable: confirm both VMs are on the same host-only network and the
app is bound to the host-only IP, not
127.0.0.1. - App can reach the internet: you left a NAT/bridged adapter attached. Detach it.
Re-verify with
curl -s https://example.comtiming out. - Container won't start: check
sudo docker logs juiceand that the port isn't already bound.
Cleanup & reset
# Reset the app to a pristine state:
sudo docker rm -f juice
sudo docker run -d --name juice -p 10.10.10.20:3000:3000 bkimminich/juice-shop
# Full teardown: delete the container, image, and finally the VMs/snapshots.
sudo docker rm -f juice
sudo docker image rm bkimminich/juice-shop
# Then delete the VM snapshots / disks from your hypervisor.
Snapshot the clean state before you start testing so a reset is one click, and delete the VMs entirely when the exercise is over.
Limitations & further reading
A lab proves exploitability in the lab. It does not prove your production deployment is identically configured — version drift, WAFs, and custom hardening all change the picture, which is why reproduction is one input to a judgment, not the whole verdict. Pair this with the triage workflow in Scanner Finding vs. Proven Exploitability.
Related
- Taking Over a Website Using Disguised WordPress Plugins Confirmed
- Unveiling the Dark Side of JavaScript Confirmed
- JavaScript Without the Dark Side Confirmed
- Scanner Finding vs. Proven Exploitability Lab / Educational