CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27730

The Proxy that Talked Too Much: Breaking esm.sh with SSRF

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 26, 2026·6 min read·105 visits

Executive Summary (TL;DR)

Critical SSRF in esm.sh allows internal network access via DNS aliasing. The application validated URL strings instead of resolved IPs, enabling attackers to bypass 'localhost' blocks using domains like '127.0.0.1.nip.io'.

A critical Server-Side Request Forgery (SSRF) vulnerability in esm.sh allowed attackers to bypass string-based hostname validation using DNS aliases. By masking internal IP addresses behind innocent-looking domain names, attackers could trick the CDN into scanning local networks or retrieving cloud metadata. While a patch attempted to pin hosts during redirects, the fundamental flaw of validating hostnames before DNS resolution remains a classic example of 'checking the ID card but ignoring the face'.

The Hook: A Proxy by Any Other Name

In the modern web development ecosystem, 'no-build' CDNs like esm.sh are the new hotness. They allow developers to import npm packages directly into the browser as ES modules, handling all the transpilation and bundling on the fly. To achieve this magic, the service needs to fetch external resources. It acts as a highly specialized proxy, taking a URL, processing the content, and serving it back.

But here is the golden rule of web security: If you let a user tell your server where to go, they will eventually send it to hell.

The vulnerability lies in the /http(s) fetch route. This feature is designed to proxy and bundle external modules. From a hacker's perspective, this is not a 'feature'; it is an open invitation. It is a portal that accepts a URL and makes an HTTP request from the context of the server. If we can manipulate that request, we are no longer just visiting the website; we are inside their network.

The Flaw: The String Comparison Fallacy

The developers of esm.sh knew that SSRF was a risk. They implemented a safeguard function, likely something akin to isLocalhost, to forbid requests to loopback addresses. Their logic was simple: check the input URL.

If the hostname is "localhost", block it. If it is "127.0.0.1", block it. If it starts with "192.168.", block it.

This is the String Comparison Fallacy. It is like a bouncer at a club who has a list of banned names. If 'John Doe' is banned, but John shows up wearing a fake mustache and calls himself 'Juan Dough', the bouncer lets him in. The bouncer checks the name (the string), not the person (the IP address).

In the world of TCP/IP, computers do not care about strings; they care about IP addresses. Validation that happens before DNS resolution is effectively useless against a motivated attacker. We don't need to use the string "localhost"; we just need a string that becomes localhost when the server asks the DNS resolver.

The Code: Pinning the Tail on the Wrong Donkey

Let's look at the fix introduced in commit 0593516c4cfab49ad3b4900416a8432ff2e23eb0. The developers realized that redirects were a major issue. Previously, you could point the validator to safe.com, which would then return a 302 Redirect to localhost. The initial check passes, and the HTTP client blindly follows the redirect into the danger zone.

To fix this, they introduced a map called allowedHosts and a CheckRedirect hook:

// Simplified view of the patch
client := &http.Client{
    CheckRedirect: func(req *http.Request, via []*http.Request) error {
        // Only allow if the host was explicitly allowed during the initial check
        if _, ok := allowedHosts[req.URL.Host]; !ok {
            return fmt.Errorf("redirect host %s not allowed", req.URL.Host)
        }
        return nil
    },
}

This code (paraphrased for clarity) effectively pins the request to the original hostname. If you start at example.com, you must stay at example.com.

The Problem? This fixes redirect-based SSRF, but it arguably misses the forest for the trees. If the initial allowedHost is evil-alias.com, and evil-alias.com resolves to 127.0.0.1, the map entry is created for evil-alias.com. The redirect check is never triggered because there is no redirect—just a direct connection to a local IP disguised as a public domain.

The Exploit: DNS Rebinding & Magic Domains

Exploiting this requires zero coding and about 30 seconds of setup. We utilize a 'magic' DNS service like nip.io or sslip.io. These services automatically resolve subdomains to the IP address contained within the string.

The Attack Chain:

  1. Target Selection: We want to hit the local admin interface, usually running on port 80 or 8080.
  2. Payload Construction: We construct a domain: 127.0.0.1.nip.io. To the esm.sh validator, this string does not equal "localhost" or "127.0.0.1". It looks like a valid external domain.
  3. Execution: We send the GET request: GET https://esm.sh/http://127.0.0.1.nip.io:80/admin

What happens inside the server?

  1. Validation: The app checks: if "127.0.0.1.nip.io" in blacklist. False. Request allowed.
  2. Resolution: The Go net/http client asks DNS: "Who is 127.0.0.1.nip.io?"
  3. Connection: DNS replies: "It is 127.0.0.1". The client connects to itself.
  4. Exfiltration: The server returns the content of the internal admin panel to us.

> [!NOTE] > Cloud environments are even juicier. Using 169.254.169.254.nip.io allows an attacker to steal AWS IAM credentials or instance metadata.

The Impact: From Proxy to Pivot

The impact of a blind SSRF like this ranges from "annoying" to "business-ending," depending on where the esm.sh instance is hosted.

If hosted in a Kubernetes cluster, an attacker can hit the Kubelet API, potentially dumping pod configurations or secrets. If hosted on AWS/GCP/Azure, the Instance Metadata Service (IMDS) is the primary target. While modern IMDS (IMDSv2) requires a token header, many legacy setups or misconfigured proxies (which might forward headers inadvertently) are still vulnerable to IMDSv1.

Furthermore, this turns the CDN into a port scanner. An attacker can fuzz ports on the local network (192.168.1.1.nip.io:22, :23, :25) and analyze the response times or error messages to map out the internal infrastructure. The server becomes a pivot point for further attacks.

The Fix: Trust No One, Resolve Everything

The patch in version 137 adds friction by preventing redirects, which is good practice. However, the only true fix for SSRF is DNS Resolution at the Application Layer.

How to actually fix this:

  1. Resolve First: Do not pass the hostname to the HTTP client. Resolve it to an IP address first using a safe resolver.
  2. Validate the IP: Check the resolved IP against a strict allowlist or blocklist (RFC 1918 private ranges, loopback, link-local).
  3. Fetch by IP: If the IP is safe, make the request to the IP address, setting the Host header manually to the original hostname (to support virtual hosting).

Until the validation logic moves from String-land to IP-land, bypasses will likely continue to surface.

Official Patches

esm.shGitHub Commit fixing redirect SSRF

Fix Analysis (1)

Technical Appendix

CVSS Score
8.6/ 10
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N

Affected Systems

esm.sh <= v136

Affected Versions Detail

Product
Affected Versions
Fixed Version
esm.sh
esm-dev
<= 137137
AttributeDetail
CWE IDCWE-918
Attack VectorNetwork
CVSS v3.08.6 (High)
Attack ComplexityLow
Privileges RequiredNone
Exploit MaturityProof of Concept

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1005Data from Local System
Collection
CWE-918
Server-Side Request Forgery (SSRF)

Server-Side Request Forgery (SSRF)

Known Exploits & Detection

Research AnalysisDNS alias bypass using nip.io domains
NucleiDetection Template Available

Vulnerability Timeline

Fix commit merged
2025-06-16
Vulnerability Published
2026-02-25
CVE-2026-27730 Assigned
2026-02-25

References & Sources

  • [1]GitHub Advisory GHSA-p2v6-84h2-5x4r

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 2 hours ago•GHSA-F897-62HF-6GXQ
7.5

GHSA-f897-62hf-6gxq: Unbounded Request Body and JSON-RPC Batch DoS in Model Context Protocol TypeScript SDK

The Model Context Protocol (MCP) TypeScript SDK contains an uncontrolled resource consumption vulnerability in its HTTP server transport implementations and middleware adapters. Affected versions fail to enforce upper bounds on incoming HTTP POST request body sizes and JSON-RPC 2.0 batch array lengths. In addition, middleware wrappers for Express and Hono parse request bodies into memory prior to validating Host and Origin headers. Unauthenticated remote attackers can exploit these flaws to trigger Out-Of-Memory (OOM) process crashes or monopolize the single-threaded Node.js event loop.

Amit Schendel
Amit Schendel
1 views•11 min read
•about 2 hours ago•CVE-2026-87029
5.9

CVE-2026-87029: Remote Denial of Service via Uncaught Exception in Node.js Undici Decompress Interceptor

An uncaught exception vulnerability (CWE-248) exists in the Node.js HTTP/1.1 client library undici, affecting versions 7.15.0 through 7.30.0 and 8.0.0 through 8.12.1. The flaw resides in the DecompressHandler interceptor (lib/interceptor/decompress.js), which processes compressed HTTP response streams using asynchronous Node.js zlib callbacks. When forwarding decompressed data chunks or stream completion events to downstream handlers, the interceptor fails to wrap the invocations of super.onResponseData() and super.onResponseEnd() in try...catch blocks. If a downstream interceptor or handler throws an exception during response processing—such as the built-in dump interceptor enforcing a maximum response size—the exception escapes the asynchronous zlib callback stack onto the top-level Node.js event loop, terminating the entire client process.

Alon Barad
Alon Barad
2 views•10 min read
•about 3 hours ago•CVE-2026-109932
5.9

CVE-2026-109932: Unbounded Memory Consumption via WebSocket PING Frame Flood in Node.js undici

An uncontrolled resource consumption vulnerability in the WebSocket client implementation of Node.js undici allows a remote WebSocket server to trigger an out-of-memory (OOM) crash in the client process. The vulnerability stems from the failure of the incoming frame parser (ByteParser) to respect socket write backpressure when automatically emitting PONG responses to incoming PING control frames. By continuously streaming small 2-byte PING frames while refusing to read from the TCP connection, a malicious server forces the client to queue outbound PONG frames in the V8 heap without bound.

Alon Barad
Alon Barad
2 views•10 min read
•about 4 hours ago•CVE-2026-87018
7.5

CVE-2026-87018: Denial of Service via TLS 1.2 Renegotiation in Node.js Undici

CVE-2026-87018 (GHSA-qrxm-fqhp-jwvm) is a high-severity Denial of Service (DoS) vulnerability in undici, the default HTTP/1.1 and HTTP/2 client for Node.js that backs the global fetch() implementation. The flaw stems from reachable assertions in undici's socket and session error handlers, which assume that TLS hostname verification errors (ERR_TLS_CERT_ALTNAME_INVALID) cannot occur after the initial connection establishment. By initiating a post-handshake TLS 1.2 renegotiation and presenting a certificate with a mismatched Subject Alternative Name (SAN), a remote HTTPS server can trigger this assertion and terminate the entire Node.js runtime process via an uncaught AssertionError.

Amit Schendel
Amit Schendel
3 views•11 min read
•about 5 hours ago•CVE-2026-116682
8.6

CVE-2026-116682: Arbitrary File Read via Improper Symlink Resolution in FileBrowser Quantum

FileBrowser Quantum prior to versions 1.5.2-stable and 2.0.1-beta contains an improper link resolution vulnerability (CWE-59) in its file indexing and retrieval subsystems. When processing file read, download, preview, media streaming, OnlyOffice integration, or public share requests, the application resolves symbolic links via Go's filepath.EvalSymlinks without verifying that the canonical target path remains within the configured source root and the user's assigned access scope. Consequently, authenticated users with read access or unauthenticated holders of public share links can read arbitrary server-accessible files outside their designated directory boundaries.

Amit Schendel
Amit Schendel
5 views•10 min read
•about 7 hours ago•CVE-2026-116676
8.8

CVE-2026-116676: OS Command Injection via Terraform Workspace Parameter in Atlantis

CVE-2026-116676 (GHSA-rjh2-4xgw-p2hj) is an OS command injection vulnerability (CWE-78) in Atlantis, an open-source Go application that automates Terraform pull request workflows via webhooks. From version 0.4.0 prior to version 0.47.1, Atlantis constructed Terraform and Conftest commands by joining argument slices into a single space-separated string and passing them to a shell interpreter (`sh -c`). Because user-controlled workspace names from `atlantis.yaml`, pull request comments, HCL configurations, and API requests were insufficiently validated against shell metacharacters, an authenticated contributor eligible to trigger a Terraform plan can execute arbitrary commands under the Atlantis service account.

Amit Schendel
Amit Schendel
5 views•11 min read