Appearance
Scenario 2: SQL Injection & Reflected XSS
| Field | Value |
|---|---|
| Internal ID | 06 |
| Type | Offensive (attacker, from the browser) |
| Difficulty | Medium (level 2); lab time limit 45 minutes |
| Tasks | 5: four auto-scored milestones and one flag task, total 350 points |
| Lab surface | dvwa: the right-hand pane is the embedded DVWA browser (DvwaView.tsx) |
| Scored on | Browser (DVWA proxy signals); Kali history and files as a fallback |
| Guide | cyberrange/portal/public/scenarios/scenario_02_web_application_attack_sql_injection.md |
Objectives
The student attacks DVWA (Damn Vulnerable Web Application), running at security level Low, entirely from the browser through the portal's DVWA proxy, shown as an embedded pane next to the instructions. They demonstrate the two most common web weaknesses: SQL injection (reading data the application should protect) and reflected cross-site scripting (getting the application to run their script), then submit a capture-the-flag code shown on the XSS result page. No Kali or command line is needed for the primary scored path.
Concepts
How untrusted input reaches a SQL query and an HTML response, why input validation and output encoding matter, and the difference between reflected and stored XSS. The guide frames each milestone as a question the student makes the database or page answer.
Topology, roles and machines
| Machine | Address | Role | Reached by |
|---|---|---|---|
| dvwa | 10.0.(50+N).30 | Target web app and database | Portal proxy only (LXD proxy device on 10.115.77.1:18300+N) |
| Kali | 10.0.(50+N).10 | Optional fallback path | Browser terminal tab |
The DVWA proxy (portal/src/app/lab/dvwa/[[...path]]/route.ts, lib/dvwaProxy.ts) requires a portal session, allows only the SQL-injection, XSS, login, logout, security and asset paths, strips the rest of the DVWA menu, and rewrites HTML, cookies and redirects. Since pull request 256 it also signs the student in: when a page load is bounced to DVWA's login form, the proxy logs in to that student's own DVWA with the stock lab account, pins Security to Low and replays the request, so the lab opens ready to use; DVWA's own login page is the fallback if that fails (lib/dvwaAutoLogin.ts). Since pull requests 225 and 245 it injects a fluid stylesheet (/dvwa-theme/css/embedded.css) so DVWA fits the narrow pane.
The embedded pane (components/scenario/DvwaView.tsx) shows the target IP, a Required: Security Low badge and a strip with only five modules: Home, SQL Injection, XSS (Reflected), DVWA Security and Login. Home, Security and Login load inside the frame. SQL Injection and XSS (Reflected) open in a new top-level tab instead, because the proxy's CSP sandbox isolates them and only a top-level page keeps DVWA's SameSite=Lax session cookie on form submits, which the browser scoring needs. A side cue on Task 1, shown inline under the module strip since pull request 223, warns that DVWA Security must read Low and that the lab sets it.
Tools
A web browser is enough. The guide's optional fallback uses curl and sqlmap on Kali; both are preinstalled.
Vulnerabilities
- SQL injection in the DVWA "SQL Injection" module at security Low.
- Reflected XSS in the DVWA "XSS (Reflected)" module at security Low.
Both exist only in the lab's DVWA container, which is created from the dvwa-base golden image at security level Low and destroyed with the lab.
Milestones, checks and evidence
The primary path is browser scoring: as the student's requests and DVWA's responses pass through the portal proxy, detectBrowserMilestone() (portal/src/lib/dvwaScoreSignals.ts) looks for proof in the response and, on a match, calls POST /internal/browser-score with a shared secret. The API records one PASS per milestone (a unique index prevents duplicates).
| # | Milestone | Points | Browser signal (what proves it) | Kali fallback |
|---|---|---|---|---|
| 1 | Injection Point | 50 | An always-true payload returns at least two "First name" rows, or a quote produces a SQL syntax error | A real curl to the SQLi page with an injection payload, or a probe file showing an error or results |
| 2 | Database Extraction | 75 | A UNION SELECT against information_schema/database() whose result shows information_schema or dvwa | A users file with user:hash rows |
| 3 | Admin Hash | 100 | A UNION SELECT ... FROM users whose result contains a 32-hex hash | A real sqlmap dump command in history plus a 32-hex hash file |
| 4 | Reflected XSS | 75 | A request to the reflected-XSS page with a <script name, reflected as "Hello <script" in the response | A curl of the XSS page in history, or a proof file containing the reflection |
| 5 | Capture the Flag | 50 | Flag-only: the student submits the per-student code (adjective-noun-number, for example brave-otter-7421) that the XSS (Reflected) result page prints under the "Hello" line. Matched case-insensitively. | none |
Sources: dvwaScoreSignals.ts, pods_router.py (browser_score, _BROWSER_LABELS), check_scenario_6 in scoring_checks.sh, hybrid_scoring.py (PURE_FLAG_MILESTONES), flag_planting.py (generate_capture_flag).
Since pull request 232 the proxy waits up to 5 seconds for the API's answer. If the API rejects the score (for example HTTP 409 because the student's active lab is another scenario or is not active) or does not answer, a banner on the DVWA page says the attack worked but was not scored, instead of failing silently (browserScoreFailureHint() in dvwaScoreSignals.ts).
The browser scorer ignores placeholder values, and the Kali fallback ignores history lines that contain angle-bracket placeholders or echo/printf, so pasting the guide's examples verbatim does not score.
Completion detection
A browser PASS short-circuits Manual Check for that milestone (has_browser_pass in scoring.py). Task 5 scores when the code is submitted in the exercise panel. The scenario is complete when all five tasks have a PASS; the score is 350.
Logs and evidence
milestone_verificationrows withdetection_databeginningbrowser:for the browser path.- Instructor diagnostics (pull request 267, off unless
SCORING_DIAGNOSTICS_SCENARIOSincludes6): when a request is clearly an attempt at one milestone but did not score, the proxy posts only a reason code toPOST /internal/browser-diagnostic(same shared secret as browser scoring):SECURITY_NOT_LOW,PAYLOAD_NO_ROWS,QUOTE_NO_ERROR,UNION_NO_METADATA,UNION_NO_HASHorXSS_NOT_REFLECTED(browserNearMiss()). The payload and page are never sent, and a milestone already passed on that lab keeps its PASS. See Lifecycle & Scoring. - Per-student flags are planted in the DVWA database and web container as supplementary artifacts for Milestones 1 and 3.
- The Task 5 code is generated per student with an HMAC of
COHORT_FLAG_SECRET(generate_capture_flag()), and provisioning rewrites DVWA's Low-levelxss_r/source/low.phpso the result page prints it inline. IfCOHORT_FLAG_SECRETis missing, flag planting fails and Task 5 cannot be completed (D5). No SQL table of flags is seeded any more, so a SQL dump cannot return several candidate flags.
Standards mapping
| Framework | Mapping | Note |
|---|---|---|
| MITRE ATT&CK (portal) | T1190 Exploit Public-Facing Application | Applied to the whole scenario |
| OWASP | Not tagged in code | Closest: A03:2021 Injection (SQL injection and XSS) |
Documentation vs implementation
D9: there is still no rubric row for scenario 6 milestone 4; rubrics.py now states that M4 "stays browser-scored and needs no rubric". The rubrics endpoint lists M1 to M3 and M5. Browser and history scoring of M4 still work. See Open Questions.
Safety
DVWA is never published: it is reachable only through the session-gated portal proxy with a path allowlist. Even a working reflected-XSS payload here cannot read the portal's cookies, because the proxied page has no real origin. The automatic sign-in uses DVWA's stock lab account (already printed in the guide) and only ever reaches the DVWA of the requester's own active lab, found from their session (ownerActivePodId() in the proxy route). The container is unprivileged and destroyed at teardown.