# Root Cause Analysis — CVE-2026-48611

## Summary

CVE-2026-48611 is a critical unauthenticated authentication-bypass in phpBB
3.3.0–3.3.16 (and 4.0.0-a2). The UCP "login-link" flow
(`ucp.php?mode=login_link`) lets an attacker choose which authentication
provider handles the login via a fully attacker-controlled `auth_provider`
request parameter. By selecting the bundled `apache` provider and sending an
HTTP Basic `Authorization` header that carries any existing username (e.g.
`admin`), an unauthenticated remote attacker obtains a valid phpBB session as
that user — including administrators — **without knowing the password**. The
`apache` provider trusts `PHP_AUTH_USER` from the Basic header and returns
`LOGIN_SUCCESS` after a database lookup without ever validating the password,
after which `ucp_login_link` calls `$user->session_create()`. The flaw is
present in the **default** `auth_method=db` installation; OAuth does not need
to be configured. Fixed in phpBB 3.3.17 (2026-06-06).

## Impact

- **Package/component affected:** `phpBB/includes/ucp/ucp_login_link.php`
  (attacker-controlled provider selection) combined with
  `phpBB/phpbb/auth/provider/apache.php` (password-less `login()`).
- **Affected versions:** phpBB 3.3.0 through 3.3.16, and 4.0.0-a2. Default
  `auth_method=db` boards are vulnerable out of the box.
- **Risk level and consequences:** Critical (CVSS 9.8). A single unauthenticated
  HTTP request hijacks any known account. Logging in as an administrator yields
  full board control (ACP access, user management, extension installation,
  persisted backdoors). Usernames are public on phpBB boards, so admin/moderator
  accounts are trivially targetable.

## Impact Parity

- **Disclosed/claimed maximum impact:** Unauthenticated remote account
  hijacking of arbitrary known accounts (including administrators) → full board
  compromise; no password, no prior access, no user interaction.
- **Reproduced impact from this run:** Full parity. Against a real running
  phpBB 3.3.16 (Apache 2.4 + mod_php 8.2 + SQLite, default `auth_method=db`,
  installed via the official CLI installer) a single unauthenticated POST to
  `ucp.php?mode=login_link&auth_provider=apache&login_link_aikido=1` with
  `Authorization: Basic admin:x` (password `x` deliberately wrong) returned a
  `302` response that set the session cookie `phpbb3_..._u=2` (the
  administrator account, `user_id=2`, `user_type=USER_FOUNDER`). Reloading
  `index.php` with that stolen session rendered the page as the `admin` user
  and showed the **Administration Control Panel** link (`adm/index.php`) — an
  admin-only UI element — confirming a genuine administrator session. The same
  request against phpBB 3.3.17 left the session as anonymous (`_u=1`) and
  produced no admin session.
- **Parity:** `full`.
- **Not demonstrated:** N/A — the claimed impact (authz bypass / admin account
  takeover) was demonstrated end-to-end on the real product.

## Root Cause

Two cooperating defects form the exploit chain:

1. **Attacker-controlled auth provider.** In `ucp_login_link::main()` the
   provider is resolved from the request:
   ```php
   // phpBB/includes/ucp/ucp_login_link.php (3.3.16)
   $provider_collection = $phpbb_container->get('auth.provider_collection');
   $auth_provider = $provider_collection->get_provider($request->variable('auth_provider', ''));
   ```
   The `auth_provider` value is taken verbatim from the request (GET or POST),
   so an attacker can force the use of **any** registered provider class under
   `phpbb/auth/provider/`, not only the board's configured `auth_method`.

2. **Password-less `apache` provider.** `phpbb/auth/provider/apache.php::login()`
   decodes `PHP_AUTH_USER`/`PHP_AUTH_PW` from the HTTP Basic
   `Authorization` header, requires that `PHP_AUTH_USER === $username`, looks
   the user up in the database, and — for an active user — returns
   `LOGIN_SUCCESS` **without comparing `PHP_AUTH_PW` to the stored password
   hash**. This is intentional *only* when Apache itself is the authenticating
   proxy (`.htpasswd`); the provider simply trusts the username the proxy
   forwards. By driving this provider through the login-link flow, the
   attacker's own HTTP client becomes the "trusted proxy" and supplies any
   username it wants.

   Back in `ucp_login_link::main()`, a `LOGIN_SUCCESS` result with no error
   leads directly to:
   ```php
   $user->session_create($login_result['user_row']['user_id'], false, false, true);
   $this->perform_redirect();
   ```
   i.e. a fully authenticated session for the chosen user is created and the
   browser is redirected to the index — all from one unauthenticated POST.

The login-link gate (`get_login_link_data_array()` requiring a non-empty
`login_link_*` GET parameter) is trivially satisfied with dummy data such as
`login_link_aikido=1`, so it provides no meaningful protection.

**Fix (phpBB 3.3.17, ticket/17659).** The login-link handling was moved out of
`ucp.php`/`ucp_login_link.php` into a dedicated controller
`phpbb/ucp/controller/oauth.php`. In `ucp.php` the `login_link` mode now
redirects to that controller (`phpbb_redirect_to_controller(...)`), and the
controller resolves the provider **without** the attacker-controlled argument:
```php
// phpBB/phpbb/ucp/controller/oauth.php (3.3.17), link_account()
$auth_provider = $this->auth_collection->get_provider();   // no argument -> configured auth_method (db)
```
With the default `db` provider, `login()` actually verifies the password hash,
so the wrong password (`x`) is rejected and no session is created. The
`auth_provider=apache` trick is therefore no longer reachable. Key commits in
`release-3.3.16..release-3.3.17`: `12c4cf6f78`, `c05603cc3a`,
`4a2962bfc8` (`[ticket/17659]` series — "Add new oauth link controller" /
"Make account linking work via controller" / "Move login linking for oauth to
controller").

## Reproduction Steps

1. **Script:** `bundle/repro/reproduction_steps.sh` (self-contained, idempotent;
   run with `PRUVA_ROOT=<bundle dir> bash bundle/repro/reproduction_steps.sh`).
2. **What it does:**
   - Reuses (or creates) git worktrees of phpBB at `release-3.3.16`
     (vulnerable) and `release-3.3.17` (fixed) from the durable project cache
     mirror.
   - Builds a Docker image per version on `php:8.2-apache` (gd/intl/zip
     extensions, mod_rewrite, AllowOverride All) and runs the official phpBB
     CLI installer (`install/phpbbcli.php install`) with a SQLite backend and an
     `admin`/`adminadmin` founder account (`user_id=2`).
   - Starts each image as a running Apache+mod_php service and sends the **real
     exploit** through it (HTTP requests are issued from inside each container
     against `127.0.0.1:80`, because the sandbox blocks host→container port
     publishing; this still crosses the genuine Apache/PHP request boundary and
     populates `PHP_AUTH_USER`/`PHP_AUTH_PW` via mod_php):
     ```
     POST /ucp.php?mode=login_link&auth_provider=apache&login_link_aikido=1
     Authorization: Basic base64(admin:x)
     Content-Type: application/x-www-form-urlencoded
     login_username=admin&login_password=x&login=Login
     ```
   - Asserts the vulnerable build sets the session cookie `*_u=2` and that the
     index page, loaded with the stolen session, shows the admin username and
     the ACP link. Asserts the fixed build leaves the session anonymous
     (`*_u=1`) for the same request (both via `ucp.php` and directly via the
     new controller `/app.php/user/oauth/link_account`).
3. **Expected evidence:** The vulnerable response is a `302 Found` whose
   `Set-Cookie` headers include `phpbb3_...._u=2` and a `Location` to the
   index; the fixed response is a `301` redirect to the controller (ucp.php
   path) / a `200` login-link form with a `class="error"` block and
   `phpbb3_...._u=1` (controller path). The script exits `0` only when the
   vulnerable build hijacks admin **and** the fixed build blocks it.

## Evidence

- `bundle/logs/reproduction_steps.log` — full annotated run log (two
  consecutive successful runs).
- `bundle/logs/vuln_exploit_response.txt` / `bundle/logs/vuln_setcookie_summary.txt`
  — vulnerable 3.3.16 exploit response. Excerpt:
  ```
  HTTP/1.1 302 Found
  Set-Cookie: phpbb3_c6ct2_u=1; ...; HttpOnly
  Set-Cookie: phpbb3_c6ct2_u=2; ...; HttpOnly          <-- admin user_id=2
  Location: http://localhost/index.php?sid=e7988db9...
  ```
- `bundle/repro/artifacts/vuln/index_with_session.html` — `index.php` loaded
  with the stolen cookie; contains `username-coloured">admin` and the
  admin-only `Administration Control Panel` / `adm/index.php` link.
- `bundle/repro/artifacts/vuln/cookies.txt` — Netscape cookie jar with
  `phpbb3_..._u = 2`.
- `bundle/logs/fixed_exploit_ctrl_response.txt` /
  `bundle/logs/fixed_setcookie_summary.txt` — fixed 3.3.17 controller response.
  Excerpt: only `Set-Cookie: phpbb3_9wg5u_u=1` (anonymous) and a
  `<div class="error">...not available. Please restart the login
  process.</div>` block; no `_u=2`, no redirect to the index.
- `bundle/repro/artifacts/fixed/exploit_ucp_response.txt` — fixed `ucp.php`
  path returns `301 Moved Permanently` to
  `/app.php/user/oauth/link_account?...` (no apache-provider login).
- `bundle/repro/runtime_manifest.json` — runtime evidence manifest
  (`entrypoint_kind=api_remote`, `service_started=true`,
  `healthcheck_passed=true`, `target_path_reached=true`).
- **Environment:** Docker `php:8.2-apache` (Apache/2.4.67, PHP/8.2.32,
  mod_php), phpBB `release-3.3.16` and `release-3.3.17`, SQLite3 backend,
  default `auth_method=db`, board installed via
  `php install/phpbbcli.php install`.

## Recommendations / Next Steps

- **Patch immediately.** Upgrade every phpBB instance to 3.3.17 or later. This
  is the only complete fix.
- **Temporary workaround (pre-3.3.17, only if Apache/LDAP auth is unused):**
  remove `phpbb/auth/provider/apache.php` and `phpbb/auth/provider/ldap.php`
  from the board root, and disable OAuth in the ACP until upgraded (per the
  phpBB team's urgent advisory).
- **Defense-in-depth:** authentication providers must never be selectable from
  untrusted request input; provider resolution should always use the
  board-configured `auth_method`. The `apache` provider's trust model (proxy
  authenticated the user) should be gated to boards that actually configure
  Apache/LDAP front-auth, and should not be exposed through flows designed for
  OAuth.
- **Detection:** hunt request logs for `mode=login_link` together with
  `auth_provider=apache` (in either query string or POST body); also watch for
  the body-only variant where `mode=login_link` and `auth_provider=apache` are
  in the POST body while the query string carries `login_link_*=1`.
- **Testing:** add a regression test asserting that `ucp.php?mode=login_link`
  never honors an attacker-supplied `auth_provider`, and that the `apache`
  provider cannot grant `LOGIN_SUCCESS` without external-auth configuration.

## Additional Notes

- **Idempotency:** `reproduction_steps.sh` was executed twice consecutively;
  both runs exited `0` with identical verdicts. The script reuses cached
  worktrees and Docker images on subsequent runs (only container start/stop
  and the HTTP exploit are re-executed), so re-runs complete in a few seconds.
- **Surface match:** the claimed surface is `api_remote` (HTTP endpoint). The
  proof drives the real Apache+mod_php endpoint `ucp.php` (and the fixed
  controller) with the actual exploit request, satisfying the
  `api_remote`/`endpoint` requirement; `runtime_manifest.json` records
  `service_started`, `healthcheck_passed`, and `target_path_reached` all
  `true`.
- **Sandbox networking note:** host→container port publishing is blocked in
  this environment, so the script issues exploit traffic from inside each
  container via `docker exec curl http://127.0.0.1:80/...`. This still crosses
  the genuine Apache + mod_php request boundary (PHP_AUTH_USER/PHP_AUTH_PW are
  populated by mod_php from the `Authorization: Basic` header), and is
  equivalent to an external attacker hitting the deployed forum over HTTP.
- **Negative control:** the identical exploit request against 3.3.17 does not
  create an admin session (`_u=1`), and `ucp.php?mode=login_link` is reduced to
  a redirect to the new OAuth controller, confirming the patch closes the
  attack path.
