It might be necessary to apply some mitigation using external tools to give Dev teams more time to fix critical vulnerabilities, especially for large codebases. Employing Web Application Firewall could be such a temporary solution.

The article covers setting up a test Kubernetes environment with NGINX Ingress controller and ModSecurity WAF to block Spring RCE (CVE-2022-22965), known as Spring4Shell.

Kubernetes on macOS

One of the ways to get Kubernetes on a macOS machine is to enable it in the Docker desktop app.

If Kubernetes is up, the command kubectl get nodes should show a single node called docker-desktop.

Nginx ingress controller

We can install Nginx ingress controller using helm or create Kubernetes resources using kubectl:

helm upgrade --install ingress-nginx ingress-nginx \
  --repo \
  --namespace ingress-nginx --create-namespace


kubectl apply -f

We have to wait until the ingress pod gets ready before proceeding:

kubectl wait \
  --namespace ingress-nginx \
  --for=condition=ready pod \ \

Test application

We are going to create a simple web server and an ingress resource.

kubectl create deployment modsecurity-demo --image=httpd --port=80
kubectl expose deployment modsecurity-demo
kubectl create ingress demo-localhost --class=nginx \

To access our k8s application, we need to add the following record to the /etc/hosts file: modsecurity

We need to keep the tunnel to ingress pod up for all the subsequent actions:

kubectl port-forward --namespace=ingress-nginx service/ingress-nginx-controller 8080:80

Let’s test our environment:

curl http://modsecurity:8080/

We should get <html><body><h1>It works!</h1></body></html>

ModSecurity Nginx module

To enable ModSecurity, we need to edit the ingress ConfigMap:

kubectl edit configmaps --namespace ingress-nginx ingress-nginx-controller

and put the following excerpt in the data key:

  allow-snippet-annotations: "true"
  enable-modsecurity: "true"
  enable-owasp-modsecurity-crs: "true"

We could check if the configuration is ok by the command:

kubectl logs -f --namespace ingress-nginx \

The following logs are a good sign:

I0401 13:36:43.188153       8 controller.go:159] "Configuration changes detected, backend reload required"
I0401 13:36:43.457233       8 controller.go:176] "Backend successfully reloaded"

Now, we have enabled ModSecurity and The OWASP ModSecurity Core Rule Set.

For more complex solutions, examine the following files:

  • /etc/nginx/modsecurity/modsecurity.conf;
  • /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf;

By default, ModSecurity works in the detection mode.

To block attacks, we override the config using the modsecurity-snippet key:

  allow-snippet-annotations: "true"
  enable-modsecurity: "true"
  enable-owasp-modsecurity-crs: "true"
  modsecurity-snippet: |-
    SecRuleEngine On
    SecRequestBodyAccess On

The command curl 'http://modsecurity:8080/?param="><script>alert(1);</script>' should return

<head><title>403 Forbidden</title></head>
<center><h1>403 Forbidden</h1></center>

Spring4Shell exploit (CVE-2022-22965)

We may represent the Spring4Shell exploit using the following request:

curl 'http://modsecurity:8080/?!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di'

That request will be blocked by rule 944130 of OWASP ModSecurity Core Rule Set as it contains certain Java classes listed here.

What if an attacker could use other classes?

The common thing among these types of attacks is the usage of Java Classloader (an object that is responsible for loading classes).

Let’s see how we can catch it.

ModSecurity rules

ModSecurity rules have the following syntax:


Variables represent parts of the request. In this demo, we will employ ARGS and ARGS_NAMES variables.

  • ARGS contains all arguments, including the POST payload
  • ARGS_NAMES contains all request parameter names.

The operator is an expression specifying how and what to search in arguments. If the operator name is omitted, the string is interpreted as a regular expression.

We might use something like \.classLoader\. for our purpose.

We want to block matched requests and add some meaningful descriptions to logs. We use the following action string for that:

log,deny,phase:2,id:76000001,severity:CRITICAL,msg:\'Possible Spring4Shell exploitation\'

Let’s put it all together in the ingress-nginx-controller ConfigMap as

  allow-snippet-annotations: "true"
  enable-modsecurity: "true"
  enable-owasp-modsecurity-crs: "true"
  modsecurity-snippet: |-
    SecRuleEngine On
    SecRequestBodyAccess On
    SecRule ARGS|ARGS_NAMES "\.classLoader\." "log,\
      msg:\'Possible Spring4Shell exploitation\'"

Now, if we request

curl 'http://modsecurity:8080/?class.module.classLoader.resources.context.parent.pipeline.first.pattern=pupalupa'


curl -X POST http://modsecurity:8080/ -d 'class.module.classLoader.resources.context.parent.pipeline.first.pattern=pupalupa'

we will get the 403 Forbidden error.

Do we need to think about URL encoding bypasses and use some input preprocessing? No, ModSecurity automatically decodes URL on request parameters.

But if you are deliberate in performing URL decoding twice, use the urlDecode function.

That is an oversimplified example to get a glimpse of the usage of ModSecurity WAF in Kubernetes. I highly recommend the ModSecurity documentation.