CVEReports
CVEReports

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

Product

  • Home
  • Dashboard
  • 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
8.6

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

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 26, 2026·6 min read·3 visits

PoC Available

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.