Anonymous user enumeration on a WordPress REST API via the ?rest_route= bypass
Target redacted. This finding is not submitted to the program nor publicly disclosed yet, so hostnames are replaced with redacted.example and account identifiers are removed. The technical detail is kept intact.
Summary
An unauthenticated attacker could list every registered user of the WordPress site with a single GET request. The site blocked /wp-json, /wp-login.php and xmlrpc.php, but the classic /?rest_route= query-string form of the REST API was not covered, so the block was cosmetic.
Impact
User enumeration is the first stage of a credential-stuffing chain: real names, slugs and Gravatar hashes (MD5 of the email, rainbow-table friendly) give an attacker a validated username list plus a path to the email addresses (PII). No authentication and no victim interaction required.
Reproduction
GET /?rest_route=/wp/v2/users&per_page=100 HTTP/1.1
Host: redacted.example
Accept: application/json
Response: 200 OK with a JSON array of users (id, name, slug, avatar_urls).
The blocking was inconsistent:
| Request | Result |
|---|---|
GET /wp-json/wp/v2/users | 404 (blocked) |
GET /wp-login.php | 404 (blocked) |
POST /xmlrpc.php | 403 (blocked) |
GET /?rest_route=/wp/v2/users&per_page=100 | 200 with data |
Root cause
The hardening was applied to URL paths only. In WordPress the REST API is also reachable through the rest_route query parameter, which never touches /wp-json and therefore skipped every rule.
Severity
CVSS 3.1 5.3 — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N. Programs usually triage it as P4/Low because the disclosure alone is not highly sensitive.
Remediation
- Filter at the endpoint level (
rest_endpoints/rest_user_caps), not on the/wp-jsonpath. - Do not expose avatar hashes to unauthenticated requests.
- Add a WAF rule for
rest_route-based access towp/v2/users.
Lesson
"Blocked" endpoints deserve a second request in their alternative form. One extra parameter changed a 404 into a full user dump.