# Blackbox Web Audit

> Authorized black-box web/API audit WITHOUT test accounts — unauth probing discipline, unauth-IDOR rounds using leaked IDs, JS-bundle-first attack surface mapping, non-destructive gates, and local report delivery conventions (no zip, follow sibling example folder). Use when asked to audit/pentest a target where you hold no credentials, and when writing the local report deliverable afterwards.

- Skill: `zyrexnn/blackbox-web-audit` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add zyrexnn/blackbox-web-audit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zyrexnn/blackbox-web-audit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: zyrexnn (https://skillmd.com/u/zyrexnn)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zyrexnn/blackbox-web-audit

---


# Black-Box Web Audit Without Credentials

Class workflow for authorized audits where you have NO test account (operator-requested or VDP scope). Complements the manually-authored `bb-methodology` (5-phase workflow) and `bug-bounty-reporting-workflow` (validation standard) — load those too; this skill adds the no-credentials specifics they lack.

## Hard Constraints (non-destructive discipline)

1. **GET-only on anything state-changing-adjacent.** Shutdown/reset/redeem endpoints: probe with GET even if the app uses POST; NEVER send the real state-changing verb on production (a `POST /api/version/shutdown` was deliberately never sent).
2. **Fake IDs for write-path probes.** Create-order/payment endpoints tested only with nonexistent UUIDs; expect-and-document 401.
3. **Credentials are always fake** for rate-limit/login tests. A quarantine of your own IP after 429 is *evidence the control works* — record it as such.
4. Embedded keys found in bundles get **tested live against their real API** before any claim (Firebase: `identitytoolkit.googleapis.com/v1/accounts:createAuthUri?key=` → 400 "API key not valid" = revoked, informational only).

## Pre-Flight: Authorization Check

Before any probing, check for a public bug bounty program (HackerOne/Bugcrowd/Intigriti) and `/.well-known/security.txt` on apex + www. If NEITHER exists, downgrade to passive/non-intrusive observation only (CT logs, header analysis, unauth GETs) and state that explicitly in the report's legal-notes section — findings from active testing of a no-program target are unreportable and create liability.

## Attack Surface When You Have No Account

Subfinder/gau often return near-zero on these targets. Two maps work:

**Map A — CT-log subdomain sweep (do this FIRST, ~2 min):**
1. `curl "https://crt.sh/?q=%25.DOMAIN&output=json"` → on 502/malformed (frequent), fall back to `https://api.certspotter.com/v1/issuances?domain=DOMAIN&include_subdomains=true&expand=dns_names` (returned 24 hosts when crt.sh was down).
2. Probe every host in one bash loop: status, redirect target, `<title>`, Server/X-Powered-By headers; tee to `recon/<target>/host_probe.txt`.
3. High-yield host classes: `staging-*`/`*dev*` (note auth gates), `mail.*` (webmail brand/version), `api-*`/`apps.*` (swagger candidates), odd personal-looking names (leftover deployments).
4. On ASP.NET Core hosts try `/swagger/index.html` then `/swagger/v1/swagger.json` (also `/swagger/doc.json`). A 200 swagger.json with empty `security`/`securitySchemes` = Medium documentation-exposure finding; enumerate paths read-only, never send the documented POSTs.

**Map B — the JS bundle** when a SPA exists:

1. Download all `/_next/static/chunks/*.js` (or equivalent SPA bundles) to disk; never `cat` minified files — grep them.
2. Extract endpoint inventory (regex `/api/[a-z0-9/_-]+`, quoted AND backtick strings) → typically 50–100 endpoints from a ~1MB bundle.
3. Probe each with GET, no auth. **Verify content-type on EVERY probe**: Next.js/SPA backends answer unknown `/api/*` paths with HTTP 200 `text/html` (index fallback). Only `application/json` counts; HTML fallback ≠ open endpoint. This one check kills most false-positive "exposure" claims. Same trap via redirect chains: routers 307-normalize paths first (Go: `/render/https://x` → `/render/https:/x`), and following the redirect lands on the SPA catch-all 200 — a ffuf "hit" that looks like an open SSRF/render proxy. Before claiming ANY discovered path does something server-side, diff its body against the app's index.html baseline (byte size + `<title>`): identical shell = fallback route, not a feature (real case: `/render/<url>` flagged by ffuf, killed as SSRF after body-diff, 2026-08-23).
4. Mine JS for: hardcoded config (`authMode`, OIDC labels), sandbox/test features shipped to prod (`simulate-payment`, debug flags), embedded keys/client IDs.
5. Rate-limit test: N sequential bad logins to `POST /api/auth/login` → document lockout window (`retryAfter`) as a verified control.

## Origin Exposure via SPF (Cloudflare bypass — highest-yield single technique)

When the target sits behind Cloudflare, mine DNS for the origin IP before scanning:
1. `dig TXT domain` / DoH — an SPF record with `ip4:` names the mail/origin server outright (`v=spf1 ip4:ORIGIN ~all` leaked it in one engagement).
2. Verify multi-vhost serving: `curl -skI --resolve host:443:ORIGIN https://host/` for EVERY known hostname (apex, my., api., app., plus any gateway domains mined from JS bundles). One origin commonly serves all vhosts AND sibling products.
3. Confirm via SNI cert mismatch: `openssl s_client -connect ORIGIN:443 -servername host` returning a cert for a DIFFERENT domain = shared origin proven. nuclei flags this as `mismatched-ssl-certificate`.
4. Impact chain once confirmed: full WAF/rate-limit/bot-protection bypass by pointing requests at the origin IP; often exposes internal-only services (admin panels, AI agent builders like Flowise — probe `/api/v1/version`, `/ping`, `get-upload-file?path=` traversal variants, setup/signup takeover) and non-HTTP port clusters from a quick `-p-` nmap.

Also: `/etc/hosts`-pinning every discovered hostname to the origin IP makes all later
tooling (nuclei, ffuf) test origin-side behavior instead of Cloudflare edge.


Real IDs from ANY public leak make a meaningful object-access pass possible:

1. **Harvest real object UUIDs** from public catalog/listing endpoints (e.g. `/api/voucher-groups` leaking full catalog incl. hidden items).
2. **Replay GET-only** against every ID-bearing variant: direct path `/api/x/{id}`, query forms `?xId=` / `?x_id=`, sibling objects (`orders`, `vouchers`, `user`), admin-ish resources.
3. **Controls are mandatory:** same request with fake/nonexistent ID (expect 404) proves the route resolves IDs; valid-ID-no-auth returning uniform 401 proves auth gating. 200-valid/404-fake differential on a gated endpoint = protection working, NOT IDOR.
4. **Write the negative matrix into the report** (probe → status → verdict table in SUMMARY.md). Confirmed-no-IDOR with evidence demonstrates coverage; silence demonstrates nothing.
5. If a per-ID detail endpoint IS open (by design), fold it into the parent disclosure finding rather than inventing an IDOR claim.

## Complementary Negatives (run all, ~5 min)

- Parameter reflection: unique 8+ char canary through query params on main pages; count occurrences (0 expected on well-built SPAs).
- Open redirect: `?next=https://evil.com` / `?redirect=` — check Location header; fixed internal redirect = safe.
- Injection probes on path params (`'`, ` OR 1=1`, traversal): expect uniform 404/400 (parameterized); varied error bodies = investigate.
- CORS: send foreign origins + `null`; no reflection = clean (severity tiers live in `bug-bounty-reporting-workflow`).
- dalfox single-pass on parameterized pages for a scanner second-opinion.

## Environment Pitfalls

- **Cloudflare blocks default `python-urllib` UA with 403** while curl/browser UA passes. Every urllib/requests-based PoC MUST set a browser User-Agent or it fails where your curl probes succeeded.
- No `dig/host/nslookup` on many boxes → DNS-over-HTTPS via curl (`https://dns.google/resolve?name=X&type=A`).
- crt.sh intermittently returns malformed short responses → fall back to JS mining for subdomain/endpoint hints.
- **ASP.NET edge can trap API probing in infinite 307 redirect loops** (`Location:` points at the same URL; `-L` never resolves). Diagnose by reading headers, not just following. Workarounds that flipped 307→real answers in one case: add `Accept: application/json` (→403 = WAF gate), try trailing slash, force `--http1.1`.
- ASP.NET hosts leak versions via headers even when pages are gated — capture `X-Powered-By`, `X-AspNet-Version`, `X-AspNetMvc-Version`, and literal `Server: Microsoft-IIS/8.0` (IIS 8.0 = Windows Server 2012, EOL Oct 2023 → version-exposure finding with upgrade recommendation).
- Missing binaries are setup issues, not findings — note once, work around, move on.
- **Long inline bash gets hardline-blocked by the command parser.** Compound one-liners carrying complex regex (brace quantifiers like `.{60}canary.{40}`) or many chained statements can trip "BLOCKED (hardline): malformed executable payload". Reliable pattern that always worked: `write_file` a small dependency-free Python probe (urllib + http.cookiejar), then run `python3 script.py`. Keep loops/regex/context-extraction in the script; keep each terminal call short. This is an executor quirk, not "shell is broken".
- **Laravel catch-all fallback route poisons naive probing.** Unknown paths return HTTP 200 + homepage HTML (~same byte size every time, only canonical/og:url differ) instead of 404. So `/telescope`, `/horizon`, `.env.backup` all "200 OK". Triage rule: 403 with tiny iso-8859-1 body = real Apache block; 200 + ~56KB text/html = catch-all, NOT exposure; only non-fallback sizes/content-types count as hits. Also expect soft-404 profile pages (`/anggota/{invalid}` → 200 empty shell) and broken routes that 500 into debug pages (see `references/laravel-app-audit.md`).

## CMS Estates: WordPress + Moodle

When fingerprint shows `wp-content/` / `xmlrpc.php` / `/wp-json` or a `MoodleSession` cookie, follow `references/wp-moodle-deep-exploitation.md`. Headline rules from that reference:

- **Dual-vhost estates**: probe apex AND origin vhosts separately (`curl --resolve host:443:<origin-ip>`) — xmlrpc was CF-blocked (520) on apex but fully alive on the origin vhost; 520 ≠ downtime.
- **readme.txt version enum is per-vhost**: plugin `readme.txt` was 404 for ALL plugins on origin but 200 via apex. Try both hosts before concluding "no readme".
- **Moodle version**: read top heading of `/UPGRADING.md` (5.x) or `/lib/upgrade.txt` (≤4.4); anonymous config leak via `POST /lib/ajax/service.php` with `tool_mobile_get_public_config`; always try `/info.php`.
- **Moodle login spray REQUIRES scraping the hidden `logintoken` CSRF field first** (GET login page with cookie jar → POST username/password/logintoken). Without it every attempt false-negatives as invalid-login even with correct credentials.
- Keep brute-force budgets exact and agreed up-front (e.g. 27 xmlrpc attempts, 3 Moodle logins), sequential with delay; uniform failures = no-lockout control verified.

## Report Delivery (user preferences — hard rules)

1. **NEVER ship a zip archive.** User explicitly rejected it ("kenapa bentuknya zip"). Plain individual files, ONE canonical location only.
2. **Before creating the report folder, look for a sibling example target and copy its structure exactly** (e.g. `/workspace/reports/<previous-target>/`). Canonical observed shape:
   - `SUMMARY.md` — emoji severity matrix (Severity | Title | CVSS | CWE | Endpoint | Link), PoC list, evidence list, verified-working-controls table, priority recommendations.
   - `metadata.json` — `{target, scan_time, total_findings, severity_summary{CRITICAL..INFORMATIONAL}, findings[{file_name,title,severity,cvss,cwe,endpoint,last_modified}], pocs[], evidence_files[]}`.
   - `findings/[SEVERITY]_slug.md` — Target/Severity/CVSS/CWE/Status header, raw request/response blocks, impact, verified-negatives, remediation.
   - `pocs/*.py`, `evidence/*`.
3. **Report language = whatever the CURRENT engagement specifies** (this user alternates: English ("berbahasa inggris") and Bahasa Indonesia). Ask once if unspecified; never inherit the last session's choice.
4. **Execute every delivered PoC end-to-end before shipping.** Both bugs found in the initial PoC (truncated-body slice breaking json.loads; leftover experimental line) surfaced only on first run.
5. **Post-engagement cleanup is a hard rule for this user.** After the report folder is final, delete recon working dirs and /tmp probe artifacts (user: "abis ngapa-ngapain cache nya di bersihkan"). Copy anything worth keeping into `evidence/` FIRST, then `rm -rf` recon dir and tmp files; verify only deliverables remain.
6. **If a report-aggregator tool exists that regenerates SUMMARY.md from findings**, run it BEFORE hand-writing the rich SUMMARY — the tool overwrites the file with its simple index format. Restore/enrich afterwards or skip the tool; never let the generated index be the shipped summary.

## Follow-Up Rounds (user-mandated workflow)

When the operator asks for follow-up testing ("validasi exploit", round 2), resolve it into **ONE explicitly-stated objective** — no ambiguity — and restate it in the todo list before probing. Same non-destructive gates apply (GET-only on state-changing paths, fake credentials, small bursts). **Every outcome must land in the report**: positives AND negatives go into `evidence/validation_roundN_notes.md` (verdict matrix + per-test raw detail) and get reflected in SUMMARY.md rows (validated controls table grows; strengthened findings get updated evidence). A negative validation is coverage proof, not waste. Concrete patterns (timing enumeration, ID sweeps, SQLi canary battery, Accept:application/json debug trace) live in `references/laravel-app-audit.md`.

## References

- `references/nextjs-gateway-case-study.md` — worked example: hardened Next.js+Cloudflare AI gateway, 71-endpoint JS-mined surface, unauth-IDOR round with negative matrix, 1 Low + 5 Info outcome.
- `references/ai-reseller-storefront-case-study.md` — worked example: SPF→origin multi-vhost bypass on a Cloudflare-fronted AI-credit reseller, Flowise fingerprinting, gateway-vs-console rate-limit differential, auditor-side artifact FPs (output-redaction masking, SPA-fallback SSRF), session-preserving auth testing under login cooldown.
- `references/laravel-app-audit.md` — Laravel estate specifics: debug-page triage (APP_DEBUG vs Ignition vs catch-all), dual-mode HTML+JSON debug trace leak, response-signature table from an enterprise Laravel audit, login-throttle PoC pattern, validation-round patterns (timing enum, ID sweep, SQLi canary), safe trigger set.

