WAF mit HAProxy und Coraza Rocky: Unterschied zwischen den Versionen

Aus Xinux Wiki
Zur Navigation springen Zur Suche springen
(Die Seite wurde neu angelegt: „= WAF-Demo: HAProxy mit Coraza auf Rocky Linux = == Ziel == Diese Übung zeigt eine minimale Web Application Firewall auf Basis von '''HAProxy''' und '''Coraz…“)
 
 
(27 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
= WAF-Demo: HAProxy mit Coraza auf Rocky Linux =
+
== WAF-Demo: HAProxy mit Coraza auf Rocky Linux ==
  
== Ziel ==
+
WAF mit HAProxy und Coraza (OWASP CRS) vor www.it2XX.int, TLS-Terminierung auf revproxy.it2XX.int.
Diese Übung zeigt eine minimale Web Application Firewall auf Basis von '''HAProxy''' und '''Coraza''' (OWASP CRS) vor einem einfachen Webserver. Gleichzeitig demonstriert der Aufbau, dass Rocky Linux bereits von Haus aus durch '''firewalld''' und '''SELinux''' gehärtet ist, bevor überhaupt eine WAF ins Spiel kommt. Bewusst nur eine einzige Komponente pro Aufgabe, ein Backend, eine Fehlerseite.
+
==Maschine==
 +
*Wir klonen das Rocky Template
  
== Topologie ==
+
==Vorgaben==
;Beschreibung
+
{| class="wikitable"
Ein einzelner Rocky-Linux-Host waf01.it2XX.int übernimmt alle Rollen.
+
! Einstellung !! Wert
 +
|-
 +
| '''Name''' || revproxy.it2XX.int
 +
|-
 +
| '''IP-Adresse''' || 10.88.2XX.41/24
 +
|-
 +
| '''Gateway''' || 10.88.2XX.1
 +
|-
 +
| '''Nameserver''' || 10.88.2XX.21
 +
|-
 +
| '''Search-Domain''' || it2XX.int
 +
|-
 +
| '''Benutzername''' || kit
 +
|-
 +
| '''Benutzerpasswort''' || kit
 +
|-
 +
| '''Administrator''' || root
 +
|-
 +
| '''Administratorpasswort''' || radler
 +
|-
 +
| '''Netz''' || DMZ
 +
|}
  
* HAProxy lauscht extern auf Port 80
+
==Einstellungen==
* coraza-spoa (WAF-Engine) lauscht nur auf 127.0.0.1:9000
+
;Selbterklärend
* httpd (Backend) lauscht nur auf 127.0.0.1:8888
+
*rocky-setup.sh -f revproxy.it2XX.int -a 10.88.2XX.41/24 -g 10.88.2XX.1 -n 10.88.2XX.21
 +
==Übliche Dinge==
 +
*.ssh/config anpassen
 +
*Pubkey übertragen
 +
==Zertifikate besorgen==
 +
*wget https://xinux.de/downloads/script/get-cert.sh
 +
*sudo bash get-cert.sh
  
Nur Port 80 (und SSH für die Administration) ist von außen erreichbar. Die interne Kommunikation zwischen HAProxy, Coraza und dem Backend bleibt vollständig auf Loopback.
+
== Topologie ==
 +
* revproxy.it2XX.int (10.88.2XX.41) HAProxy terminiert TLS auf Port 443, coraza-spoa läuft lokal auf 127.0.0.1:9000
 +
* www.it2XX.int (10.88.2XX.11) – bestehender Backend-Webserver, TLS auf Port 443
  
== Backend-Webserver vorbereiten ==
+
== HAProxy installieren ==
;httpd installieren
+
*dnf install -y haproxy git
*dnf install -y httpd
 
  
;Backend auf einen unüblichen Port legen
+
== Zertifikat auf dem Reverse Proxy vorbereiten ==
Wir nutzen bewusst Port 8888 statt 80, um später mit SELinux arbeiten zu müssen statt uns auf den Standard-Kontext zu verlassen.
+
;Beschreibung
 +
HAProxy erwartet Zertifikat und Key als eine gemeinsame PEM-Datei.
  
<pre>
+
*cat /etc/ssl/own.crt /etc/ssl/own.key > /etc/haproxy/own-combined.pem
# /etc/httpd/conf.d/backend.conf
+
*chmod 600 /etc/haproxy/own-combined.pem
Listen 127.0.0.1:8888
 
<VirtualHost 127.0.0.1:8888>
 
    DocumentRoot /var/www/backend
 
</VirtualHost>
 
</pre>
 
  
;Testseite anlegen
+
==Härtung SELinux-Kontext setzen==
*mkdir -p /var/www/backend
+
*semanage fcontext -a -t cert_t "/etc/haproxy/own-combined\.pem"
*echo "Backend erreichbar" > /var/www/backend/index.html
+
*restorecon -v /etc/haproxy/own-combined.pem
  
== Härtung mit firewalld ==
+
== Härtung mit firewalld (revproxy) ==
;Beschreibung
+
*firewall-cmd --permanent --add-service=https
Von außen darf ausschließlich HTTP (Port 80) und SSH erreichbar sein. Die internen Ports 8888 und 9000 hängen am Loopback und benötigen daher gar keine Freigabe.
 
 
 
*firewall-cmd --permanent --add-service=http
 
*firewall-cmd --permanent --add-service=ssh
 
 
*firewall-cmd --reload
 
*firewall-cmd --reload
  
 
;Kontrolle
 
;Kontrolle
*firewall-cmd --list-all
+
*firewall-cmd --list-services
 
 
== Härtung mit SELinux ==
 
;Beschreibung
 
httpd läuft standardmäßig unter dem Typ httpd_t. Port 8888 gehört aber nicht zur Standardliste von http_port_t, daher blockiert SELinux den Start zunächst, obwohl firewalld gar nicht betroffen ist – ein guter Anlass, den Unterschied zwischen Netzwerkfilter und Mandatory Access Control zu zeigen.
 
  
;Prüfen, welche Ports http_port_t bereits enthält
+
== Härtung mit SELinux (revproxy) ==
 +
;Kontrolle der Kontexte
 
*semanage port -l | grep http_port_t
 
*semanage port -l | grep http_port_t
 +
*ls -Z /etc/haproxy/own-combined.pem
 +
unconfined_u:object_r:'''cert_t''':s0 /etc/haproxy/own-combined.pem
  
;Port 8888 korrekt zu http_port_t hinzufügen
+
== Härtung - Haproxy ausgehende Verbindungen erlauben ==
*semanage port -a -t http_port_t -p tcp 8888
+
*setsebool -P haproxy_connect_any on
 
+
*systemctl restart haproxy
Damit kann sowohl httpd auf 8888 binden als auch HAProxy (Typ haproxy_t) sich dorthin verbinden, ohne die pauschale Boolean haproxy_connect_any aktivieren zu müssen. Bewusst kein chcon, sondern die persistente Policy-Änderung über semanage.
 
 
 
;httpd starten und aktivieren
 
*systemctl enable --now httpd
 
 
 
== HAProxy installieren ==
 
*dnf install -y haproxy
 
  
 
== Coraza (coraza-spoa) installieren ==
 
== Coraza (coraza-spoa) installieren ==
;Beschreibung
+
;coraza-spoa Binary installieren
coraza-spoa ist ein eigenständiger Go-Daemon, der als SPOE-Agent für HAProxy arbeitet. Es gibt kein Rocky-Paket, aber ein fertiges Release-Binary, sodass kein Compiler auf dem System nötig ist.
+
*dnf install -y golang git
 +
*git clone --depth=1 https://github.com/corazawaf/coraza-spoa /usr/local/src/coraza-spoa
 +
*cd /usr/local/src/coraza-spoa
 +
*go run mage.go build
 +
*cp build/coraza-spoa /usr/local/bin/coraza-spoa
 +
*chmod +x /usr/local/bin/coraza-spoa
  
*mkdir -p /etc/coraza-spoa
 
*curl -L -o /usr/local/bin/coraza-spoa https://github.com/corazawaf/coraza-spoa/releases/latest/download/coraza-spoa_linux_amd64
 
*chmod +x /usr/local/bin/coraza-spoa
 
  
 
;OWASP Core Rule Set und Basisregeln laden
 
;OWASP Core Rule Set und Basisregeln laden
 +
*mkdir -p /etc/coraza-spoa
 
*git clone --depth=1 https://github.com/coreruleset/coreruleset /etc/coraza-spoa/coreruleset
 
*git clone --depth=1 https://github.com/coreruleset/coreruleset /etc/coraza-spoa/coreruleset
 
*curl -L -o /etc/coraza-spoa/coraza.conf https://raw.githubusercontent.com/corazawaf/coraza/main/coraza.conf-recommended
 
*curl -L -o /etc/coraza-spoa/coraza.conf https://raw.githubusercontent.com/corazawaf/coraza/main/coraza.conf-recommended
Zeile 76: Zeile 92:
  
 
;Coraza-Konfiguration
 
;Coraza-Konfiguration
 +
*vim /etc/coraza-spoa/config.yaml
 
<pre>
 
<pre>
 
# /etc/coraza-spoa/config.yaml
 
# /etc/coraza-spoa/config.yaml
 
bind: 127.0.0.1:9000
 
bind: 127.0.0.1:9000
loglevel: info
+
log_level: info
 +
log_file: /dev/stdout
 +
log_format: console
  
 
applications:
 
applications:
   waf01:
+
   - name: revproxy
 +
    log_level: info
 +
    log_file: /dev/stdout
 +
    log_format: console
 
     directives: |
 
     directives: |
       Include /etc/coraza-spoa/coraza.conf
+
       Include @coraza.conf-recommended
      SecRuleEngine On
 
 
       Include /etc/coraza-spoa/crs-setup.conf
 
       Include /etc/coraza-spoa/crs-setup.conf
 
       Include /etc/coraza-spoa/coreruleset/rules/*.conf
 
       Include /etc/coraza-spoa/coreruleset/rules/*.conf
 +
      SecRuleEngine On
 +
    response_check: false
 +
    transaction_ttl_ms: 5000                           
 
</pre>
 
</pre>
  
 
;systemd-Unit
 
;systemd-Unit
 +
*vim /etc/systemd/system/coraza-spoa.service
 
<pre>
 
<pre>
 
# /etc/systemd/system/coraza-spoa.service
 
# /etc/systemd/system/coraza-spoa.service
Zeile 109: Zeile 134:
  
 
== HAProxy-Konfiguration ==
 
== HAProxy-Konfiguration ==
;SPOE-Filter einbinden und Backend definieren
+
;Konfiguration
 +
*vim /etc/haproxy/haproxy.cfg
 
<pre>
 
<pre>
 
# /etc/haproxy/haproxy.cfg
 
# /etc/haproxy/haproxy.cfg
Zeile 121: Zeile 147:
 
     timeout client 30s
 
     timeout client 30s
 
     timeout server 30s
 
     timeout server 30s
 +
    errorfile 403 /etc/haproxy/errors/403-waf.http
  
 
frontend fe_web
 
frontend fe_web
     bind *:80
+
     bind 10.88.2XX.41:443 ssl crt /etc/haproxy/own-combined.pem
 
     filter spoe engine coraza config /etc/haproxy/coraza.cfg
 
     filter spoe engine coraza config /etc/haproxy/coraza.cfg
 
     http-request deny deny_status 403 if { var(txn.coraza.action) -m str deny }
 
     http-request deny deny_status 403 if { var(txn.coraza.action) -m str deny }
Zeile 129: Zeile 156:
  
 
backend backend_web
 
backend backend_web
     server web01 127.0.0.1:8888 check
+
     server web01 10.88.2XX.11:443 check ssl verify none
  
 
backend coraza-spoa
 
backend coraza-spoa
Zeile 136: Zeile 163:
 
     server spoa1 127.0.0.1:9000 check
 
     server spoa1 127.0.0.1:9000 check
 
</pre>
 
</pre>
 +
;Check
 +
*haproxy -c -f  /etc/haproxy/haproxy.cfg
  
 
== SPOE-Konfiguration für Coraza ==
 
== SPOE-Konfiguration für Coraza ==
 +
*vim /etc/haproxy/coraza.cfg
 
<pre>
 
<pre>
# /etc/haproxy/coraza.cfg
 
 
[coraza]
 
[coraza]
 
spoe-agent coraza-agent
 
spoe-agent coraza-agent
Zeile 152: Zeile 181:
  
 
spoe-message coraza-req
 
spoe-message coraza-req
     args app=str(waf01) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
+
     args app=str(revproxy) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
 
     event on-frontend-http-request
 
     event on-frontend-http-request
 
</pre>
 
</pre>
  
 
== Eigene Fehlerseite ==
 
== Eigene Fehlerseite ==
;Beschreibung
+
*mkdir -p /etc/haproxy/errors
Genau eine Fehlerseite für alle von der WAF geblockten Requests, kein zusätzliches Fehlerseiten-Set.
+
*vim /etc/haproxy/errors/403-waf.http
 
 
 
<pre>
 
<pre>
# /etc/haproxy/errors/403-waf.http
 
 
HTTP/1.1 403 Forbidden
 
HTTP/1.1 403 Forbidden
 
Content-Type: text/html
 
Content-Type: text/html
Zeile 168: Zeile 195:
 
</pre>
 
</pre>
  
;Fehlerseite in haproxy.cfg einbinden
+
*systemctl restart haproxy
In den defaults-Block ergänzen:
 
 
 
<pre>
 
errorfile 403 /etc/haproxy/errors/403-waf.http
 
</pre>
 
 
 
 
*systemctl enable --now haproxy
 
*systemctl enable --now haproxy
  
 
== Test ==
 
== Test ==
;Normale Anfrage
+
*curl -ik https://revproxy.it2XX.int/
*curl -i http://waf01.it2XX.int/
+
*curl -ik "https://revproxy.it2XX.int/?id=1' OR '1'='1"
 
 
;Angriffsversuch, der von Coraza/OWASP CRS erkannt werden sollte
 
*curl -i "http://waf01.it2XX.int/?id=1' OR '1'='1"
 
 
 
Die erste Anfrage liefert die Backend-Testseite mit Status 200, die zweite die eigene 403-Fehlerseite, ohne dass die Anfrage überhaupt das Backend erreicht.
 
  
== Zusammenfassung ==
+
Erste Anfrage: Status 200 von www.it2XX.int. Zweite Anfrage: 403-Fehlerseite, www.it2XX.int wird nicht erreicht.
* firewalld lässt von außen ausschließlich HTTP und SSH zu
 
* SELinux verhindert, dass httpd oder HAProxy Ports außerhalb der freigegebenen Policy nutzen, unabhängig von firewalld
 
* HAProxy mit Coraza blockiert bekannte Angriffsmuster, bevor sie das Backend erreichen
 
* Genau eine Fehlerseite für alle WAF-Blocks
 

Aktuelle Version vom 6. August 2026, 08:22 Uhr

WAF-Demo: HAProxy mit Coraza auf Rocky Linux

WAF mit HAProxy und Coraza (OWASP CRS) vor www.it2XX.int, TLS-Terminierung auf revproxy.it2XX.int.

Maschine

  • Wir klonen das Rocky Template

Vorgaben

Einstellung Wert
Name revproxy.it2XX.int
IP-Adresse 10.88.2XX.41/24
Gateway 10.88.2XX.1
Nameserver 10.88.2XX.21
Search-Domain it2XX.int
Benutzername kit
Benutzerpasswort kit
Administrator root
Administratorpasswort radler
Netz DMZ

Einstellungen

Selbterklärend
  • rocky-setup.sh -f revproxy.it2XX.int -a 10.88.2XX.41/24 -g 10.88.2XX.1 -n 10.88.2XX.21

Übliche Dinge

  • .ssh/config anpassen
  • Pubkey übertragen

Zertifikate besorgen

Topologie

  • revproxy.it2XX.int (10.88.2XX.41) – HAProxy terminiert TLS auf Port 443, coraza-spoa läuft lokal auf 127.0.0.1:9000
  • www.it2XX.int (10.88.2XX.11) – bestehender Backend-Webserver, TLS auf Port 443

HAProxy installieren

  • dnf install -y haproxy git

Zertifikat auf dem Reverse Proxy vorbereiten

Beschreibung

HAProxy erwartet Zertifikat und Key als eine gemeinsame PEM-Datei.

  • cat /etc/ssl/own.crt /etc/ssl/own.key > /etc/haproxy/own-combined.pem
  • chmod 600 /etc/haproxy/own-combined.pem

Härtung SELinux-Kontext setzen

  • semanage fcontext -a -t cert_t "/etc/haproxy/own-combined\.pem"
  • restorecon -v /etc/haproxy/own-combined.pem

Härtung mit firewalld (revproxy)

  • firewall-cmd --permanent --add-service=https
  • firewall-cmd --reload
Kontrolle
  • firewall-cmd --list-services

Härtung mit SELinux (revproxy)

Kontrolle der Kontexte
  • semanage port -l | grep http_port_t
  • ls -Z /etc/haproxy/own-combined.pem
unconfined_u:object_r:cert_t:s0 /etc/haproxy/own-combined.pem

Härtung - Haproxy ausgehende Verbindungen erlauben

  • setsebool -P haproxy_connect_any on
  • systemctl restart haproxy

Coraza (coraza-spoa) installieren

coraza-spoa Binary installieren
  • dnf install -y golang git
  • git clone --depth=1 https://github.com/corazawaf/coraza-spoa /usr/local/src/coraza-spoa
  • cd /usr/local/src/coraza-spoa
  • go run mage.go build
  • cp build/coraza-spoa /usr/local/bin/coraza-spoa
  • chmod +x /usr/local/bin/coraza-spoa


OWASP Core Rule Set und Basisregeln laden
Coraza-Konfiguration
  • vim /etc/coraza-spoa/config.yaml
# /etc/coraza-spoa/config.yaml
bind: 127.0.0.1:9000
log_level: info
log_file: /dev/stdout
log_format: console

applications:
  - name: revproxy
    log_level: info
    log_file: /dev/stdout
    log_format: console
    directives: |
      Include @coraza.conf-recommended
      Include /etc/coraza-spoa/crs-setup.conf
      Include /etc/coraza-spoa/coreruleset/rules/*.conf
      SecRuleEngine On
    response_check: false
    transaction_ttl_ms: 5000                             
systemd-Unit
  • vim /etc/systemd/system/coraza-spoa.service
# /etc/systemd/system/coraza-spoa.service
[Unit]
Description=Coraza SPOA für HAProxy
After=network.target

[Service]
ExecStart=/usr/local/bin/coraza-spoa -config /etc/coraza-spoa/config.yaml
Restart=on-failure

[Install]
WantedBy=multi-user.target
  • systemctl daemon-reload
  • systemctl enable --now coraza-spoa

HAProxy-Konfiguration

Konfiguration
  • vim /etc/haproxy/haproxy.cfg
# /etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    maxconn 2000

defaults
    mode http
    timeout connect 5s
    timeout client 30s
    timeout server 30s
    errorfile 403 /etc/haproxy/errors/403-waf.http

frontend fe_web
    bind 10.88.2XX.41:443 ssl crt /etc/haproxy/own-combined.pem
    filter spoe engine coraza config /etc/haproxy/coraza.cfg
    http-request deny deny_status 403 if { var(txn.coraza.action) -m str deny }
    default_backend backend_web

backend backend_web
    server web01 10.88.2XX.11:443 check ssl verify none

backend coraza-spoa
    mode tcp
    option spop-check
    server spoa1 127.0.0.1:9000 check
Check
  • haproxy -c -f /etc/haproxy/haproxy.cfg

SPOE-Konfiguration für Coraza

  • vim /etc/haproxy/coraza.cfg
[coraza]
spoe-agent coraza-agent
    messages         coraza-req
    option var-prefix coraza
    option set-on-error error
    timeout hello     2s
    timeout idle      2m
    timeout processing 500ms
    use-backend       coraza-spoa
    log               global

spoe-message coraza-req
    args app=str(revproxy) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
    event on-frontend-http-request

Eigene Fehlerseite

  • mkdir -p /etc/haproxy/errors
  • vim /etc/haproxy/errors/403-waf.http
HTTP/1.1 403 Forbidden
Content-Type: text/html

<html><body><h1>Anfrage blockiert</h1><p>Diese Anfrage wurde von der Web Application Firewall abgelehnt.</p></body></html>
  • systemctl restart haproxy
  • systemctl enable --now haproxy

Test

Erste Anfrage: Status 200 von www.it2XX.int. Zweite Anfrage: 403-Fehlerseite, www.it2XX.int wird nicht erreicht.