The currentDirectory request parameter in the Flow.js media upload endpoint (POST /api/station/{station_id}/files/upload) is not sanitized for path traversal sequences. When combined with a local filesystem storage backend (the default), an authenticated user with media management permissions can write arbitrary files outside the station's media storage directory, achieving remote code execution by writing a PHP webshell to the web root.
In backend/src/Controller/Api/Stations/Files/FlowUploadAction.php, the currentDirectory parameter is read directly from user input at line 79 and prepended to the sanitized filename at line 83:
// FlowUploadAction.php:79-84
$currentDir = Types::string($request->getParam('currentDirectory'));
$destPath = $flowResponse->getClientFullPath();
if (!empty($currentDir)) {
$destPath = $currentDir . '/' . $destPath;
}
While $flowResponse->getClientFullPath() is sanitized via UploadedFile::filterClientPath() (which strips .. segments), the $currentDir value is prepended after this sanitization, reintroducing traversal capability.
This $destPath is passed to MediaProcessor::processAndUpload() at line 95-98. The critical issue is in the finally block at backend/src/Media/MediaProcessor.php:114-117:
// MediaProcessor.php:75-117
try {
if (MimeType::isFileProcessable($localPath)) {
// ... process media ...
return $record;
}
// ...
throw CannotProcessMediaException::forPath($path, 'File type cannot be processed.');
} catch (CannotProcessMediaException $e) {
$this->unprocessableMediaRepo->setForPath($storageLocation, $path, $e->getMessage());
throw $e;
} finally {
$fs->uploadAndDeleteOriginal($localPath, $path); // ALWAYS executes
}
The finally block writes the file to the traversed path regardless of whether the file passes MIME type validation. A .php file triggers CannotProcessMediaException, but the...
0.23.6Exploitability
AV:NAC:LPR:LUI:NScope
S:UImpact
C:HI:HA:H8.8/CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H