GitHub Security Advisory: GHSA-vvfw-4m39-fjqf
Package: wwbn/avideo (COMPOSER)
Severity: HIGH (CVSS 8.3)
CWE: CWE-352: Cross-Site Request Forgery (CSRF)
Vulnerable versions: <= 29.0
Patched in: unpatched

Summary: WWBN AVideo has CSRF in configurationUpdate.json.php Enables Full Site Configuration Takeover Including Encoder URL and SMTP Credentials

Details:
## Summary

`objects/configurationUpdate.json.php` (also routed via `/updateConfig`) persists dozens of global site settings from `$_POST` but protects the endpoint only with `User::isAdmin()`. It does not call `forbidIfIsUntrustedRequest()`, does not verify a `globalToken`, and does not validate the Origin/Referer header. Because AVideo intentionally sets `session.cookie_samesite=None` to support cross-origin iframe embedding, a logged-in administrator who visits an attacker-controlled page will have the browser auto-submit a cross-origin POST that rewrites the site's encoder URL, SMTP credentials, site `<head>` HTML, logo, favicon, contact email, and more in a single request.

## Details

The entire authorization and CSRF check for the endpoint is this block at `objects/configurationUpdate.json.php:10`:

```php
require_once $global['systemRootPath'] . 'objects/user.php';
if (!User::isAdmin()) {
    die('{"error":"' . __("Permission denied") . '"}');
}
```

Immediately after, `$_POST` values are written straight into the global `AVideoConf` object and persisted:

```php
// objects/configurationUpdate.json.php
$config = new AVideoConf();
$config->setContactEmail($_POST['contactEmail']);          // :21
$config->setLanguage($_POST['language']);                  // :22
$config->setWebSiteTitle($_POST['webSiteTitle']);          // :23
$config->setDescription($_POST['description']);           // :24
$config->setAuthCanComment($_POST['authCanComment']);     // :25
$config->setAuthCanUploadVideos($_POST['authCanUploadVideos']); // :26
// Advanced (default enabled — $global['disableAdvancedConfigurations'] is empty by default):
$config->setEncoderURL($_POST['encoder_url']);            // :32
$config->setSmtp($_POST['smtp']);                         // :33
$config->setSmtpAuth($_POST['smtpAuth']);                 // :34
$config->setSmtpSecure($_POST['smtpSecure']);             // :35
$config->setSmtpHost($_POST['smtpHost']);                 // :36
$config->setSmtpUsername($_POST['smtpUsername']);         // :37
$config->setSmtpPassword($_POST['smtpPassword']);         // :38
$config->setSmtpPort($_POST['smtpPort']);                 // :39
$config->setHead($_POST['head']);                         // :42
// ...
// Logo / favicon writes:
$fileData = base64DataToImage($_POST['logoImgBase64']);   // :68
file_put_contents($global['systemRootPath'] . $photoURL, $fileData); // :71
// favicon base64 → file_put_contents → ImageMagick `convert` invocation (:88-120)
echo '{"status":"' . $config->save() . '", ...}';         // :130
```

### Why CSRF actually lands

1. **SameSite is intentionally `None`.** `objects/include_config.php:144` sets `ini_set('session.cookie_samesite', 'None')` and the adjacent comment states the design: *"SameSite=None is intentional: AVideo supports cross-origin iframe embedding… All state-mutating endpoints that are vulnerable to CSRF must instead enforce a short-lived globalToken (verifyToken)."* This endpoint enforces no such token.

2.