Skip to content

Commit 3d4da9b

Browse files
authored
Merge pull request #842 from crowdsecurity/waf_rp_howto
Waf rp howto
2 parents ebe2628 + 4628da8 commit 3d4da9b

File tree

7 files changed

+287
-0
lines changed

7 files changed

+287
-0
lines changed

crowdsec-docs/docs/appsec/intro.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ You can follow our quick start guides depending on your web server:
6666

6767
- [Nginx/OpenResty](/appsec/quickstart/nginxopenresty.mdx)
6868
- [Traefik](/appsec/quickstart/traefik.mdx)
69+
- [CrowdSec WAF with Nginx Reverse Proxy](/u/user_guides/waf_rp_howto)
6970

7071
Or consider learning more about the AppSec capabilities:
7172

crowdsec-docs/sidebarsUnversioned.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ const sidebarsUnversionedConfig: SidebarConfig = {
629629
"user_guides/consuming_fastly_logs",
630630
"user_guides/alert_context",
631631
"user_guides/log_centralization",
632+
"user_guides/waf_rp_howto",
632633
],
633634
gettingStarted: [
634635
{
35.6 KB
Loading
41.9 KB
Loading
54.5 KB
Loading
44 KB
Loading
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
---
2+
id: waf_rp_howto
3+
title: CrowdSec WAF Reverse Proxy
4+
---
5+
6+
## Introduction
7+
8+
In this guide, we will showcase how to deploy the CrowdSec WAF with Nginx reverse proxy, easily protecting a fleet of other web applications from a single point.
9+
10+
We will set up a reverse proxy (Nginx) protected with CrowdSec in front of our web server (Apache) to block malicious traffic before it reaches our application.
11+
12+
**This article dives into the technical details of configuring CrowdSec WAF.**
13+
14+
To achieve robust protection, we'll use two key components that work in tandem: the **Security Engine** and the **Web Application Firewall (WAF)** *– enabled by an AppSec-capable Remediation Component aka **Bouncer**, in our case, CrowdSec’s NGINX Bouncer*
15+
16+
**The Security Engine**: excels at identifying persistent or recurring behaviours. It analyzes your web server/reverse proxy logs to identify suspicious patterns of behavior. For example, the http-probing scenario detects IPs rapidly requesting a large number of non-existent files – a common tactic used by vulnerability scanners searching known vulnerabilities, backdoors, or publicly exposed admin interfaces. While powerful and able to protect a large number service from various log sources, the Security Engine reacts **after** the request have been processed by your web server.
17+
18+
**The Web Application Firewall (WAF):** The WAF acts as your immediate response, blocking malicious requests before they even reach your application or backend. With the help of the bouncer/remediation component relaying the requests to the AppSec engine, it will apply virtual patching rules to block requests that are, without a doubt, malevolent. A great example is the `vpatch-env-access` rule, which blocks requests attempting to access .env files (which should never be publicly accessible\!). Our vpatching collection has hundreds of rules tailored to precisely block vulnerability attempts.
19+
20+
:::info
21+
Virtual Patching Rules focus on detecting and preventing the exploitation of a specific vulnerability, allowing very minimal risk of false positives.
22+
:::
23+
24+
**Together, these components provide layered protection, making it significantly harder for attackers to succeed.**
25+
26+
WAFs are powerful, but no matter what WAF vendors make you believe, determined attackers can often find ways to bypass your WAF configuration. Here, the Security Engine will rely on the WAF detection to make longer-term decisions against repeating malevolent IPs.
27+
28+
## Initial Setup
29+
30+
For our experiment, we’ll set up two Ubuntu 24.04 servers. One will be creatively called `webserver`, and the other will be called `nginx-RP`. We’ll install `apache` on `webserver`, configured to listen on port 3000, and `nginx` on `nginx-RP`. `webserver` has IP `Y.Y.Y.Y`, while `nginx-RP` has IP `X.X.X.X`.
31+
32+
We'll deploy the following configuration for nginx as a reverse proxy.
33+
34+
/etc/nginx/sites-enabled/default
35+
36+
```
37+
server {
38+
listen 80;
39+
40+
server_name _;
41+
42+
location / {
43+
proxy_pass http://Y.Y.Y.Y:3000; # Allows passing requests to the backend web server.
44+
proxy_set_header X-Real-IP $remote_addr; # Important to keep track of the original IP.
45+
proxy_http_version 1.1;
46+
proxy_set_header Upgrade $http_upgrade;
47+
proxy_set_header Connection 'upgrade';
48+
proxy_set_header Host $host;
49+
proxy_cache_bypass $http_upgrade;
50+
}
51+
}
52+
53+
54+
```
55+
56+
And the following Apache config:
57+
58+
/etc/apache2/sites-enabled/000-default.conf
59+
60+
```
61+
<VirtualHost *:3000>
62+
# The ServerName directive sets the request scheme, hostname and port that
63+
# the server uses to identify itself. This is used when creating
64+
# redirection URLs. In the context of virtual hosts, the ServerName
65+
# specifies what hostname must appear in the request's Host: header to
66+
# match this virtual host. For the default virtual host (this file) this
67+
# value is not decisive as it is used as a last resort host regardless.
68+
# However, you must set it for any further virtual host explicitly.
69+
#ServerName www.example.com
70+
71+
ServerAdmin webmaster@localhost
72+
DocumentRoot /var/www/html
73+
74+
RemoteIPHeader X-Real-IP
75+
RemoteIPTrustedProxy X.X.X.X
76+
77+
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
78+
# error, crit, alert, emerg.
79+
# It is also possible to configure the loglevel for particular
80+
# modules, e.g.
81+
#LogLevel info ssl:warn
82+
83+
ErrorLog ${APACHE_LOG_DIR}/error.log
84+
CustomLog ${APACHE_LOG_DIR}/access.log combined
85+
86+
# For most configuration files from conf-available/, which are
87+
# enabled or disabled at a global level, it is possible to
88+
# include a line for only one particular virtual host. For example the
89+
# following line enables the CGI configuration for this host only
90+
# after it has been globally disabled with "a2disconf".
91+
#Include conf-available/serve-cgi-bin.conf
92+
</VirtualHost>
93+
```
94+
95+
💡The two relevant parts here are `VirtualHost *:3000` to make Apache listen on port 3000, and `RemoteIPHeader` \+ `RemoteIPTrustedProxy` to ensure our logs contain the real IPs and not only the IP of the reverse proxy server.
96+
97+
So, if we now access the public IP of our reverse proxy, we’ll see Apache’s default page.
98+
99+
We do have our Nginx logs:
100+
101+
```bash
102+
==> /var/log/nginx/access.log <==
103+
A.B.C.D - - [22/May/2025:08:32:49 +0000] "GET / HTTP/1.1" 200 3121 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
104+
A.B.C.D - - [22/May/2025:08:32:49 +0000] "GET /icons/ubuntu-logo.png HTTP/1.1" 200 3322 "http://3.252.135.216/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
105+
A.B.C.D - - [22/May/2025:08:32:50 +0000] "GET /favicon.ico HTTP/1.1" 404 245 "http://3.252.135.216/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
106+
```
107+
108+
And the Apache logs:
109+
110+
```bash
111+
==> /var/log/apache2/access.log <==
112+
A.B.C.D - - [22/May/2025:08:32:49 +0000] "GET / HTTP/1.1" 200 3404 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
113+
A.B.C.D - - [22/May/2025:08:32:49 +0000] "GET /icons/ubuntu-logo.png HTTP/1.1" 200 3552 "http://3.252.135.216/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
114+
A.B.C.D - - [22/May/2025:08:32:50 +0000] "GET /favicon.ico HTTP/1.1" 404 438 "http://3.252.135.216/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
115+
116+
117+
```
118+
119+
:::warning
120+
121+
At this stage, check that both Apache's and Nginx's logs are the real originating IP (ie. `A.B.C.D`)
122+
123+
:::
124+
125+
## Time to level up our security - Security Engine
126+
127+
As soon as our server is online, hordes of malevolent IPs will jump on it with clear bad intentions. What is currently happening is this:
128+
129+
![initial setup](/img/original-setup.png)
130+
131+
Thus, it is time to step up our security with CrowdSec. We will deploy the Security Engine, the WAF, and an Nginx bouncer on our Reverse Proxy server so that we can achieve this:
132+
133+
![harden setup](/img/harden-setup.png)
134+
135+
To [install CrowdSec on our reverse proxy](https://doc.crowdsec.net/u/getting_started/installation/linux), let’s grab the crowdsec repository:
136+
137+
```bash
138+
curl -s https://install.crowdsec.net | sudo sh
139+
```
140+
141+
And let’s install crowdsec:
142+
143+
```bash
144+
sudo apt install crowdsec
145+
```
146+
147+
The relevant part of the install log is the following:
148+
149+
```bash
150+
Creating /etc/crowdsec/acquis.yaml
151+
INFO[2025-05-22 09:40:27] crowdsec_wizard: service 'nginx': /var/log/nginx/error.log /var/log/nginx/access.log
152+
INFO[2025-05-22 09:40:27] crowdsec_wizard: service 'ssh': /var/log/auth.log
153+
INFO[2025-05-22 09:40:27] crowdsec_wizard: service 'linux': /var/log/syslog /var/log/kern.log
154+
```
155+
156+
It detected we’re running nginx, and will automatically install the relevant scenarios and start monitoring the associated logfiles!
157+
158+
Then, we will enrol the SE in the console:
159+
160+
![cscli enroll](/img/cscli-enroll.png)
161+
162+
Accept it in the console:
163+
164+
![enroll console](/img/cscli-enroll-console-view.png)
165+
166+
## Detecting is cool, blocking is better.
167+
168+
To complete our setup, we need the ability to block bad IPs and requests before they reach Apache, our backend server. We will install the Nginx bouncer (or remediation component) for this. The bouncer can block IPs when instructed by CrowdSec. As simple as this:
169+
170+
```bash
171+
sudo apt install crowdsec-nginx-bouncer
172+
```
173+
174+
What matters in the installation output is that:
175+
176+
```bash
177+
cscli is /usr/bin/cscli
178+
cscli/crowdsec is present, generating API key
179+
API Key : <REDACTED>
180+
Restart nginx to enable the crowdsec bouncer : sudo systemctl restart nginx
181+
```
182+
183+
The bouncer installation detects a running CrowdSec on the same machine and in this case it will self-configure.
184+
185+
## Testing 🙂
186+
187+
From a 3rd-party machine, let’s try to trigger our newly deployed crowdsec. We're going to use the dedicated [crowdsecurity/http-generic-test](https://app.crowdsec.net/hub/author/crowdsecurity/scenarios/http-generic-test) to ensure our logs are properly processed:
188+
189+
```bash
190+
curl http://Y.Y.Y.Y/crowdsec-test-NtktlJHV4TfBSK3wvlhiOBnl
191+
```
192+
193+
On our reverse-proxy logs, we can see in CrowdSec and nginx:
194+
195+
```bash
196+
==> /var/log/crowdsec.log <==
197+
time="2025-08-06T13:06:44Z" level=info msg="Ip A.B.C.D performed 'crowdsecurity/http-generic-test' (1 events over 0s) at 2025-08-06 13:06:44.35424178 +0000 UTC"
198+
time="2025-08-06T13:06:44Z" level=info msg="(3ef52352a7e54c92b4394646a32bc095auADzhivHUYuhyJb) alert : crowdsecurity/http-generic-test by ip A.B.C.D (FR/5410)"
199+
```
200+
201+
202+
## Going further - Web Application Firewall
203+
204+
However, this approach has a limit: CrowdSec reads logs and acts based on their content, which means that you somehow react to an attack that has already happened. We want to intercept malevolent requests “on the fly” so that they never reach our backend server, Apache. This is the job of the [WAF](https://doc.crowdsec.net/docs/next/appsec/intro):
205+
206+
We follow [this quickstart guide](https://doc.crowdsec.net/docs/next/appsec/quickstart/nginxopenresty) :
207+
208+
1) We install the appsec collection. They contain the WAF rules
209+
210+
```bash
211+
sudo cscli collections install crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules
212+
```
213+
214+
2) We enable the AppSec/WAF acquisition, which allows CrowdSec to expose a service to which Nginx can post validation requests.
215+
216+
```bash
217+
cat > /etc/crowdsec/acquis.d/appsec.yaml << EOF
218+
appsec_config: crowdsecurity/appsec-default
219+
labels:
220+
type: appsec
221+
listen_addr: 127.0.0.1:7422
222+
source: appsec
223+
EOF
224+
```
225+
:::info
226+
227+
See [dedicated page about configuration file directives](/docs/next/log_processor/data_sources/appsec)
228+
229+
:::
230+
3) We restart CrowdSec
231+
232+
```bash
233+
sudo systemctl restart crowdsec
234+
```
235+
236+
4) We instruct our nginx bouncer/remediation component to rely on CrowdSec for the WAF feature:
237+
238+
```bash
239+
cat >> /etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf << EOF
240+
APPSEC_URL=http://127.0.0.1:7422
241+
EOF
242+
```
243+
244+
5) Finally, we restart nginx
245+
246+
```bash
247+
sudo systemctl restart nginx
248+
```
249+
250+
## Testing the WAF
251+
252+
So now, we can try to trigger the WAF:
253+
254+
```bash
255+
$ curl -I Y.Y.Y.Y/.env
256+
HTTP/1.1 403 Forbidden
257+
Server: nginx/1.24.0 (Ubuntu)
258+
Date: Fri, 23 May 2025 12:18:24 GMT
259+
Content-Type: text/html
260+
Connection: keep-alive
261+
cache-control: no-cache
262+
```
263+
264+
And indeed in CrowdSec’s logs:
265+
266+
```bash
267+
==> /var/log/crowdsec.log <==
268+
time="2025-05-23T12:18:24Z" level=info msg="AppSec block: crowdsecurity/vpatch-env-access from A.B.C.D (127.0.0.1)"
269+
270+
==> /var/log/nginx/error.log <==
271+
2025/05/23 12:18:24 [alert] 25451#25451: *443 [lua] crowdsec.lua:637: Allow(): [Crowdsec] denied 'A.B.C.D' with 'ban' (by appsec), client: A.B.C.D, server: _, request: "HEAD /.env HTTP/1.1", host: "X.X.X.X"
272+
273+
==> /var/log/nginx/access.log <==
274+
A.B.C.D - - [23/May/2025:12:18:24 +0000] "HEAD /.env HTTP/1.1" 403 0 "-" "curl/8.5.0"
275+
276+
277+
```
278+
279+
280+
In this case, the WAF is going to stop the request, meaning the malevolent request will never reach our *backend* web server, Apache.
281+
282+
## Wrapping this up
283+
284+
By setting up our reverse proxy with CrowdSec’s WAF and SE in front of our application, we are blocking a massive amount of requests before they even reach our application. These blocks come from various sources: The Security Engine detects and blocks behaviours, banning offending IP addresses for an extended period to avoid them consuming our resources and limiting the chances they manage to breach our application. The WAF stops malevolent requests “on the fly” before they can reach our backend web server, and the community blocklist completes both by preemptively blocking malicious IPs.
285+

0 commit comments

Comments
 (0)