The vulnerability is a sandbox bypass in Deno's fetch() and WebSocket APIs, caused by a Time-of-check/Time-of-use (TOCTOU) flaw. Deno correctly checked if a given hostname was allowed by the --deny-net rules. However, it did not perform any checks on the IP addresses that the hostname resolved to. An attacker could exploit this by using a specially crafted domain name that passes the initial hostname check but resolves to a denied IP address (e.g., localhost or an internal service IP).
The analysis of the patch commit 3d6c61477c78456b408c68ad8454dfa393232adc reveals the root cause and the fix. The core of the vulnerability was in the DNS resolver, specifically deno_fetch::dns::Resolver::call, which performed DNS lookups without validating the resulting IP addresses against the permission set.
The fix involved several changes:
- A new function,
check_resolved_permissions, was introduced in ext/fetch/dns.rs to perform the post-resolution IP check.
- The
deno_fetch::dns::Resolver::call method was updated to use this new function after a successful DNS lookup.
- The
PermissionsContainer is now threaded through the client creation process, starting from op_fetch_custom_client and op_ws_create, down to the DNS resolver, ensuring it has the context to perform the checks.
- A new
PermissionedHttpConnector was introduced to solve a problem where the DNS resolver didn't know the destination port, making port-specific deny rules ineffective. This connector uses a tokio::task_local to pass the port from the URI down to the resolver.
By identifying the functions that were modified to thread permissions and perform these new checks, we can pinpoint the functions that were part of the vulnerable execution path. These include the functions responsible for creating the HTTP and WebSocket clients (create_client_from_options, create_client_from_websocket_options) and the DNS resolver itself.