RC RANDOM CHAOS

Telegram Desktop File Theft Bug

Telegram Desktop 7.2.9 fixed a link-triggered file theft bug that could expose local session files without a passcode.

· 5 min read
Telegram Desktop File Theft Bug

A clicked link could make Telegram Desktop read arbitrary files from disk and upload them to an attacker-controlled supergroup, including the local files needed to reopen the victim’s Telegram session.

The bug chain described by BeakSec lived in Telegram Desktop’s single-instance handoff path. Telegram registers the tg:// URI scheme with the operating system. When Telegram is already running and another tg:// link is opened, the new process connects to a local socket, sends the link to the existing process, and exits.

That handoff serialized commands as text. Each record used a keyword, an argument, and a semicolon separator. The receiving instance split the incoming bytes on semicolons and treated each segment as its own instruction. The problem was that Telegram did not escape semicolons inside the URL value before writing it to the socket.

A semicolon inside the clicked link therefore stopped being data and became syntax. The new process saw one URL. The already-running process saw multiple records.

That injection alone was limited. Some accepted commands were harmless, and CMD: only accepted show and quit. The useful command was OPEN:, because it accepted any URL scheme. That exposed an internal scheme named interpret:.

interpret: was a release-publishing helper. It read a small instruction file that named a destination channel, a file to send, and text to use as the message. A from: line could restrict the action to the currently logged-in account, but the check only ran when that line was present. Leaving it out skipped the account check. The destination had to be a channel or supergroup.

The dangerous part was the action itself: InterpretSendPath could read a file from disk and send it to a chat without confirmation and without checking who requested it. From the command line, that was mostly an internal automation footgun. Through the single-instance socket injection, it became reachable from a link.

The attacker still needed the instruction file on the victim’s disk at a predictable path. Telegram Desktop’s default configuration supplied that part. In groups, received files up to 8 MiB were downloaded automatically. Broadcast channels did not auto-download in the same way. The file landed in a standard folder under the sender’s chosen name, unless a collision changed the filename.

On Windows, the user name might look like an obstacle. The interpret: path handling also accepted relative paths, and Telegram’s working directory was its data folder under %APPDATA%\Telegram Desktop. From there, the user’s Downloads folder could be reached with a deterministic relative path. The attacker did not need to know the Windows account name.

The proof of concept used three instruction files. Each one pointed at a different Telegram local data file and named the attacker’s own supergroup as the upload destination. The attacker added the victim to a supergroup, posted the instruction files, and then posted an ordinary HTTPS link. Telegram itself would handle tg:// links clicked inside Telegram in-process, so the exploit used the browser path: the HTTPS link redirected to the crafted tg:// URL. Depending on browser state and handler settings, the system could ask before launching Telegram.

Once the browser launched Telegram, the second Telegram process forwarded the crafted URL over the socket. The unescaped semicolons split the record. Multiple interpret: commands ran. The selected files were uploaded to the attacker’s group without a confirmation dialog.

The account takeover depended on Telegram Desktop’s local storage design. Telegram kept local data encrypted, including the session authorization. The relevant key structure used a data encryption key, or DEK, protected by a key encryption key, or KEK. The KEK was derived from a password and a salt.

By default, Telegram Desktop had no local passcode. With no passcode set, the password input to the derivation was empty. The salt was stored in cleartext in tdata/key_datas, the same file that held the encrypted DEK. Reading that file was enough to recompute the KEK and unwrap the DEK. With the DEK, the remaining local data could be decrypted, including the session authorization.

The proof of concept took three files. Two held secrets. The third was an index that did not hold secrets but was needed for Telegram to load the authorization in that reconstruction path. With those files placed into a fresh tdata, Telegram opened as the victim.

For defenders, the practical lesson is narrow and useful. Local encryption only helps if the local secret is not derivable from files the application can be tricked into exfiltrating. A default empty passcode turned Telegram’s encrypted-at-rest session material into recoverable session material once arbitrary file read existed. Setting a local passcode did not stop the file theft, but it made the stolen session files unusable without that passcode.

The main fix was to upgrade to Telegram Desktop 7.2.9 or later. The issue was fixed by commit db3405699f on 16 September 2026, with the 7.2.9 changelog dated the same day and the release published the following morning. The change removed the interpret:// scheme and Support::InterpretSendPath, and escaped the single-instance socket record separator using percent-prefixed hex encoding before writing values and decoding them after splitting.

The fix also added two hardening measures: CMD: and CTRL: records are skipped when the same connection carries an OPEN:, and local file paths are dropped after a non-local URL appears on the connection.

Short of upgrading, BeakSec listed three exposure reducers. Turning on “ask where to save each file” prevents automatic download, so the instruction files do not silently land on disk. Restricting group adds to contacts limits the attacker’s ability to create the delivery location. Setting a local passcode protects the stolen session material, provided the passcode is chosen like a real password.

The quiet part matters operationally. The fix shipped without an advisory; the 7.2.9 changelog mentioned only a rendering fix, and the closing commit was titled “Remove legacy interpret path helper.” For software that handles URI schemes, local IPC, automatic downloads, and session material, each boundary needs to be treated as hostile even when the feature started life as internal tooling.

Share

Keep Reading

Latest on the Wire

Full wire →

New signal daily · RSS

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.