GitHub Security Advisory: GHSA-ffr8-fxhv-fv8h
Package: wwbn/avideo (COMPOSER)
Severity: HIGH (CVSS 7.1)
CWE: CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Vulnerable versions: <= 26.0
Patched in: unpatched

Summary: AVideo is Vulnerable to SQL Injection through Subscribe Endpoint via Unsanitized user_id Parameter

Details:
## Summary

The `Subscribe::save()` method in `objects/subscribe.php` concatenates the `$this->users_id` property directly into an INSERT SQL query without sanitization or parameterized binding. This property originates from `$_POST['user_id']` in both `subscribe.json.php` and `subscribeNotify.json.php`. An authenticated attacker can inject arbitrary SQL to extract sensitive data from any database table, including password hashes, API keys, and encryption salts.

## Details

The vulnerability exists because of a disconnect between where `intval()` is applied and where the value is used in SQL.

**Entry points** — `objects/subscribe.json.php:40` and `objects/subscribeNotify.json.php:23`:

```php
// subscribe.json.php line 40
$subscribe = new Subscribe(0, $_POST['email'], $_POST['user_id'], User::getId());
```

**Constructor stores raw value** — `objects/subscribe.php:34`:

```php
public function __construct($id, $email = "", $user_id = "", $subscriber_users_id = "")
{
    // ...
    $this->users_id = $user_id;  // Raw $_POST['user_id'], no sanitization
    $this->subscriber_users_id = $subscriber_users_id;
    if (empty($this->id)) {
        $this->loadFromId($this->subscriber_users_id, $user_id, "");
    }
}
```

**`getSubscribeFromID` sanitizes local copies only** — `objects/subscribe.php:137-139`:

```php
public static function getSubscribeFromID($subscriber_users_id, $user_id, $status = "a"){
    $subscriber_users_id = intval($subscriber_users_id);  // Local variable only
    $user_id = intval($user_id);  // Local variable only — $this->users_id is NOT affected
```

When `getSubscribeFromID` finds no matching subscription (the attacker simply targets a user_id they haven't subscribed to), `loadFromId()` returns false. The object's `$this->id` remains null, and `$this->users_id` retains the unsanitized injection payload.

**Vulnerable sink** — `objects/subscribe.php:88`:

```php
public function save()
{
    if (!empty($this->id)) {
        // UPDATE path (not reached when $this->id is null)
    } else {
        $this->status = 'a';
        $sql = "INSERT INTO subscribes (users_id, email, status, ip, created, modified, subscriber_users_id) 
                VALUES ('{$this->users_id}', ...";  // Direct concatenation of injected value
    }
    $saved = sqlDAL::writeSql($sql);  // Called with NO $formats or $values
```

**`sqlDAL::writeSql` provides no protection** — `objects/mysql_dal.php:102`:

When called without `$formats`/`$values` parameters (as `save()` does), the `eval_mysql_bind()` function at line 636 returns `true` without binding any parameters. The already-concatenated SQL string is passed directly to `$global['mysqli']->prepare()` and `execute()`, executing the injection as the prepared statement itself.

## PoC

**Prerequisites:** An authenticated session on the target AVideo instance.

**Step 1: Confirm injection with time-based blind SQLi**

```bash
# Pick a user_id that the current user has NOT subscribed to (e.g., 99999)
# The S