
CVE-2023-41564 का विस्तृत विश्लेषण और proof-of-concept, जो Cockpit CMS <=2.6.3 में एक stored XSS कमजोरी है, जिसमें कोड-स्तरीय मूल कारण और शमन मार्गदर्शन शामिल है।
CVE-2023-415641 Cockpit नामक CMS (<= v2.6.3) में मौजूद एक संग्रहीत क्रॉस-साइट स्क्रिप्टिंग (Stored XSS) भेद्यता है।
फ़ाइल अपलोड के दौरान .shtml एक्सटेंशन वाली HTML फ़ाइल अपलोड करने पर, उस अपलोड की गई फ़ाइल को खोलते ही JavaScript निष्पादित हो जाती है।
1: https://nvd.nist.gov/vuln/detail/CVE-2023-41564
सबसे पहले, संभावित भेद्यता रिपोर्टर द्वारा प्रकाशित PoC2 के आधार पर हमले की विधि समझाते हैं।
पहले, फ़ाइल अपलोड अनुमति वाले खाते से साइट पर लॉग इन करें।
फिर नीचे दिए गए shtml फ़ाइल को अपलोड करें।
अपलोड की गई फ़ाइल (Asset) का URL कॉपी करें।
कॉपी किए गए लिंक तक पहुँचने पर, उपरोक्त HTML में वर्णित स्क्रिप्ट सक्रिय हो जाती है।
2: https://github.com/LongHair00/Mitre_opensource_report/blob/main/CockpitCMS-StoredXSS.md
अब मैं अपने गहन अनुसंधान के परिणामों की व्याख्या करूँगा।
Assets की अपलोड प्रक्रिया /assets/upload पथ पर POST अनुरोध से शुरू होती है।
अपलोड अनुरोध का प्रसंस्करण modules/Assets/bootstrap.php की पंक्ति 513 में परिभाषित है।
नीचे कोड दिखाया गया है।
// 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];
},
// 省略
]);
upload फ़ंक्शन App वर्ग के registry['modules']['assets'] नामक सदस्य चर में संग्रहीत है, जो Assets मॉड्यूल के फ़ंक्शन को संग्रहीत करता है।
इसलिए, upload फ़ंक्शन को कॉल करते समय $this->module('assets')->upload('file', $file); जैसा कॉल किया जाता है।
यहाँ पंजीकृत upload फ़ंक्शन Assets वर्ग4 द्वारा कॉल किया जाता है।
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);
}
आप देख सकते हैं कि registry['modules']['assets'] में पंजीकृत upload फ़ंक्शन को कॉल किया जा रहा है।
अब bootstrap.php में वर्णित upload फ़ंक्शन को देखते हैं।
यहाँ $forbiddenExtension चर में प्रतिबंधित एक्सटेंशन परिभाषित किए गए हैं।
लेकिन, shtml एक्सटेंशन छूट गया है।
इसलिए, यह एक दोष है जिसके कारण shtml फ़ाइल अपलोड करना संभव है।
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
चूँकि shtml की अनुमति न होना इसका कारण है, इसलिए forbiddenExtension में shtml जोड़कर उपाय किया जा सकता है।
$forbiddenExtension = ['bat', 'exe', 'sh', 'php', 'phar', 'phtml', 'phps', 'htm', 'html', 'xhtml', 'htaccess', 'shtml'];