parlov docs

GET

Status code differentials observed via GET requests.

Implemented

Status code differentials observed via GET requests. All strategies in this section are Safe risk tier — read-only probing with no state mutation.


Elicitation Strategies


200 vs 404

Vector

Server returns 200 for existing resources and 404 for nonexistent ones. The status code differential directly confirms which resource IDs are valid. A 200 on a resource the client shouldn't see is a direct IDOR.

Example

GET /api/users/123 HTTP/1.1
Host: target.com

HTTP/1.1 200 OK
Content-Length: 491
{"id":123,"name":"alice","email":"alice@corp.com"}

---

GET /api/users/999 HTTP/1.1
Host: target.com

HTTP/1.1 404 Not Found
Content-Length: 27
{"error":"user not found"}

Leaking Response / Methodology

  • What leaks: Status code differential (200 vs 404) confirms resource 123 exists. Response body on 200 may contain full resource representation (IDOR).

206 vs 404

Vector

Range request on an existing resource. Sending a GET request with a Range header (e.g., Range: bytes=0-10) against an existing resource returns 206 Partial Content (per RFC 9110 §15.3.7). A nonexistent resource fails to resolve and returns 404.

Example

GET /api/users/123/avatar HTTP/1.1
Host: target.com
Range: bytes=0-10

HTTP/1.1 206 Partial Content
Content-Range: bytes 0-10/4500
Content-Length: 11

---

GET /api/users/999/avatar HTTP/1.1
Host: target.com
Range: bytes=0-10

HTTP/1.1 404 Not Found

Leaking Response / Methodology

  • What leaks: 206 vs 404 confirms user 123's avatar exists. The Content-Range header leaks the total size of the resource (e.g., 4500 bytes).

301/302 vs 404

Vector

Server returns a redirect (301, 302, 307, 308) for existing resources to enforce canonical paths or trailing slashes, but returns 404 for nonexistent resources. The server must resolve the resource to determine its canonical URI, making the redirect an existence oracle.

Example

GET /api/users/123 HTTP/1.1
Host: target.com

HTTP/1.1 301 Moved Permanently
Location: /api/users/123/
Content-Length: 0

---

GET /api/users/999 HTTP/1.1
Host: target.com

HTTP/1.1 404 Not Found
Content-Length: 27
{"error":"user not found"}

Leaking Response / Methodology

  • What leaks: 301 vs 404 confirms user 123 exists. The Location header provides the canonical path.

304 vs 404

Vector

Conditional requests using If-Modified-Since or If-None-Match: * against an existing resource return 304 Not Modified. Against a nonexistent resource, the server fails the initial lookup and returns 404. This is highly bandwidth-efficient for enumeration — 304 responses have no body.

Example

GET /api/users/123 HTTP/1.1
Host: target.com
If-None-Match: *

HTTP/1.1 304 Not Modified

---

GET /api/users/999 HTTP/1.1
Host: target.com
If-None-Match: *

HTTP/1.1 404 Not Found
Content-Length: 27
{"error":"user not found"}

Leaking Response / Methodology

  • What leaks: 304 vs 404 confirms user 123 exists. The 304 has no body, minimizing bandwidth.

400 vs 404 (Parameter Validation)

Vector

If the routing layer resolves the resource before validating query parameters, an existing resource with malformed parameters (e.g., ?limit=NaN) throws 400 Bad Request, whereas a nonexistent resource fails at the routing layer with 404.

Example

GET /api/users/123?limit=NaN HTTP/1.1
Host: target.com

HTTP/1.1 400 Bad Request
Content-Length: 42
{"error":"invalid_parameter","field":"limit"}

---

GET /api/users/999?limit=NaN HTTP/1.1
Host: target.com

HTTP/1.1 404 Not Found
Content-Length: 27
{"error":"user not found"}

Leaking Response / Methodology

  • What leaks: 400 vs 404 confirms user 123 exists. The 400 means the server reached the parameter validation logic of the resource handler.

401 vs 404

Vector

Server returns 401 (with mandatory WWW-Authenticate header per RFC 9110 §15.5.2) for existing resources requiring authentication, but 404 for nonexistent resources. The RFC requires the WWW-Authenticate header on 401, making this impossible to normalize without violating the spec.

Example

GET /api/users/123 HTTP/1.1
Host: target.com

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="user-service"

---

GET /api/users/999 HTTP/1.1
Host: target.com

HTTP/1.1 404 Not Found

Leaking Response / Methodology

  • What leaks: 401 vs 404 confirms user 123 exists. The WWW-Authenticate header is a bonus signal — realm reveals internal service boundaries. This is a high-value oracle because the RFC mandates the header, so the server can't suppress it without breaking compliance.

402 vs 404

Vector

Server returns 402 Payment Required for resources behind a paywall or subscription gate, but 404 for nonexistent resources. The 402 confirms the resource exists and reveals the monetization boundary. Although 402 is technically "reserved for future use" in the RFC, many APIs (Stripe, SaaS platforms) use it in practice, making it a real-world oracle.

Example

GET /api/reports/premium-q4-2025 HTTP/1.1
Host: target.com
Authorization: Bearer <free-tier-token>

HTTP/1.1 402 Payment Required
Content-Length: 78
{"error":"payment_required","plan":"enterprise","price":"$49/mo"}

---

GET /api/reports/nonexistent-report HTTP/1.1
Host: target.com
Authorization: Bearer <free-tier-token>

HTTP/1.1 404 Not Found
Content-Length: 30
{"error":"report not found"}

Leaking Response / Methodology

  • What leaks: 402 vs 404 confirms premium-q4-2025 report exists. The 402 body may leak pricing tiers, plan names, and feature gates. Enumerating 402s across resource slugs maps the entire premium content catalog without paying.

403 vs 404

Vector

The most exploited existence oracle pattern. Server returns 403 for existing resources the client can't access and 404 for nonexistent resources — every 403 confirms existence. GitHub famously switched from 403 to 404 for private repos because a 403 on github.com/apple/secret-project confirmed the repo existed.

Example

GET /repos/apple/secret-project HTTP/1.1
Host: api.github.com
Authorization: Bearer <low-priv-token>

HTTP/1.1 403 Forbidden
{"message":"Repository access blocked","documentation_url":"..."}

---

GET /repos/apple/nonexistent-repo HTTP/1.1
Host: api.github.com
Authorization: Bearer <low-priv-token>

HTTP/1.1 404 Not Found
{"message":"Not Found","documentation_url":"..."}

Leaking Response / Methodology

  • What leaks: 403 vs 404 confirms secret-project exists. The 403 body message "Repository access blocked" is semantically different from the 404 "Not Found" — even if status codes were normalized, body diffing catches it. Body Content-Length also differs.

406 vs 404

Vector

Content negotiation failure. Sending a request with a deliberately obscure Accept header (e.g., application/vnd.made-up) causes an existing resource to return 406 Not Acceptable, while a nonexistent resource fails at routing and returns 404. The server must resolve the resource before evaluating content negotiation (RFC 9110 §12.5.1).

Example

GET /api/users/123 HTTP/1.1
Host: target.com
Accept: application/vnd.made-up

HTTP/1.1 406 Not Acceptable
Content-Length: 46
{"error":"unsupported media type requested"}

---

GET /api/users/999 HTTP/1.1
Host: target.com
Accept: application/vnd.made-up

HTTP/1.1 404 Not Found
Content-Length: 27
{"error":"user not found"}

Leaking Response / Methodology

  • What leaks: 406 vs 404 confirms user 123 exists. The 406 means the server found the resource but couldn't negotiate the representation.

410 vs 404

Vector

Historical existence oracle. Server returns 410 for resources that did exist but were intentionally removed, and 404 for resources that never existed. The 410 confirms the resource ID was valid at some point — strictly more informative than 404.

Example

GET /api/users/456 HTTP/1.1
Host: target.com

HTTP/1.1 410 Gone
{"error":"account deleted","deleted_at":"2025-11-15T08:30:00Z"}

---

GET /api/users/999 HTTP/1.1
Host: target.com

HTTP/1.1 404 Not Found
{"error":"user not found"}

Leaking Response / Methodology

  • What leaks: 410 vs 404 confirms user 456 existed and was deleted. Body may leak deletion timestamp. Implies server maintains tombstone records. Enumerating 410s across ID ranges reveals the full set of previously-valid resource IDs.

429 vs 404

Vector

Rate limiting applies per-resource or per-endpoint for valid resources. The server returns 429 Too Many Requests for valid resource paths but 404 for invalid ones. The rate limit itself becomes the oracle — if you get throttled, the resource exists and is being actively protected. Requires the server to resolve the resource before applying the rate limit (RFC 6585 §4).

Example

GET /api/users/123 HTTP/1.1
Host: target.com

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
{"error":"rate limit exceeded"}

---

GET /api/users/999 HTTP/1.1
Host: target.com

HTTP/1.1 404 Not Found
{"error":"user not found"}

Leaking Response / Methodology

  • What leaks: 429 vs 404 confirms user 123 exists. The Retry-After and X-RateLimit-* headers are bonus signals — different limits across resources reveal tiering (admin vs user, free vs paid). Some servers apply per-resource rate limiting, meaning each resource has its own counter.

500 vs 404

Vector

Server errors on existing resources (processing errors, serialization failures, backend exceptions) but returns a clean 404 for nonexistent resources. A 500 implies the server found the resource and attempted to process it — the crash confirms existence. Especially powerful when combined with malformed input fuzzing.

Example

GET /api/users/123?fields=sensitive_field HTTP/1.1
Host: target.com

HTTP/1.1 500 Internal Server Error
Content-Length: 89
{"error":"internal error","trace":"NullPointerException at UserSerializer.java:142"}

---

GET /api/users/999?fields=sensitive_field HTTP/1.1
Host: target.com

HTTP/1.1 404 Not Found
Content-Length: 27
{"error":"user not found"}

Leaking Response / Methodology

  • What leaks: 500 vs 404 confirms user 123 exists. The 500 body may leak stack traces, class names, database column names, and internal service topology. The crash itself is the signal — the server only reaches the crashing code path if the resource was found.

Cross-Method Oracles

The following oracles are not GET-specific but are observable via GET requests.

414 vs 404

Vector

Per RFC 9110 §15.5.15, a server returns 414 URI Too Long when the request URI exceeds a configured limit. If the limit check occurs after routing resolves the resource, existing resources trigger 414 while nonexistent ones return 404 before the limit is evaluated. This applies to any HTTP method but is most useful on GET and HEAD where no body is required.

Example

GET /api/users/123?padding=AAAAAAA...AAAA HTTP/1.1
Host: target.com

HTTP/1.1 414 URI Too Long

---

GET /api/users/999?padding=AAAAAAA...AAAA HTTP/1.1
Host: target.com

HTTP/1.1 404 Not Found
{"error":"user not found"}
```http

#### Leaking Response / Methodology

- **What leaks:** 414 vs 404 confirms user 123 exists. The server resolved the resource and reached its application-level URI length check.

### 416 vs 404

#### Vector

Per RFC 9110 §15.5.17, a server returns 416 Range Not Satisfiable when the `Range` header requests bytes beyond the resource's actual size. The server must *resolve* the resource and *know its size* before it can evaluate the range — so a 416 implies the resource was found. A nonexistent resource returns 404.

#### Example

```jsx
GET /api/users/123/avatar HTTP/1.1
Host: target.com
Range: bytes=99999999-100000000

HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */4500

---

GET /api/users/999/avatar HTTP/1.1
Host: target.com
Range: bytes=99999999-100000000

HTTP/1.1 404 Not Found
{"error":"user not found"}

Leaking Response / Methodology

  • What leaks: 416 vs 404 confirms user 123's avatar exists. The Content-Range: bytes */4500 header leaks the total resource size — the server reveals exactly how large the file is while rejecting the range. This is a two-for-one oracle: existence confirmation plus size disclosure.

On this page