Scalability WAF Protection using HAproxy and OWASP ModSecurity with Nginx on AlmaLinux / RockyLinux

Skalabilitas Proteksi WAF menggunakan HAproxy dan ModSecurity OWASP dengan Nginx pada AlmaLinux / RockyLinux

  • Pastikan sudah menginstall HAproxy dan OWASP ModSecurity Nginx
  • Artikel ini berhubungan dengan ke dua link artikel diatas
  • HAproxy: 192.168.100.10
  • Web1: 192.168.100.247
  • Web2: 192.168.100.182
  • Waf1: 192.168.100.122
  • Waf2: 192.168.100.254
  • Domain: lb.sideka.my.id

Konfigurasi Nginx + OWASP ModSecurity

  • Buat file konfigurasi nginx reverse proxy pada server waf1 dan waf2
  • Nama file: /usr/local/nginx/conf.d/default.conf
cat <<EOF | tee /usr/local/nginx/conf.d/default.conf
server {
    listen      80;
    listen      [::]:80;
    server_name localhost;

    modsecurity             on;
    modsecurity_rules_file  /usr/local/nginx/conf/modsecurity.conf;

    location ^~ / {
        index index.html index.htm index.php;
        try_files $uri $uri/ /index.php?$args;

        proxy_pass http://192.168.100.10:81/;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header REMOTE-HOST $remote_addr;

        set $static_fileYsIv9c7Y 0;
        if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
        {
             set $static_fileYsIv9c7Y 1;
             expires 12h;
        }
        if ( $static_fileYsIv9c7Y = 0 )
        {
             add_header Cache-Control no-cache;
        }
    }
}
EOF
  • Verifikasi file konfigurasi nginx, pastikan syntax is ok
  • Restart service nginx pada server waf1 dan waf2
nginx -t
---<output>---
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

systemctl restart nginx

Konfigurasi HAproxy

  • Edit file /etc/haproxy/haproxy.cfg
global
   log /dev/log local0
   log /dev/log local1 notice
   chroot /var/lib/haproxy
   stats timeout 30s
   user haproxy
   group haproxy
   daemon
   tune.ssl.default-dh-param 2048
   spread-checks 2
   tune.bufsize  16384
   ssl-default-bind-options prefer-client-ciphers ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.3 
   ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256
   ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

defaults
   log global
   mode http
   option httplog
   option dontlognull
   timeout connect 30s
   timeout client 30s
   timeout server 30s
   maxconn 10000
   fullconn 10000
   retries 10
   option  http-server-close
   timeout http-request 15s
   timeout queue 30s
   timeout tarpit 1m

cache haproxy-cache
   total-max-size 256
   max-age 300
   max-object-size 1000000
   process-vary off

listen stats
   bind *:8765 ssl crt /etc/haproxy/certs/lb.sideka.my.id.pem
   stats enable
   stats uri /
   stats realm Haproxy\ Statistics
   stats auth admin:admin
   stats refresh 3s

##### WAF #####
frontend http_front_waf
   bind *:80
   mode http
   option http-keep-alive
   timeout http-keep-alive 300s
   option forwardfor
   option httplog

   tcp-request connection track-sc0 src
   stick-table type ip size 1m expire 60m store conn_cur,http_err_cnt,http_req_rate(10s),http_err_rate(10s)
   http-request track-sc0 src
   http-request deny deny_status 429 if { sc_http_req_rate(0) gt 5000 }

   http-request cache-use haproxy-cache
   http-response cache-store haproxy-cache

   redirect scheme https code 301 if !{ ssl_fc }
   acl no_waf nbsrv(http_back_waf) eq 0
   use_backend http_back_web if no_waf

   http-response set-header Access-Control-Max-Age 3628800
   http-response set-header X-XSS-Protection 1;mode=block
   http-response set-header X-Frame-Options SAMEORIGIN
   http-response set-header X-Content-Type-Options nosniff
   default_backend http_back_waf

frontend https_front_waf
   mode http
   option http-keep-alive
   timeout http-keep-alive 300s
   option forwardfor
   option httplog

   tcp-request connection track-sc0 src
   stick-table type ip size 1m expire 60m store conn_cur,http_err_cnt,http_req_rate(10s),http_err_rate(10s)
   http-request track-sc0 src
   http-request deny deny_status 429 if { sc_http_req_rate(0) gt 5000 }

   http-request cache-use haproxy-cache
   http-response cache-store haproxy-cache

   acl no_waf nbsrv(http_back_waf) eq 0
   use_backend http_back_web if no_waf

   bind *:443 ssl crt /etc/haproxy/certs/lb.sideka.my.id.pem ssl prefer-client-ciphers ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.3 ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256 ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 alpn h2,http/1.1
   http-response set-header Access-Control-Allow-Origin "*"
   http-response set-header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization, JSNLog-RequestId, activityId, applicationId, applicationUserId, channelId, senderId, sessionId"
   http-response set-header Access-Control-Max-Age 3628800
   http-response set-header Access-Control-Allow-Methods "GET, DELETE, OPTIONS, POST, PUT"
   http-response set-header X-XSS-Protection 1;mode=block
   http-response set-header X-Frame-Options SAMEORIGIN
   http-response set-header X-Content-Type-Options nosniff
   http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains;preload"
   http-response set-header Referrer-Policy strict-origin-when-cross-origin
   http-response set-header Permissions-Policy geolocation=()
   http-response set-header Content-Security-Policy "default-src 'self'; object-scr 'none'; form-action 'self'; frame-ancestors 'self';"
   default_backend http_back_waf

backend http_back_waf
   mode http
   balance roundrobin
   option forwardfor header X-Client-IP
   option forwardfor
   option log-health-checks
   option httpchk
   http-check send meth OPTIONS uri / ver HTTP/1.0
   timeout connect 60s
   timeout server 300s
   retries 5
   http-reuse safe
   stick-table type ip size 1m expire 60m store conn_cur,http_err_cnt,http_err_rate(10s)

   http-request cache-use haproxy-cache
   http-response cache-store haproxy-cache

   cookie SERVERID insert indirect nocache
   server waf1 192.168.100.122:80 check maxconn 5000 weight 1 check inter 3s downinter 6s port 80 check cookie waf1
   server waf2 192.168.100.254:80 check maxconn 5000 weight 1 check inter 3s downinter 6s port 80 check cookie waf2


##### REAL SERVER #####
frontend http_front_web
   bind *:81
   mode http
   option http-keep-alive
   timeout http-keep-alive 300s
   option forwardfor
   option httplog

   tcp-request connection track-sc0 src
   stick-table type ip size 1m expire 60m store conn_cur,http_err_cnt,http_err_rate(10s)
   http-request track-sc0 src

   http-request cache-use haproxy-cache
   http-response cache-store haproxy-cache

   http-response set-header Access-Control-Max-Age 3628800
   http-response set-header X-XSS-Protection 1;mode=block
   http-response set-header X-Frame-Options SAMEORIGIN
   http-response set-header X-Content-Type-Options nosniff
   default_backend http_back_web

backend http_back_web
   mode http
   balance roundrobin
   option forwardfor
   option log-health-checks
   option httpchk
   http-check send meth OPTIONS uri / ver HTTP/1.0

   stick-table type ip size 1m expire 60m store conn_cnt,conn_cur,conn_rate(10s),sess_cnt,sess_rate(10s),http_req_cnt,http_req_rate(10s),http_err_cnt,http_err_rate(10s) 
   stick on src

   http-request cache-use haproxy-cache
   http-response cache-store haproxy-cache

   timeout connect 60s
   timeout server 300s
   retries 5
   http-reuse safe
   server server_web1 192.168.100.247:80 check maxconn 5000 weight 1 check inter 3s downinter 6s port 80
   server server_web2 192.168.100.182:80 check maxconn 5000 weight 1 check inter 3s downinter 6s port 80
  • Verifikasi file konfigurasi haproxy, pastikan tidak ada error
  • Restart service haproxy
haproxy -f /etc/haproxy/haproxy.cfg -c
---<output>---
Configuration file is valid

systemctl restart haproxy

Pengujian

  • Akses server web server dengan domain: lb.sideka.my.id
  • Request valid akan langsung diarahkan ke web server
  • Pengujian modsecurity
  • Request tidak valid akan langsung di blokir oleh modsecurity
  • Pengujian banyak request
  • Jika request yang masuk dalam 10 detik lebih dari 5000 request, maka akan muncul error 429
  • Jika ke dua server waf mengalami down, maka http request akan langsung dialihkan ke backend web server (bypass)
  • Menambahkan server waf baru cukup mudah, clone server waf existing/copy konfigurasi dari server existing lalu tambahkan server waf pada backend waf di haproxy
  • Cek header
curl -i https://lb.sideka.my.id
---<output>---
HTTP/2 200 
server: nginx/1.23.4
date: Tue, 08 Aug 2023 18:46:46 GMT
content-type: text/html; charset=UTF-8
x-powered-by: PHP/8.2.9
cache-control: no-cache
access-control-allow-origin: *
access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, JSNLog-RequestId, activityId, applicationId, applicationUserId, channelId, senderId, sessionId
access-control-max-age: 3628800
access-control-allow-methods: GET, DELETE, OPTIONS, POST, PUT
x-xss-protection: 1;mode=block
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
strict-transport-security: max-age=15552000; includeSubDomains;preload
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=()
content-security-policy: default-src 'self'; object-scr 'none'; form-action 'self'; frame-ancestors 'self';
set-cookie: SERVERID=waf1; path=/

<!DOCTYPE html>
<html>
<head>
  <title>sys-ops.id</title>
  <link rel="stylesheet" href="css/styles.css">
</head>
<body>
    <header>
    <h1>Welcome To My Page!</h1>
    <h3>image v3 - Aug 2023</h3>
  </header>
  <main>
    <img src="img/cat2.png" alt="cat" />
    <p>Server hostname: web1</p>
    <p>Server IP address: 192.168.100.247</p>
    <p>PHP version: 8.2.9</p>
  </main>
  <footer>
    <p>© 2023 sys-ops.id</p>
  </footer>
</body>
</html>
  • Monitoring server backend lewat haproxy stat

herdiana3389

A system administrator with skills in system administration, virtualization, linux, windows, networking, cloud computing, container, etc.