Skip to content

ptai 1.1.0 — verification coverage + resilience (Juice Shop 0→12 verified)#20

Merged
0xSteph merged 14 commits into
mainfrom
1.0.1-safety-recall
Jun 29, 2026
Merged

ptai 1.1.0 — verification coverage + resilience (Juice Shop 0→12 verified)#20
0xSteph merged 14 commits into
mainfrom
1.0.1-safety-recall

Conversation

@0xSteph

@0xSteph 0xSteph commented Jun 26, 2026

Copy link
Copy Markdown
Owner

ptai 1.1.0 — verification coverage + resilience

Roughly doubles the vulnerability classes ptai can oracle-verify, and fixes the bug that made a scan of a live, otherwise-vulnerable app report 0 verified.

Added

  • Ten new verification oracles (each with a control that must fail on a safe target): trusted-header bypass, JWT alg:none, host-header poisoning, XXE, type confusion, stored XSS, sequential IDOR, mass-assignment, non-blind SSRF, SQLi login-bypass. ptai now verifies 14 distinct classes.
  • Path-traversal oracle (ptai demo → 4 findings, all 4 oracle-VERIFIED).

Fixed

  • Scope safety: active-tool targets (sqlmap/dalfox) are host-locked to the engagement target; the scan no longer feeds scraped third-party URLs to attack tools.
  • Verify resilience: the verify phase waits for a target to recover before running oracles, instead of failing every one against a target the sweep knocked over. This is what took OWASP Juice Shop from 0 → 12 oracle-verified in a scan.

Validation

  • Juice Shop scoped scan: 12 verified (JWT alg:none, BOLA, IDOR, type confusion).
  • Multi-vuln honeypot: 23 verified across 14 classes, 100% precision, 0 false positives.
  • Reproducible runs (honeypot + clean-app gates in the repo), not field false-positive rates.

Known gaps (honest)

  • Juice Shop's login SQLi verifies in isolation but not yet in a full scan (verify-phase context); the blind-OAST tier (blind SSRF/XXE, OOB SQLi) needs an OAST collaborator and is deferred.

0xSteph added 14 commits June 26, 2026 18:26
Scope safety: the web scan scraped external links from a target's content and
fed them to sqlmap/dalfox, actively testing third parties outside the engagement.
Active-tool targets are now host-locked to the engagement host. This also stops
the scan from overloading and crashing the target before the verify phase, which
is what produced 0 verified findings on otherwise-vulnerable apps.

Path-traversal oracle: a traversal finding is now oracle-verified (re-fetch the
payload, confirm the file content reproduces, benign control twin). ptai demo is
now 4 findings, all 4 oracle-VERIFIED.
A trusted-proxy header bypass is now oracle-verified: re-send the request with
the bypass header and confirm privileged 2xx access, re-send without it and
confirm denial (401/403). The with/without differential is the proof; a path
that returns 200 to both is not gated and abstains. Takes the external-honeypot
scan from 5 verified to 10 (the 5 critical X-Forwarded-User=admin bypasses).
… (recall cycle 2)

Three new verification oracles, each proving via an attack-vs-control differential
and reusing an existing OracleKind (no schema bump):
- jwt_alg_none: an unsigned (alg:none) token is accepted while a bogus-signature
  token is rejected.
- stored_xss: a unique canary planted via POST survives unescaped on read-back.
- host_header_poison: a poisoned Host reflects in the response while the legit
  host does not.

External-honeypot scan: verified 10 -> 14 (JWT alg:none x3 + host-header). The
stored-XSS oracle is sound and unit-green but does not yet fire on the live
stored-XSS findings end-to-end; the probe->oracle wiring is the next fix.
Comment/feedback sinks usually require auth, so the oracle now acquires a test
session once (setting the session cookie and a bearer header) before the
POST + read-back. Unit tests stub the acquisition to stay offline. (End-to-end
verification on the honeypot still needs the probe to emit the actually-winning
store param; tracked separately.)
- xxe: re-POST the XXE payload and confirm the disclosed file marker
  (root:x:0:0:) reproduces, with a benign no-entity control that must not.
- type_confusion: confirm a type-confused value (negative quantity, zero price)
  is accepted while an obviously-invalid control is rejected.
External-honeypot scan: verified 14 -> 17 (XXE + 2 type-confusion). Also
hardens the stored-XSS probe to emit the actually-winning store field; that
class still does not verify live end-to-end and stays tracked.
… status

The oracle tried JSON first and broke on its 200, but a server that reads form
fields returns 200 to a JSON POST while storing nothing, so the canary was never
planted and stored XSS never verified. Now it reads back after each body shape
(form first) and passes only when the raw canary survives unescaped. Stored XSS
verifies end-to-end: external-honeypot scan 17 -> 20 verified.
…l cycle 4)

Three oracles, each reusing an existing OracleKind (no schema bump):
- ssrf: a non-blind fetch endpoint echoes an attacker-chosen URL's content
  (marker present for the probe URL, absent for a control).
- mass_assignment: a privileged field persists on re-read after the write.
- sequential_idor: one session reads >=2 distinct user records by walking ids.
Wires the privilege_escalation_patch + idor_sequential probes to emit recipes.
External-honeypot scan: verified 20 -> 21 (sequential IDOR). SSRF and
mass-assignment verify in unit tests but not yet live end-to-end; tracked.
The prior with/without-field differential collapsed on a persistent state
mutation: once role=admin is written it stays admin, so N/N replay on a shared
record poisoned itself. The oracle now acquires its OWN fresh account each
attempt (non-privileged baseline), locates its own record (signup user_id, else
matching the signup email in the collection), writes the privileged value to
itself, and re-reads, passing iff the field flips. Meaningful (a regular user
escalated itself) and idempotent (each attempt is its own throwaway record).
External-honeypot scan: verified 21 -> 22.
Server-side fetchers that echo the fetched body let an attacker make the server
retrieve an arbitrary URL. The probe points the fetch parameter at the target's
own root (reachable internal content) and confirms the content echoes back while
an unreachable control does not, then emits the ssrf recipe the oracle re-proves.
Turns the prior discovery-only SSRF finding into a VERIFIED one.
External-honeypot scan: verified 22 -> 23 (14 distinct oracle-verified classes).
An aggressive sweep can knock a fragile single-container target over (Juice Shop
under load). The verify phase ran once, after the whole sweep, against whatever
was left, so a target that fell over mid-scan failed every oracle and the scan
reported 0 verified despite valid, replayable recipes. The verify phase now
health-checks the engagement target and waits for it to answer again before the
oracle loop; a healthy target sees no delay. Juice Shop scan: 0 -> 12 verified
(8 critical JWT alg:none, BOLA, 2 IDOR, type confusion), container survives.
Juice Shop's iconic SQLi (' OR 1=1-- in the login email field returns a valid
JWT) was detected but never verified: the probe omitted /rest/user/login from its
path list, and its recipe pointed at the boolean oracle, which only handles
GET-param SQLi. Adds /rest/user/login, a dedicated sqli_login_bypass oracle
(injection returns an auth token its false twin does not, N/N differential), and
points the recipe at it. Verifies 3/3 live against Juice Shop.
The probe can emit a (shape,payload) combo that tripped a non-token detection
signal but returns no token on replay (a form POST to Juice Shop's JSON-only
login). The oracle now tries the recipe's combo, then canonical JSON
' OR 1=1 --/' OR '1'='1 combos, passing on the first real token-vs-false-twin
differential. Juice Shop's login bypass now verifies via the fallback.
CI caught a test still asserting the probe emits the boolean oracle; the probe now
emits sqli_login_bypass. While fixing it, corrected the oracle's control: it used
the SQL false-twin, but a comment-based bypass (... --) logs in regardless of the
OR's truth, so the twin bypassed too and erased the differential (Juice Shop
returns a token for both). The control is now a benign non-injection login, which
fails normally. Juice Shop login bypass verifies 3/3.
Bumps to 1.1.0 (this is a minor release, not a 1.0.1 patch: it adds 10 new
oracle classes, the verify-resilience fix, and the scope-safety fix). Rewrites
the changelog into an honest 1.1.0 entry covering the new coverage, the fix that
took Juice Shop from 0 to 12 verified, and the validation numbers.
@0xSteph 0xSteph changed the title ptai 1.0.1 — scope safety + path-traversal oracle ptai 1.1.0 — verification coverage + resilience (Juice Shop 0→12 verified) Jun 29, 2026
@0xSteph 0xSteph merged commit 1658a0c into main Jun 29, 2026
6 checks passed
@0xSteph 0xSteph deleted the 1.0.1-safety-recall branch June 29, 2026 05:26
0xSteph added a commit that referenced this pull request Jul 5, 2026
ptai 1.1.0 — verification coverage + resilience (Juice Shop 0→12 verified)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant