Skip to content
KitploitKITPLOIT
工具博客
Log in
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
工具/GitHubGitHub/sota70/cve-2023-41564-research
漏洞分析漏洞利用Web应用程序漏洞利用Web安全渗透测试学习与教育
GitHubsota70/cve-2023-41564-research

cve-2023-41564-research

CVE-2023-41564 的详细分析与概念验证,该漏洞是 Cockpit CMS <=2.6.3 中的一个存储型 XSS 漏洞,包含代码级根本原因及缓解指南。

查看仓库
401年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2023-41564 漏洞概要

CVE-2023-415641 是 Cockpit CMS(<= v2.6.3)中存在的一个存储型 XSS 漏洞。
在文件上传时,若上传 .shtml 扩展名的 HTML 文件,则该上传文件被打开时 JavaScript 会立即执行。

1: https://nvd.nist.gov/vuln/detail/CVE-2023-41564

攻击成功流程

首先,基于疑似漏洞报告者公开的 PoC2,讲解攻击方法。
首先,使用拥有文件上传权限的账户登录网站。
cms login cms assets 然后上传如下所示的 shtml 文件。

复制已上传文件(Asset)的 URL。 cms link 访问复制的链接后,上述 HTML 中描述的脚本将执行。 cms xss
2: https://github.com/LongHair00/Mitre_opensource_report/blob/main/CockpitCMS-StoredXSS.md

具体发生过程

此部分将说明我进一步深入调查的结果。
Asset 的上传处理始于对 /assets/upload 路径的 POST 请求。
上传请求的处理定义在 modules/Assets/bootstrap.php 的第 51 行3。
代码如下所示。

// 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'];
下载工具