Have you tried the CrowdSec WAF yet? No? You’re looking for a Kubernetes tutorial, you say?
We got you!
The CrowdSec WAF is a compact solution that combines the classic benefits of a WAF with CrowdSec’s unique crowd-powered and behavior-based approach. It is also a cattle-friendly WAF and works seamlessly in Kubernetes and Docker environments.
In this tutorial, we’ll show you how to leverage the CrowdSec WAF to enhance the security of your Kubernetes applications. We’ll walk you through how to set up the CrowdSec WAF in a Kubernetes cluster to protect a vulnerable WordPress installation with the in-band rules. We will also show you how to write a custom rule to block specific attack vectors or specific use cases, such as requests to the xmlrpc.php
file in WordPress.
Pre-requisites
First things first — there are a few things you need to have in place in order to follow this tutorial.
- A working Kubernetes cluster (tested on minikube)
- Nginx Ingress Controller installed
- Helm
- Kubectl
- Nuclei
You can also find the source code for this tutorial in our GitHub repository.
Architecture overview
Before we explain our target infrastructure, an important note: this guide uses ingress-nginx version 1.11.4 (1.12.0 is the latest at the time of writing), as lua plugins support was removed in 1.12.0. We are currently exploring alternative solutions to support the newest version of ingress-nginx, but in the meantime, 1.11.4 is the latest version we can support.
Running the CrowdSec WAF in Kubernetes requires a few components:
- CrowdSec itself: the deployment consists of two pods.
- The AppSec pod: This pod will receive requests from the ingress, analyze them, and inform the ingress whether the request should be blocked or not.
- The LAPI pod: This pod will handle communication with the CrowdSec central API and provide authentication for the AppSec pod
- The ingress-nginx remediation component: a LUA plugin that will forward all requests to CrowdSec for analysis, and block malicious requests.
- An application to protect 🙂
Installing WordPress
Like we said, we need something to protect with our WAF!
For the sake of simplicity, we’ll deploy a simple WordPress instance by using the chart provided by Bitnami, but the next steps will work the same way for any web application you may have in your cluster.
helm install wordpress oci://registry-1.docker.io/bitnamicharts/wordpress -f wp-values.yaml
Installing WordPress vulnerable plugin to CVE-2024-1071
Let’s get a shell in the WordPress pod and install the vulnerable plugin.
kubectl exec -it wordpress-7797f4d564-zp6rd -- bash
Then install the plugin.
wp plugin install ultimate-member --version="2.8.2"
Note: Of course, do not do this if you are following along and have exposed your WordPress to the internet!
Enable the plugin in the WordPress admin panel and activate the Misc"=>"Enable custom table for usermeta
in the plugin settings.
Now, we’ll use nuclei to confirm that our application is indeed vulnerable. We will use the following template to check if the vulnerability is present.
$ nuclei -u http://mywp.local:31669 -t http/cves/2024/CVE-2024-1071.yaml
__ _
____ __ _______/ /__ (_)
/ __ \/ / / / ___/ / _ \/ /
/ / / / /_/ / /__/ / __/ /
/_/ /_/\__,_/\___/_/\___/_/ v3.1.7
projectdiscovery.io
[INF] Current nuclei version: v3.1.7
[INF] Current nuclei-templates version: v10.1.0
[WRN] Scan results upload to cloud is disabled.
[INF] New templates added in latest release: 114
[INF] Templates loaded for current scan: 1
[WRN] Executing 1 unsigned templates. Use with caution.
[INF] Targets loaded for current scan: 1
[CVE-2024-1071] [http] [critical] http://mywp.local:31669/wp-admin/admin-ajax.php?action=um_get_members
Nuclei will return a critical vulnerability on the target.
Installing CrowdSec
Now that we have a vulnerable application, let’s protect it!
The first step is to install CrowdSec. We’ll be using our official helm chart with the following values.
container_runtime: containerd
image:
tag: v1.6.4
config:
config.yaml.local: |
api:
server:
auto_registration:
enabled: true
token: "${REGISTRATION_TOKEN}"
allowed_ranges:
- "127.0.0.1/32"
- "192.168.0.0/16"
- "10.0.0.0/8"
- "172.16.0.0/12"
agent:
acquisition:
- namespace: ingress-nginx
podName: ingress-nginx-controller-*
program: nginx
env:
- name: DISABLE_PARSERS
value: "crowdsecurity/whitelists"
- name: PARSERS
value: "crowdsecurity/cri-logs"
- name: COLLECTIONS
value: "crowdsecurity/nginx"
appsec:
enabled: true
acquisitions:
- source: appsec
listen_addr: "0.0.0.0:7422"
path: /
appsec_config: crowdsecurity/crs-vpatch
labels:
type: appsec
configs:
mycustom-appsec-config.yaml: |
name: crowdsecurity/crs-vpatch
default_remediation: ban
#log_level: debug
outofband_rules:
- crowdsecurity/crs
inband_rules:
- crowdsecurity/base-config
- crowdsecurity/vpatch-*
env:
- name: COLLECTIONS
value: "crowdsecurity/appsec-wordpress"
lapi:
replicas: 1
env:
- name: BOUNCER_KEY_nginx
value: 6b71a77194327e3bf00bcef884d2688c
- name: DISABLE_ONLINE_API
value: "true"
Most of it is pretty standard, the interesting parts are:
config:
config.yaml.local: |
api:
server:
auto_registration:
enabled: true
token: "${REGISTRATION_TOKEN}"
allowed_ranges:
- "127.0.0.1/32"
- "192.168.0.0/16"
- "10.0.0.0/8"
- "172.16.0.0/12"
This configures auto-validation of the log processors. This is especially useful when running in Kubernetes, as pods can come and go. With this setting, each log processor you have will use its own set of credentials and will be automatically accepted by LAPI.
Next, we have the CrowdSec WAF configuration.
appsec:
enabled: true
acquisitions:
- source: appsec
listen_addr: "0.0.0.0:7422"
path: /
appsec_config: my/custom-config
labels:
type: appsec
configs:
mycustom-appsec-config.yaml: |
name: my/custom-config
default_remediation: ban
inband_rules:
- crowdsecurity/base-config
- crowdsecurity/vpatch-*
env:
- name: COLLECTIONS
value: "crowdsecurity/appsec-wordpress"
When running in Kubernetes, the CrowdSec WAF runs in a dedicated log processor that will only handle the HTTP requests analysis (i.e., it won’t process any logs).
We first start by declaring an AppSec datasource, and loading the configuration called crowdsecurity/crs-vpatch
.
We also installed the crowdsecurity/appsec-wordpress collection, which contains all the virtual-patching rules to protect against the WordPress known vulnerabilities and, of course, contains the rule to protect against the CVE-2024-1071 vulnerability.
helm install crowdsec crowdsec/crowdsec -f crowdsec-values.yaml
Now, let’s check if all pods are running properly.
Setting up Nginx Ingress with CrowdSec
Patch the ingress to add the CrowdSec plugin.
helm upgrade -n ingress-nginx ingress-nginx ingress-nginx/ingress-nginx -f ingress-nginx-values.yaml
Retest the vulnerability
Thanks to the CrowdSec WAF and to the in-band rule that blocks requests to the vulnerable endpoint, the request will be blocked. This rule is part of the appsec-wordpress collection we installed earlier in the crowdsec-values.yaml
file.
$ nuclei -u http://mywp.local:31669 -t /tmp/CVE-2024-1071.yaml --debug
__ _
____ __ _______/ /__ (_)
/ __ \/ / / / ___/ / _ \/ /
/ / / / /_/ / /__/ / __/ /
/_/ /_/\__,_/\___/_/\___/_/ v3.1.7
projectdiscovery.io
[INF] Current nuclei version: v3.1.7
[INF] Current nuclei-templates version: v10.1.0
[WRN] Scan results upload to cloud is disabled.
[INF] New templates added in latest release: 114
[INF] Templates loaded for current scan: 1
[WRN] Executing 1 unsigned templates. Use with caution.
[INF] Targets loaded for current scan: 1
[INF] [CVE-2024-1071] Dumped HTTP request for http://mywp.local:31669/?p=1
....
[INF] [CVE-2024-1071] Dumped HTTP request for http://mywp.local:31669/wp-admin/admin-ajax.php?action=um_get_members
POST /wp-admin/admin-ajax.php?action=um_get_members HTTP/1.1
Host: mywp.local:31669
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36
Connection: close
Content-Length: 63
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip
directory_id=b9238&sorting=user_login,SLEEP(5)&nonce=be737783e4
[DBG] [CVE-2024-1071] Dumped HTTP response http://mywp.local:31669/wp-admin/admin-ajax.php?action=um_get_members
HTTP/1.1 403 Forbidden
Connection: close
Transfer-Encoding: chunked
Cache-Control: no-cache
Content-Type: text/html
Date: Fri, 06 Dec 2024 13:44:14 GMT
CrowdSec Ban
....
[INF] No results found. Better luck next time!
Ta-da! Nuclei will receive a 403 error from the server, and the request will be blocked by CrowdSec.
Writing a custom rule
We just saw how the CrowdSec WAF can block a critical vulnerability using virtual patching thanks to in-band rules from the CrowdSec Hub.
In this next step, we will show you how to write a custom rule for a specific use case. Our example is the xmlrpc.php
file in WordPress. This file is often used in brute force attacks. If users forget to disable it, it can be a security risk. We will write a custom rule to block requests to this file.
Following the documentation, we will write a rule to block requests to xmlrpc.php
.
name: crowdsecurity/vpatch-xmlrpc
debug: true
description: "Block XMLRPC requests"
rules:
- and:
- zones:
- METHOD
match:
type: equals
value: POST
- zones:
- URI
transform:
- lowercase
match:
type: endsWith
value: xmlrpc.php
labels:
type: exploit
service: http
behavior: "http:exploit"
label: "Block XMLRPC requests"
This rule will block any POST request to any URI ending with xmlrpc.php
.
Now let’s add this rule to the CrowdSec values and upgrade the helm chart.
# upgrade-crowdsec-values.yaml
appsec:
rules:
mycustom-appsec-rule.yaml: |
name: crowdsecurity/vpatch-xmlrpc
debug: true
description: "Block XMLRPC requests"
rules:
- and:
- zones:
- METHOD
match:
type: equals
value: POST
- zones:
- URI
transform:
- lowercase
match:
type: endsWith
value: xmlrpc.php
labels:
type: exploit
service: http
behavior: "http:exploit"
label: "Block XMLRPC requests"
helm upgrade -n crowdsec crowdsec crowdsec/crowdsec -f crowdsec-values.yaml -f upgrade-crowdsec-values.yaml
Now, we will test the rule by sending a POST request to xmlrpc.php
.
$ curl -XPOST http://mywp.local:31669/blog/xmlrpc.php | head
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
CrowdSec Ban
As expected, the request is blocked by CrowdSec, and we receive a 403 error.
Summing up
In this tutorial, we demonstrated the power of the CrowdSec WAF in a Kubernetes environment and how we seamlessly protected a vulnerable application. By using both a pre-made rule from the CrowdSec Hub and a custom rule, we quickly and easily made our WordPress application more secure by automatically blocking specific requests.
In a future article, we’ll explore how to use out-of-band rules to block attackers based on their behavior and to detect multi-step exploitation.
Stay safe and see you soon!