GHSA-WC4H-2348-JC3P
Vulnerability from github – Published: 2026-04-03 03:30 – Updated: 2026-04-06 23:41Summary
Ech0 implements link preview (editor fetches a page title) through GET /api/website/title. That is legitimate product behavior, but the implementation is unsafe: the route is unauthenticated, accepts a fully attacker-controlled URL, performs a server-side GET, reads the entire response body into memory (io.ReadAll). There is no host allowlist, no SSRF filter, and InsecureSkipVerify: true on the outbound client.
Attacker outcome : Anyone who can reach the instance can force the Ech0 server to open HTTP/HTTPS URLs of their choice as seen from the server’s network position (Docker bridge, VPC, localhost from the process view).
Go’s default http.Client follows redirects (unless disabled). Redirect chains can move the server-side request from an allowed-looking host to an internal target; the code does not disable this in SendRequest.
Affected Components
Ech0 codebase:
-
internal/handler/common/common.go
Handles the/api/website/titleendpoint and accepts user-controlled URL input. -
internal/service/common/common.go
Processes the request and invokes the outbound HTTP fetch (GetWebsiteTitle). -
internal/util/http/http.go
Performs the HTTP request (SendRequest) with the following insecure configurations: - No URL validation or allowlist
- Redirects enabled (default client behavior)
InsecureSkipVerify: true
PoC
Environment: Ech0 listening on http://127.0.0.1:6277 (e.g. Docker image sn0wl1n/ech0:latest). No cookies or Authorization header.
Step 1 — baseline: unauthenticated server-side fetch (public URL):
curl.exe -sS -m 20 "http://127.0.0.1:6277/api/website/title?website_url=https://example.com"
Observed result (verified): HTTP 200, JSON with code: 1 and data Example Domain — proves the Ech0 process performed an outbound GET without any client auth.
Step 2 — impact: host-bound page + recorded leak (repo PoC file)
Committed PoC page: poc_ssrf_proof.html
- From
poc file directory, listen on 0.0.0.0 (port 9999):
python -m http.server 9999 --bind 0.0.0.0
- Docker Desktop (Windows / macOS): Ech0 in Docker fetches the host via
host.docker.internal:
curl.exe -sS -m 20 "http://127.0.0.1:6277/api/website/title?website_url=http://host.docker.internal:9999/poc_ssrf_proof.html"
Recorded response (verified this workspace, Ech0 4.2.2 in Docker):
{"code":1,"msg":"获取网站标题成功","data":"ECH0_SSRF_POC_LEAK_2026"}
Python server log: GET /poc_ssrf_proof.html → 200 (proves the server/container pulled the page from your host).
Leak channel: the backend reads the full HTML body before parsing (see io.ReadAll in SendRequest).
Impact
- Verified: Unauthenticated callers can make the Ech0 process issue server-side HTTP(S) requests to internal/reserved targets reachable from that process (PoC Step 2: host-reachable listener reflected in JSON).
- Code-level: The full response is read into memory (
io.ReadAll); only the title string is returned. Combined with default HTTP redirect following (standardhttp.Clientbehavior; not disabled here), the effective request graph is larger than a single URL. - TLS:
InsecureSkipVerify: truemeans misissued or intercepted TLS to internal HTTPS services is still accepted from the server’s perspective. - Deployment-dependent: Where routing allows (typical cloud VMs),
169.254.169.254-class endpoints are in scope for the same code path; treat as *high. - DOS(Denial of Service): reading the whole body into memory with io.ReadAll is a DoS vector if you point it at a massive file.
Remediation
- Enforce SSRF-safe URL policy: allow only needed schemes/hosts; block link-local, metadata, and loopback unless explicitly required.
- Remove
InsecureSkipVerify; use normal TLS verification. - Limit redirects (disable or cap hops; re-validate each target).
- Add response size / timeout limits; optionally restrict egress at the network layer.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/lin-snow/ech0"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.4.8-0.20260401031029-4ca56fea5ba4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-35036"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-03T03:30:53Z",
"nvd_published_at": "2026-04-06T17:17:12Z",
"severity": "HIGH"
},
"details": "### Summary\n\nEch0 implements **link preview** (editor fetches a page title) through **`GET /api/website/title`**. That is **legitimate product behavior**, but the implementation is **unsafe**: the route is **unauthenticated**, accepts a **fully attacker-controlled URL**, performs a **server-side GET**, reads the **entire response body** into memory (`io.ReadAll`). There is **no** host allowlist, **no** SSRF filter, and **`InsecureSkipVerify: true`** on the outbound client.\n\n**Attacker outcome :** Anyone who can reach the instance can **force the Ech0 server** to open **HTTP/HTTPS URLs of their choice** as seen from the **server\u2019s network position** (Docker bridge, VPC, localhost from the process view). \nGo\u2019s default `http.Client` **follows redirects** (unless disabled). Redirect chains can move the server-side request from an allowed-looking host to an internal target; the code does not disable this in `SendRequest`.\n\n### Affected Components\n\n**Ech0 codebase:**\n\n- `internal/handler/common/common.go` \n Handles the `/api/website/title` endpoint and accepts user-controlled URL input.\n\n- `internal/service/common/common.go` \n Processes the request and invokes the outbound HTTP fetch (`GetWebsiteTitle`).\n\n- `internal/util/http/http.go` \n Performs the HTTP request (`SendRequest`) with the following insecure configurations:\n - No URL validation or allowlist\n - Redirects enabled (default client behavior)\n - `InsecureSkipVerify: true`\n\n### PoC \n\n**Environment:** Ech0 listening on `http://127.0.0.1:6277` (e.g. Docker image `sn0wl1n/ech0:latest`). No cookies or `Authorization` header.\n\n**Step 1 \u2014 baseline: unauthenticated server-side fetch (public URL):**\n\n```bash\ncurl.exe -sS -m 20 \"http://127.0.0.1:6277/api/website/title?website_url=https://example.com\"\n```\n\n**Observed result (verified):** HTTP 200, JSON with `code: 1` and `data` **`Example Domain`** \u2014 proves the **Ech0 process** performed an outbound GET without any client auth.\n\n**Step 2 \u2014 impact: host-bound page + recorded leak (repo PoC file)**\nCommitted PoC page: **`poc_ssrf_proof.html`** \n\n1. From **`poc file directory`**, listen on **0.0.0.0** (port **9999**):\n\n```bash\npython -m http.server 9999 --bind 0.0.0.0\n```\n\n2. **Docker Desktop (Windows / macOS):** Ech0 in Docker fetches the host via `host.docker.internal`:\n\n```bash\ncurl.exe -sS -m 20 \"http://127.0.0.1:6277/api/website/title?website_url=http://host.docker.internal:9999/poc_ssrf_proof.html\"\n```\n\n**Recorded response (verified this workspace, Ech0 4.2.2 in Docker):**\n\n```json\n{\"code\":1,\"msg\":\"\u83b7\u53d6\u7f51\u7ad9\u6807\u9898\u6210\u529f\",\"data\":\"ECH0_SSRF_POC_LEAK_2026\"}\n```\n\n**Python server log:** `GET /poc_ssrf_proof.html` \u2192 **200** (proves the **server/container** pulled the page from your host).\n\n**Leak channel:** the backend **reads the full HTML body** before parsing (see `io.ReadAll` in `SendRequest`).\n\n\n### Impact\n\n- **Verified:** Unauthenticated callers can make the Ech0 process issue **server-side HTTP(S) requests** to **internal/reserved targets** reachable from that process (PoC Step 2: host-reachable listener reflected in JSON).\n- **Code-level:** The full response is **read into memory** (`io.ReadAll`); only the title string is returned. Combined with **default HTTP redirect following** (standard `http.Client` behavior; not disabled here), the effective request graph is larger than a single URL.\n- **TLS:** `InsecureSkipVerify: true` means **misissued or intercepted TLS** to internal HTTPS services is still accepted from the server\u2019s perspective.\n- **Deployment-dependent:** Where routing allows (typical cloud VMs), **`169.254.169.254`-class** endpoints are in scope for the **same code path**; treat as **high*.\n- **DOS(Denial of Service)**: reading the whole body into memory with io.ReadAll is a DoS vector if you point it at a massive file.\n\n\n## Remediation\n\n- Enforce **SSRF-safe URL policy**: allow only needed schemes/hosts; block link-local, metadata, and loopback unless explicitly required.\n- Remove **`InsecureSkipVerify`**; use normal TLS verification.\n- **Limit redirects** (disable or cap hops; re-validate each target).\n- Add **response size / timeout** limits; optionally restrict egress at the **network** layer.",
"id": "GHSA-wc4h-2348-jc3p",
"modified": "2026-04-06T23:41:04Z",
"published": "2026-04-03T03:30:53Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/lin-snow/Ech0/security/advisories/GHSA-wc4h-2348-jc3p"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-35036"
},
{
"type": "PACKAGE",
"url": "https://github.com/lin-snow/Ech0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Ech0 has Unauthenticated Server-Side Request Forgery in Website Preview Feature"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.