# Final Validation Report — OWASP Juice Shop Penetration-Test PoC Catalogue

**Target:** OWASP Juice Shop **v20.3.0**, local only `http://127.0.0.1:3000` · **Report date:** 2026-10-18 · **Source report:** `JuiceShop_PenTest_PoC.pdf` (65 findings)

## 1. Executive summary

This report independently validates all **65 findings** in the supplied penetration-test PoC catalogue against a locally-run OWASP Juice Shop v20.3.0 instance. Each finding was re-tested with finding-specific decision rules — an HTTP 200/500, a reflected string, or a reachable endpoint was never treated as proof by itself.

**Validated verdict totals (original 65):**

| Verdict | Count |
|---|---|
| Confirmed | 52 |
| Not Reproduced | 12 |
| Inconclusive | 1 |
| **Total** | **65** |

- **Confirmed: 52** — reproduced with evidence of the claimed security impact.
- **Not Reproduced: 12**, of which **4** rest on a demonstrably **invalid/flawed PoC** (the report's own cited evidence was the app shell or an error message), **2** describe **by-design/public** behavior, and **3** are **version-specific** (behaved differently on v20.3.0).
- **Inconclusive: 1** — the source report lacked a usable PoC.

> **Not-Reproduced does not mean fixed.** It means the documented PoC, executed faithfully (including a reconstructed/version-adjusted variant where justified), did not demonstrate the claimed impact on v20.3.0.

## 2. Scope, methodology & limitations

- **Scope:** one authorized local instance (`127.0.0.1:3000`). No external hosts; no off-host redirects; no high-volume/destructive testing. Disposable accounts/records used for state-changing tests.
- **Tooling:** Python `requests` for HTTP replay, Playwright (headless Chromium) for browser/XSS execution, `curl` for spot checks. No commercial scanners.
- **Evidence:** request/response artifacts stored with **secrets redacted** and chained under a SHA-256 hash ledger (not published here; see Evidence-quality caveats).
- **Limitations:** JWT forgery, XXE file-read, and SSRF-to-internal-services were **not** exploited (out of scope). Findings depending on seeded state that does not exist on a fresh instance are noted. All results are specific to **v20.3.0** and may differ from the report's original (earlier) version.

## 3. Confirmed findings

### 52 findings reproduced with impact

| # | Title | Sev | Verdict | Rationale |
|---|---|---|---|---|
| 1 | SQL injection authentication bypass (admin) on /rest | CRITICAL | Confirmed | HTTP 200; JWT identity=admin@juice-sh.op, role=admin |
| 3 | Authenticated access to the full user table (/api/Us | HIGH | Confirmed | HTTP 200; low-priv customer read 24 users (24 emails) |
| 4 | IDOR: authenticated user reads another user's basket | HIGH | Confirmed | HTTP 200; read basket id=1 (UserId=1) as a different user (my bid=None) |
| 5 | Mass-assignment lets a user self-register with role= | HIGH | Confirmed | HTTP 201; account created with role='admin' via API field |
| 6 | Privileged admin account created via mass assignment | HIGH | Confirmed | mass-assigned account performs admin action (config HTTP 200, /api/Users 200, 25 users) |
| 7 | Password reset via guessable security question (bend | HIGH | Confirmed | reset accepted (HTTP 200) using ONLY the security answer; login with the new password succeeded |
| 8 | Password reset via guessable security question (jim@ | HIGH | Confirmed | reset accepted (HTTP 200) using ONLY the security answer; login with the new password succeeded |
| 11 | NoSQL operator injection updates all reviews | HIGH | Confirmed | PATCH /rest/products/reviews accepted a NoSQL operator ($in) in the id filter and updated the matched review |
| 12 | SQL injection exposes soft-deleted products (Christm | HIGH | Confirmed | HTTP 200; UNION (close=')) , 10 cols — v20.3.0 added alternateImages) returned soft-deleted product names (Christmas, Special) |
| 13 | SQL injection login as a specific user (Bender) | HIGH | Confirmed | HTTP 200; JWT identity=bender@juice-sh.op |
| 14 | SQL injection login as amy@juice-sh.op | HIGH | Confirmed | HTTP 200; JWT identity=amy@juice-sh.op |
| 15 | SQL injection login as bjoern@juice-sh.op | HIGH | Confirmed | HTTP 200; JWT identity=bjoern@juice-sh.op |
| 16 | SQL injection login as chris.pike@juice-sh.op | HIGH | Confirmed | HTTP 200; JWT identity=chris.pike@juice-sh.op |
| 17 | SQL injection login as ciso@juice-sh.op | HIGH | Confirmed | HTTP 200; JWT identity=ciso@juice-sh.op |
| 18 | SQL injection login as morty@juice-sh.op | HIGH | Confirmed | HTTP 200; JWT identity=morty@juice-sh.op |
| 19 | SQL injection login as support@juice-sh.op | HIGH | Confirmed | HTTP 200; JWT identity=support@juice-sh.op |
| 20 | SQL injection login as user Jim | HIGH | Confirmed | HTTP 200; JWT identity=jim@juice-sh.op |
| 22 | UNION SQL injection in product search exposes full D | HIGH | Confirmed | HTTP 200; sqlite_master schema dumped (10 cols); CREATE TABLE statements returned |
| 23 | Encryption keys directory listing exposed at /encryp | HIGH | Confirmed | HTTP 200; directory listing served |
| 24 | Poison-null-byte bypass reads /ftp/package.json.bak  | HIGH | Confirmed | HTTP 200; dev backup file contents returned via %00 bypass |
| 25 | Reflected XSS payload accepted in product search | HIGH | Confirmed | javascript dialog with marker XSSMARK25 fired — reflected/DOM XSS executes in the browser |
| 26 | Deleted 5-star feedback via admin access | MEDIUM | Confirmed | admin DELETE /api/Feedbacks/10 succeeded (HTTP 200); our disposable feedback removed |
| 28 | Order details reachable by direct reference | MEDIUM | Confirmed | unauthenticated GET /rest/track-order/8ab2-1c2b419448ca008f returned the order's real data (products/total/email) created by another account |
| 29 | Order tracking exposes order data without authorizat | MEDIUM | Confirmed | unauthenticated GET /rest/track-order/8ab2-1c2b419448ca008f returned the order's real data (products/total/email) created by another account |
| 31 | Unauthenticated access to all customer feedback (/ap | MEDIUM | Confirmed | HTTP 200; 8 feedback records returned unauthenticated |
| 32 | Unauthenticated access to users' uploaded memories ( | MEDIUM | Confirmed | HTTP 200; 10 memory records returned unauthenticated |
| 33 | Weak password policy: single-character passwords acc | MEDIUM | Confirmed | HTTP 201; account created with a 1-character password |
| 34 | Weak/guessable credential login (MC SafeSearch) | MEDIUM | Confirmed | HTTP 200; login succeeded with the well-known weak credential |
| 35 | Ordered logically-deleted Christmas product | MEDIUM | Confirmed | soft-deleted product (id 10) added to basket (HTTP 200); checkout succeeded (HTTP 200) with an orderConfirmation |
| 36 | Zero-star feedback bypasses client-side rating valid | MEDIUM | Confirmed | HTTP 201; feedback stored server-side with rating=0 (client validation bypassed) |
| 38 | Product reviews endpoint exposes review data (NoSQL  | MEDIUM | Confirmed | HTTP 200; review data returned without authentication |
| 39 | Open redirect via /redirect allowlist bypass | MEDIUM | Confirmed | HTTP 302; Location -> https://evil.example.com/?x=https://github.com/juice-shop/ju (off-allowlist, not followed) |
| 40 | Application configuration exposed at /rest/admin/app | MEDIUM | Confirmed | HTTP 200; full application configuration returned |
| 41 | Full application configuration disclosed | MEDIUM | Confirmed | HTTP 200; full application configuration returned |
| 42 | Permissive CORS policy (Access-Control-Allow-Origin: | MEDIUM | Confirmed | Access-Control-Allow-Origin: * |
| 43 | Confidential acquisitions document served from /ftp | MEDIUM | Confirmed | HTTP 200; confidential acquisitions document served from /ftp |
| 44 | Poison-null-byte bypass reads /ftp/coupons_2013.md.b | MEDIUM | Confirmed | HTTP 200; coupons .bak contents returned via %00 bypass |
| 46 | Disallowed file type accepted by upload | MEDIUM | Confirmed | disallowed .txt accepted (HTTP 204); control .pdf=204 |
| 47 | Inventory quantities enumerable (/api/Quantitys) | LOW | Confirmed | HTTP 200; quantity inventory records returned unauthenticated |
| 48 | Product quantity records enumerable at /api/Quantity | LOW | Confirmed | HTTP 200; quantity inventory records returned unauthenticated |
| 51 | CAPTCHA answer exposed client-side at /rest/captcha  | LOW | Confirmed | HTTP 200; response includes the CAPTCHA 'answer' field client-side |
| 52 | Verbose server error / stack disclosure | LOW | Confirmed | malformed/unexpected input yields a 500 stack-trace page (same verbose handler as #59; leaks internal paths + Express version) |
| 54 | Security questions enumerable at /api/SecurityQuesti | LOW | Confirmed | HTTP 200; security questions enumerable unauthenticated |
| 55 | Swagger/OpenAPI documentation publicly exposed | LOW | Confirmed | HTTP 200 at /api-docs/; OpenAPI/Swagger doc exposed |
| 56 | Challenge continue-code endpoint exposed (/rest/cont | LOW | Confirmed | HTTP 200; challenge continue-code returned |
| 58 | Prometheus metrics exposed without authentication | LOW | Confirmed | HTTP 200; Prometheus metrics served without authentication |
| 59 | Verbose error page leaks stack trace and internal pa | LOW | Confirmed | HTTP 500 at /rest/qwertz returns a full stack trace (node_modules, juice-shop paths) and leaks 'Express ^4.22.1' |
| 60 | Poison-null-byte bypass reveals the hidden easter eg | LOW | Confirmed | HTTP 200; hidden easter-egg file read via %00 bypass |
| 61 | Static files browsable under /ftp (legal.md) | LOW | Confirmed | HTTP 200; static file under /ftp served directly |
| 62 | Oversized file accepted by upload | LOW | Confirmed | ~110KB file accepted (HTTP 204) |
| 64 | security.txt disclosure at /.well-known/security.txt | INFO | Confirmed | HTTP 200; security.txt served |
| 65 | Language list endpoint reachable (/rest/languages) | INFO | Confirmed | HTTP 200; language list endpoint reachable |

## 4. Not Reproduced / Invalid-Flawed PoC

### Invalid or flawed PoC (4) — the report's own evidence does not support the claim

| # | Title | Sev | Verdict | Rationale |
|---|---|---|---|---|
| 2 | Administrative API endpoints reachable (/rest/admin/ | HIGH | Invalid / Flawed PoC | HTTP 200 {"version":"20.3.0"}; /rest/admin/application-version is PUBLIC by design (no auth gate) — returning a version string is not an aut |
| 37 | Product reviews endpoint error/injection surface | MEDIUM | Invalid / Flawed PoC | GET /rest/products/reviews -> HTTP 500 is a routing/error response (no product id), not an injection result. The injectable NoSQL path is th |
| 45 | Server access logs exposed at /support/logs | MEDIUM | Invalid / Flawed PoC | HTTP 200 returns the SPA HTML (8892 bytes, text/html) — NOT logs. The PDF's cited 8892 bytes is this same SPA page, so the report mislabeled |
| 49 | Recycling requests enumerable at /api/Recycles | LOW | Invalid / Flawed PoC | HTTP 200; body is the 76-byte error 'endpoint is not supported', NOT records. The PDF's cited 76 bytes is this same error message. |

### By-design / public behavior (2)

| # | Title | Sev | Verdict | Rationale |
|---|---|---|---|---|
| 57 | Web3/NFT endpoint reachable (/rest/web3/nftUnlocked) | LOW | Not a Vulnerability (by design) | HTTP 200 body {"status":false}; reachable but returns status:false with no data or security-relevant effect. |
| 63 | Full product catalogue enumerable (/api/Products) | INFO | Not a Vulnerability (by design) | HTTP 200; 47 products returned — this is the public shop catalogue (public by design), not an access-control flaw. |

### Version-specific (3) — fixed/changed in v20.3.0

| # | Title | Sev | Verdict | Rationale |
|---|---|---|---|---|
| 9 | Persisted XSS via API bypasses client-side sanitizer | HIGH | Not Reproduced (version-specific) | overlays dismissed; product detail opened (iframe_in_dom=False) but payload did NOT execute — description rendered escaped/sanitized on v20. |
| 10 | Stored XSS via API-only product review | HIGH | Not Reproduced (version-specific) | overlays dismissed; product detail opened (iframe_in_dom=True) but review payload did NOT execute — reviews rendered escaped on v20.3.0. |
| 53 | User registration accepts empty email/password | LOW | Not Reproduced (version-specific) | HTTP 400; exact PDF payload rejected by server-side validation on v20.3.0 (PDF saw 201 on an older version). |

### Other Not Reproduced (3)

| # | Title | Sev | Verdict | Rationale |
|---|---|---|---|---|
| 27 | Deluxe membership purchase flow present (business-lo | MEDIUM | Not Reproduced | HTTP 400; deluxe not granted (payment/validation enforced). Body: {"status":"error","error":"Insuffienct funds in Wallet"} |
| 30 | Review author spoofing via NoSQL update | MEDIUM | Not Reproduced | PATCH HTTP 200 changed the message but the author stayed 'da_spoof_1792303794@test.local' — the author field is not writable via this endpoi |
| 50 | Notification replay endpoint reachable (anti-automat | LOW | Not Reproduced | HTTP 200 body 'OK'; endpoint reachable and returns OK but produces no security-relevant effect (only re-emits a UI notification). |

## 5. Inconclusive

### Inconclusive (1) — insufficient PoC in the source report

| # | Title | Sev | Verdict | Rationale |
|---|---|---|---|---|
| 21 | SQLi UNION forges accountant session | HIGH | Inconclusive | PDF documents NO request and NO response for #21 (endpoint=base URL, '(no request captured)', '(no response captured)', HTTP=None). Juice Sh |

## 6. Version-specific behavior, duplicates & adjusted payloads

- **Adjusted payloads:** #12 and #22 (SQLi UNION) — the report's **9-column** payload errors on v20.3.0 (an `alternateImages` column was added); both were Confirmed only with a **10-column** UNION. Original and adjusted payloads are recorded separately in evidence.
- **Duplicates / overlaps:** #41 duplicates #40 (`/rest/admin/application-configuration`); #48 duplicates #47 (`/api/Quantitys`). Counted once each in the 65 but flagged as overlapping.
- **Version differences:** #9/#10 (payload stored via API but escaped by the v20.3.0 frontend on render); #53 (empty-field registration rejected with HTTP 400 on v20.3.0, vs HTTP 201 in the report's version).

## 7. Evidence-quality caveats (must read)

- **#27 (Deluxe membership):** the **saved** evidence (`paymentMode` attempts) shows HTTP 400 → recorded **Not Reproduced**. A later manual probe using an **empty request body** indicated the membership can be obtained without payment (observed role `customer→deluxe`), **but that transcript was not captured as a saved evidence artifact**. This discrepancy is documented, not silently resolved; the original-65 total retains the saved-evidence verdict. Manual confirmation recommended.
- **N1 / N2 (supplementary, §8):** their transcripts are **in-memory probe output, not saved evidence artifacts**. N1's **financial effect remains unmeasured**. N2's **intended authorization policy is not assumed** — it is reported as an authorization *inconsistency* pending confirmation of the intended design.

## 8. Supplementary observations (OUTSIDE the original-65 totals)

These were discovered during validation and are **not** counted in the 65.

| ID | Area | Observation | Status | Caveat |
|---|---|---|---|---|
| N1 | Business logic | `POST /api/BasketItems` accepts a **negative quantity** (e.g. -50) and checkout returns an order confirmation | Partially verified | financial/total effect **not measured** |
| N2 | Authorization | A non-admin customer can `POST /api/Products` (HTTP 201, persisted); `DELETE` is admin-gated (401) — a create/delete authz **inconsistency** | Observed | intended policy **not assumed**; transcript not a saved artifact |

## 9. Independent-discovery notes (informational)

Earlier discovery also noted: missing **Content-Security-Policy** (no defense-in-depth for XSS); the JWT payload carries an **unsalted MD5 password hash**; **no login lockout/rate-limiting**; **no anti-automation on registration**. These are informational and separate from the 65.

---
*Generated from existing validation result files. No evidence, timestamps, or artifact paths were invented. Raw evidence artifacts and the source PDF are intentionally not published.*