Local privilege escalation to root in TeamViewer for Linux
The root teamviewerd daemon on Linux accepts commands from
any local user over 127.0.0.1:5939 with no per-caller
authorization. From there a short chain turns an ordinary account into
root, with no sudo, no password, and no TeamViewer account required.
Unprivileged → root in one command
The proof of concept runs entirely as a normal user. It hands any shell
command to the root daemon, which executes it. Below is a faithful replay of a
real run. uid=1001 goes in, and uid=0 comes out.
Replay of poc.sh. Timings and counters are from a lab run on 15.79.4.
Five steps, all from an unprivileged socket
Every attacker action is an ordinary TCP write to 127.0.0.1:5939.
The only privileged actor anywhere in the chain is teamviewerd itself,
acting on attacker-supplied input.
Speak IPC
Complete the 5939 handshake with a hardcoded key. No account, no authz.
Write root config
0309 SetValue points the CustomConfiguration URL at a loopback server.
Crash to reload
A malformed frame aborts the daemon; systemd restarts it and re-reads the config.
Fetch + extract
The daemon downloads the attacker's zip and extracts it with no path check.
Write anywhere
Zip Slip ../ traversal drops a file into /etc/cron.d → root command.
The trust model: there isn't one
On Linux, TeamViewer ships a system service, teamviewerd, that runs as
root and listens on the loopback interface, TCP port 5939.
The GUI client talks to it over this socket using a binary IPC protocol. The daemon
does privileged work on the client's behalf, such as editing the system configuration,
fetching "custom configuration" bundles from the network, and managing the device identity.
The protocol has a handshake, which looks like authentication but is not. The client proves nothing about who it is; it only proves it knows a constant compiled into the binary:
# response the daemon accepts, for anyone who can read the binary response = md5(server_nonce + K) K = 40c289053be8c1697d74d836fc1d2f6e # hardcoded in teamviewerd
Because K is the same everywhere and the daemon applies no
per-caller authorization after the handshake, any local user who can open a
socket to 127.0.0.1:5939 can issue privileged commands. That is the root
of everything below.
Local privilege escalation to root
The escalation composes four primitives. None is exotic on its own; chained, they walk an unprivileged socket all the way to root code execution.
1 · Arbitrary write to the root config
Command 0309 (SetValue) writes any key/value into the
root-owned /etc/teamviewer/global.conf. The daemon never checks whether the
caller should be allowed to. We set the two keys that make the daemon fetch from us:
0309 CustomConfigurationUrl = http://127.0.0.1:8899/ 0309 CustomConfigurationRevisionUrl = http://127.0.0.1:8899/
Setting RevisionUrl forces a synchronous write-through of both keys to
disk. The config file is mode 600 (unreadable to us), but its directory is
world-executable, so the unprivileged process can stat() the file and poll
its mtime:size signature to prove the flush landed before relying on
it. That turns a race into a confirmed state transition.
2 · A crash that doubles as a reload
The daemon re-reads global.conf on startup, so the planted URLs take
effect after a restart. An unprivileged user cannot call systemctl restart,
but it can make the daemon restart itself. One command frame with ID 0x0887
carrying a body of the wrong type throws an uncaught C++ exception:
std::terminate -> SIGABRT -> systemd Restart=on-abort -> fresh daemon re-reads our config
3 · The Zip Slip path traversal (the core of the vulnerability)
With our URLs live, firing the CustomConfiguration 'f' trigger makes the
root daemon fetch a zip from our loopback server and extract it. This is
where we plant a classic Zip Slip.
../../etc/cron.d/tvlpe walks up out of that directory and
lands wherever the attacker points it. It is the archive equivalent of the
../ path-traversal bug (CWE-22), and because teamviewerd extracts
as root, the write happens as root.
TeamViewer's extractor builds each output path as dest_dir + "/" + entry_name
with no ../ sanitization, so a crafted entry name escapes the temp directory
entirely:
# zip entry name, stored verbatim ../../../../../../../../../../../../etc/cron.d/tvlpe # root resolves it to -> /etc/cron.d/tvlpe
Extra ../ above / are harmless, so we over-supply depth and
land the write wherever we want. Here that target is a cron file that runs our command
as root within the minute.
4 · Winning the cleanup race with a symlink
The daemon unlinks its extraction scratch space afterward. We plant a symlink at the
expected scratch path pointing at the real target, so the root write follows the link to
/etc/cron.d/tvlpe and the subsequent unlink only removes the (now harmless)
link, so the swap beats the cleanup. The PoC verifies success out-of-band: the attacker's
HTTP server logs the root fetch, so each attempt is confirmed, not hoped for.
uid=1001 and no sudo writes an arbitrary root-owned file and
executes an arbitrary command as root, repeatably and reliably.
Scoring & scope
CVE-2026-19743 is scored 7.8 High for the privilege escalation.
Vector CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H:
The weakness is cross-platform. The same IPC path traversal reaches
NT AUTHORITY\SYSTEM on Windows and root on macOS, but this writeup covers the
Linux teamviewerd chain. It is most dangerous on shared and multi-user hosts:
kiosks, lab machines, call-center workstations, and any box where an unprivileged service
account could be subverted.
| Branch | Affected | Fixed | Status |
|---|---|---|---|
| 15.x | < 15.82 | 15.82 | patch |
| 15.64 LTS (Win 7/8) | < 15.64.8 | 15.64.8 | patch |
| 14.7 | < 14.7.48855 | 14.7.48855 | patch |
| 13.2 (Linux) | < 13.2.153995 | 13.2.153995 | patch |
Fixing & detecting it
- Update to 15.82 (or the patched build for your branch, above). This is the only real fix, and it is bundled in advisory TV-2026-1010.
- If you can't patch immediately, stop and disable
teamviewerdon hosts where untrusted local users have shell access. The socket is the attack surface. - Detection: repeated
teamviewerdSIGABRT / restart cycles injournalctl -u teamviewerd; unexpectedCustomConfiguration*keys inglobal.conf; and new or modified files under/etc/cron.dcorrelated with daemon restarts.
Timeline
- 2026-07-17Reported to TeamViewer through YesWeHack: unauthenticated local IPC to root, with a working proof-of-concept.
- 2026-09-07TeamViewer accepts the report.
- 2026-09-29TeamViewer publishes advisory TV-2026-1010 and releases 15.82.
Coordinated disclosure through the vendor. Details are released only after a fixed build was available.