GitHub Security Advisory: GHSA-mwgh-92m2-wvhv
Package: wwbn/avideo (COMPOSER)
Severity: MODERATE (CVSS 4.3)
CWE: CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection')
Vulnerable versions: <= 29.0
Patched in: unpatched

Summary: AVideo: Unauthenticated CRLF/ICS Injection in Scheduler downloadICS.php Allows Calendar Event Spoofing

Details:
## Summary

The unauthenticated `plugin/Scheduler/downloadICS.php` endpoint passes attacker-controlled `title`, `description`, and `joinURL` parameters into `Scheduler::downloadICS()`, which builds an ICS calendar file via the `ICS` helper class. `ICS::escape_string()` (`objects/ICS.php:167-169`) only escapes `,` and `;` and does NOT neutralize CR/LF, so attacker CRLF bytes inside a property value break out and inject arbitrary ICS lines — including `END:VEVENT` / `BEGIN:VEVENT` pairs that add entire attacker-controlled calendar events. Because the malicious `.ics` file is served from the victim's trusted AVideo origin, this enables high-credibility calendar phishing: forged meetings with attacker-chosen `SUMMARY`, `URL`, `LOCATION`, and `DESCRIPTION` landing in the victim's calendar after import.

## Details

### Vulnerable code path

**`plugin/Scheduler/downloadICS.php`** — unauthenticated entry point:

```php
if(!AVideoPlugin::isEnabledByName('Scheduler')){
    forbiddenPage('Scheduler is disabled');
}
if(empty($_REQUEST['title'])){ forbiddenPage('Title cannot be empty'); }
if(empty($_REQUEST['date_start'])){ forbiddenPage('date_start cannot be empty'); }

Scheduler::downloadICS($_REQUEST['title'], $_REQUEST['date_start'], @$_REQUEST['date_end'],
    @$_REQUEST['reminder'], @$_REQUEST['joinURL'], @$_REQUEST['description']);
```

There is no session check, no CSRF token, no user-role check — only an empty-check on `title`/`date_start` and a plugin-enabled check.

**`plugin/Scheduler/Scheduler.php:367-382`** passes inputs directly to the ICS builder:

```php
$props = array(
    'location' => $location,
    'description' => $description,   // attacker-controlled
    'dtstart' => $dtstart,
    'dtend' => $dtend,
    'summary' => $title,             // attacker-controlled
    'url' => $joinURL,               // attacker-controlled
    'valarm' => $VALARM,
);
$ics = new ICS($props);
...
echo $icsString;
```

**`objects/ICS.php:167-169`** — incomplete escape:

```php
private function escape_string($str) {
    return preg_replace('/([\,;])/','\\\$1', $str);
}
```

Per RFC 5545 §3.3.11, TEXT values must also have CR/LF either folded or encoded as `\n`. This implementation does neither. `ICS::to_string()` (line 101) joins every property with `"\r\n"`, so any raw `\r\n` sequence embedded in a value breaks out of the property line and injects new ICS directives.

### Verified exploit output

Running the builder with a CRLF-laden `description` produces a file with two distinct `VEVENT` blocks (the second entirely attacker-controlled):

```
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DESCRIPTION:Hello
END:VEVENT
BEGIN:VEVENT
SUMMARY:Injected
URL:http://attacker.com
DTSTART:20260501T000000Z
DTEND:20260501T130000Z
SUMMARY:Legit
URL;VALUE=URI:https://example.com
DTSTAMP:20260424T082123Z
UID:69eb2803d1aa2
END:VEVENT
END:VCALENDAR
```

The injected `BEGIN:VEVENT` / `END:VEVENT` pair is standards-complian