
CVE-2023-415641 ist eine gespeicherte XSS-Schwachstelle (Stored XSS) im CMS Cockpit (<= v2.6.3).
Wenn beim Datei-Upload eine HTML-Datei mit der Erweiterung .shtml hochgeladen wird, wird in dem Moment, in dem die hochgeladene Datei geöffnet wird, JavaScript ausgeführt.
1: https://nvd.nist.gov/vuln/detail/CVE-2023-41564
Zunächst erkläre ich die Angriffsmethode auf der Grundlage des PoC2, das von der Person veröffentlicht wurde, die vermutlich die Schwachstelle gemeldet hat.
Zuerst melden Sie sich mit einem Konto, das über Datei-Upload-Berechtigungen verfügt, auf der Website an.
Dort laden Sie eine shtml-Datei wie die folgende hoch.
Kopieren Sie die URL der hochgeladenen Datei (Asset).
Wenn Sie auf den kopierten Link zugreifen, wird das im obigen HTML beschriebene Skript ausgeführt.
2: https://github.com/LongHair00/Mitre_opensource_report/blob/main/CockpitCMS-StoredXSS.md
Ab hier erkläre ich die Ergebnisse meiner tiefergehenden Untersuchungen.
Der Upload-Prozess für Assets beginnt mit einer POST-Anfrage an den Pfad /assets/upload.
Die Verarbeitung der Upload-Anfrage ist in Zeile 513 von modules/Assets/bootstrap.php definiert.
Nachfolgend ist der Code dargestellt.
// assets api
$this->module('assets')->extend([
// 省略
'upload' => function(string|array $param = 'files', array $meta = [], bool $isUpload = true) {
$files = [];
if (is_string($param) && isset($this->app->request->files[$param])) {
$files = $this->app->request->files[$param];
} elseif (is_array($param) && isset($param['name'], $param['error'], $param['tmp_name'])) {
$files = $param;
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$uploaded = [];
$failed = [];
$_files = [];
$assets = [];
$allowed = $this->app->retrieve('assets/allowed_uploads', '*');
$allowed = $allowed == '*' ? true : str_replace([' ', ','], ['', '|'], preg_quote(is_array($allowed) ? implode(',', $allowed) : $allowed));
$max_size = $this->app->retrieve('assets/max_upload_size', 0);
$forbiddenExtension = ['bat', 'exe', 'sh', 'php', 'phar', 'phtml', 'phps', 'htm', 'html', 'xhtml', 'htaccess'];
$forbiddenMime = [
'application/x-httpd-php', 'application/x-php', 'text/x-php',
'text/html', 'application/xhtml+xml'
];
if (isset($files['name']) && is_array($files['name'])) {
$cnt = count($files['name']);
for ($i = 0; $i < $cnt; $i++) {
$_file = $this->app->path('#tmp:').'/'.$files['name'][$i];
$_mime = $finfo->file($files['tmp_name'][$i]);
$_isAllowed = $allowed === true ? true : preg_match("/\.({$allowed})$/i", $_file);
$_sizeAllowed = $max_size ? filesize($files['tmp_name'][$i]) < $max_size : true;
$extension = strtolower(pathinfo(parse_url($_file, PHP_URL_PATH), PATHINFO_EXTENSION));
if (!$extension) {
$_isAllowed = false;
}
// prevent uploading php / html files
if ($_isAllowed && (
in_array($extension, $forbiddenExtension) ||
in_array(strtolower($_mime), $forbiddenMime)
)) {
$_isAllowed = false;
}
if (!$files['error'][$i] && $_isAllowed && $_sizeAllowed && ($isUpload ? move_uploaded_file($files['tmp_name'][$i], $_file) : rename($files['tmp_name'][$i], $_file))) {
$_files[] = $_file;
$uploaded[] = $files['name'][$i];
if (\preg_match('/\.(svg|xml)$/i', $_file)) {
file_put_contents($_file, \SVGSanitizer::clean(\file_get_contents($_file)));
}
} else {
$failed[] = $files['name'][$i];
}
}
}
if (count($_files)) {
$assets = $this->add($_files, $meta, isset($meta['_id']));
foreach ($_files as $file) {
unlink($file);
}
}
return ['uploaded' => $uploaded, 'failed' => $failed, 'assets' => $assets];
},
// 省略
]);
Die upload-Funktion wird in der Membervariable registry['modules']['assets'] der App-Klasse gespeichert, welche die Funktionen des Assets-Moduls enthält.
Beim Aufruf der upload-Funktion wird diese daher wie folgt verwendet: $this->module('assets')->upload('file', $file);
Die hier registrierte upload-Funktion wird von der Assets-Klasse4 aufgerufen.
public function upload() {
this->helper('session')->close();
this->hasValidCsrfToken(true);
if (!this->isAllowed('assets/upload')) {
return $this->stop(['error' => 'Upload not allowed'], 401);
}
$meta = ['folder' => $this->param('folder', '')];
return $this->module('assets')->upload('files', $meta);
}
Wie man sehen kann, wird die in registry['modules']['assets'] registrierte upload-Funktion aufgerufen.
Schauen wir uns nun die in bootstrap.php beschriebene upload-Funktion an.
Dort werden in der Variable $forbiddenExtension die verbotenen Dateierweiterungen definiert.
Die Erweiterung shtml fehlt jedoch in dieser Liste.
Genau hier liegt der Fehler, wodurch das Hochladen von shtml-Dateien möglich wird.
3: https://github.com/Cockpit-HQ/Cockpit/blob/2.6.3/modules/Assets/bootstrap.php#L51
4: https://github.com/Cockpit-HQ/Cockpit/blob/2.6.3/modules/Assets/Controller/Assets.php#L140
Da die Ursache darin liegt, dass shtml nicht verboten ist, kann die Schwachstelle behoben werden, indem shtml zu forbiddenExtension hinzugefügt wird.
$forbiddenExtension = ['bat', 'exe', 'sh', 'php', 'phar', 'phtml', 'phps', 'htm', 'html', 'xhtml', 'htaccess', 'shtml'];