Note: The below information is deprecated as HAProxy Enterprise now offers a fully functional native WAF module which supports whitelist-based rulesets, blacklist-based rulesets, and ModSecurity rulesets!
Greeting to Thomas Heil, from our German partner Olanis, for his help in Apache and modsecurity configuration assistance.
What is a Web Application Firewall (WAF)?
Years ago, it was common to protect networks using a firewall… Well known devices which filter traffic at layer 3 and 4…
Now, the failures have moved from the network stack to the application layer, making the old firewall useless and obsolete (for protection purpose I mean). We used then to deploy IDS or IPS, which tried to match attacks at a packet level. These products are usually very hard to tune.
Then the Web Application Firewall arrived: it’s a firewall aware of the layer 7 protocol in order to be more efficient when deciding to block requests (or responses).
This is because the attacks became more complicated, like the SQL Injection, Cross Site scripting, etc…
One of the most famous opensource WAF is mod_security, which works as a module on Apache webserver and IIS for a long time and has been announced recently for nginx too.
A very good alternative is naxsi, a module for nginx, still young but very promising.
On today’s article, I’ll focus on modsecurity for Apache. In a next article, I’ll build the same platform with naxsi and Nginx.
Scalable WAF platform
The main problem with WAF, is that they require a lot of resources to analyse each requests headers and body. (it can even be configured to analyze the response). If you want to be able to protect all your upcoming traffic, then you must think scalability.
In the present article I’m going to explain how to build a reliable and scalable platform where WAF capacity won’t be an issue. I could add “and where WAF maintenance could be done during business hours).
Here are the basic purpose to achieve:
- Web Application Firewall: achieved by Apache and modsecurity
- High-availability: application server and WAF monitoring, achieved by HAProxy
- Scalability: ability to adapt capacity to the upcoming volume of traffic, achieved by HAProxy
It would be good if the platform would achieve the following advanced features:
- DDOS protection: blind and brutal attacks protection, slowloris protection, achieved by HAProxy
- Content-Switching: ability to route only dynamic requests to the WAF, achieved by HAProxy
- Reliability: ability to detect capacity overusage, this is achieved by HAProxy
- Performance: deliver response as fast as possible, achieved by the whole platform
Web platform with WAF Diagram
The diagram below shows the platform with HAProxy frontends (prefixed by ft_) and backends (prefixed by bk_). Each farm is composed by 2 servers.
As you can see, at first, it seems all the traffic goes to the WAFs, then comes back in HAProxy before being routed to the web servers. This would be the basic configuration, meeting the following basic requirements: Web Application Firewall, High-Availability, Scalability.
Platform installation
As load-balancer, I’m going to use our well known ALOHA 🙂
The web servers are standard debian with apache and PHP, the application used on top of it is dokuwiki. I have no procedure for this one, this is very straight forward!
The WAF run on CentOS 6.3 x86_64, using modsecurity 2.5.8. The installation procedure is outside of the scope of this article, so I documented it on my personal wiki.
All of these servers are virtualized on my laptop using KVM, so NO, I won’t run performance benchmark, it would be ridiculous!
Configuration
WAF configuration
Basic configuration here, no tuning at all. The purpose is not to explain how to configure a WAF, sorry.
Apache Configuration
Modification to the file /etc/httpd/conf/httpd.conf:
Listen 192.168.10.15:81 [...] LoadModule security2_module modules/mod_security2.so LoadModule unique_id_module modules/mod_unique_id.so [...] NameVirtualHost 192.168.10.15:81 [...] <IfModule mod_security2.c> SecPcreMatchLimit 1000000 SecPcreMatchLimitRecursion 1000000 SecDataDir logs/ </IfModule> <VirtualHost 192.168.10.15:81> ServerName * AddDefaultCharset UTF-8 <IfModule mod_security2.c> Include modsecurity.d/modsecurity_crs_10_setup.conf Include modsecurity.d/aloha.conf Include modsecurity.d/rules/*.conf SecRuleEngine On SecRequestBodyAccess On SecResponseBodyAccess On </IfModule> ProxyPreserveHost On ProxyRequests off ProxyVia Off ProxyPass / http://192.168.10.2:81/ ProxyPassReverse / http://192.168.10.2:81/ </VirtualHost>
Basically, we just turned Apache into a reverse-proxy, accepting traffic for any server name, applying modsecurity rules before routing traffic back to HAProxy frontend dedicated to web servers.
Client IP
HAProxy works has a reverse proxy and so will use its own IP address to get connected on the WAF server. So you have to install mod_rpaf to get the client IP in the WAF for both tracking and logging.
To install mod_rpaf, follow these instructions: apache mod_rpaf installation.
Concerning its configuration, we’ll do it as below, edit the file /etc/httpd/conf.d/mod_rpaf.conf:
LoadModule rpaf_module modules/mod_rpaf-2.0.so <IfModule rpaf_module> RPAFenable On RPAFproxy_ips 192.168.10.1 192.168.10.3 RPAFheader X-Client-IP </IfModule>
modsecurity custom rules
In the Apache configuration there is a directive which tells modsecurity to load a file called aloha.conf. The purpose of this file is to tell to modsecurity to deny the health check requests from HAProxy and to prevent logging them.
HAProxy will consider the WAF as operational only if it gets a 403 response to this request. (see HAProxy configuration below).
Content of the file /etc/httpd/modsecurity.d/aloha.conf:
SecRule REQUEST_FILENAME "/waf_health_check" "nolog,deny"
Load-Balancer (HAProxy) configuration for basic usage
The configuration below is the first shoot we do when deploying such platform, it is basic, simple and straight forward:
######## Default values for all entries till next defaults section defaults option http-server-close option dontlognull option redispatch option contstats retries 3 timeout connect 5s timeout http-keep-alive 1s # Slowloris protection timeout http-request 15s timeout queue 30s timeout tarpit 1m # tarpit hold tim backlog 10000 # public frontend where users get connected to frontend ft_waf bind 192.168.10.2:80 name http mode http log global option httplog timeout client 25s maxconn 1000 default_backend bk_waf # WAF farm where users' traffic is routed first backend bk_waf balance roundrobin mode http log global option httplog option forwardfor header X-Client-IP option httpchk HEAD /waf_health_check HTTP/1.0 # Specific WAF checking: a DENY means everything is OK http-check expect status 403 timeout server 25s default-server inter 3s rise 2 fall 3 server waf1 192.168.10.15:81 maxconn 100 weight 10 check server waf2 192.168.10.16:81 maxconn 100 weight 10 check # Traffic secured by the WAF arrives here frontend ft_web bind 192.168.10.2:81 name http mode http log global option httplog timeout client 25s maxconn 1000 # route health check requests to a specific backend to avoid graph pollution in ALOHA GUI use_backend bk_waf_health_check if { path /waf_health_check } default_backend bk_web # application server farm backend bk_web balance roundrobin mode http log global option httplog option forwardfor cookie SERVERID insert indirect nocache default-server inter 3s rise 2 fall 3 option httpchk HEAD / timeout server 25s server server1 192.168.10.11:80 maxconn 100 weight 10 cookie server1 check server server2 192.168.10.12:80 maxconn 100 weight 10 cookie server2 check # backend dedicated to WAF checking (to avoid graph pollution) backend bk_waf_health_check balance roundrobin mode http log global option httplog option forwardfor default-server inter 3s rise 2 fall 3 timeout server 25s server server1 192.168.10.11:80 maxconn 100 weight 10 check server server2 192.168.10.12:80 maxconn 100 weight 10 check
Advanced Load-Balancing (HAProxy) configuration
We’re going now to improve a bit the platform. The picture below shows which type of protection is achieved by the load-balancer and the WAF:
The configuration below adds a few more features:
- DDOS protection on the frontend
- abuser or attacker detection in bk_waf and blocking on the public interface (ft_waf)
- Bypassing WAF when overusage or unavailable
Which will allow to meet the advanced requirements: DDOS protection, Content-Switching, Reliability, Performance.
######## Default values for all entries till next defaults section defaults option http-server-close option dontlognull option redispatch option contstats retries 3 timeout connect 5s timeout http-keep-alive 1s # Slowloris protection timeout http-request 15s timeout queue 30s timeout tarpit 1m # tarpit hold tim backlog 10000 # public frontend where users get connected to frontend ft_waf bind 192.168.10.2:80 name http mode http log global option httplog timeout client 25s maxconn 10000 # DDOS protection # Use General Purpose Couter (gpc) 0 in SC1 as a global abuse counter # Monitors the number of request sent by an IP over a period of 10 seconds stick-table type ip size 1m expire 1m store gpc0,http_req_rate(10s),http_err_rate(10s) tcp-request connection track-sc1 src tcp-request connection reject if { sc1_get_gpc0 gt 0 } # Abuser means more than 100reqs/10s acl abuse sc1_http_req_rate(ft_web) ge 100 acl flag_abuser sc1_inc_gpc0(ft_web) tcp-request content reject if abuse flag_abuser acl static path_beg /static/ /dokuwiki/images/ acl no_waf nbsrv(bk_waf) eq 0 acl waf_max_capacity queue(bk_waf) ge 1 # bypass WAF farm if no WAF available use_backend bk_web if no_waf # bypass WAF farm if it reaches its capacity use_backend bk_web if static waf_max_capacity default_backend bk_waf # WAF farm where users' traffic is routed first backend bk_waf balance roundrobin mode http log global option httplog option forwardfor header X-Client-IP option httpchk HEAD /waf_health_check HTTP/1.0 # If the source IP generated 10 or more http request over the defined period, # flag the IP as abuser on the frontend acl abuse sc1_http_err_rate(ft_waf) ge 10 acl flag_abuser sc1_inc_gpc0(ft_waf) tcp-request content reject if abuse flag_abuser # Specific WAF checking: a DENY means everything is OK http-check expect status 403 timeout server 25s default-server inter 3s rise 2 fall 3 server waf1 192.168.10.15:81 maxconn 100 weight 10 check server waf2 192.168.10.16:81 maxconn 100 weight 10 check # Traffic secured by the WAF arrives here frontend ft_web bind 192.168.10.2:81 name http mode http log global option httplog timeout client 25s maxconn 1000 # route health check requests to a specific backend to avoid graph pollution in ALOHA GUI use_backend bk_waf_health_check if { path /waf_health_check } default_backend bk_web # application server farm backend bk_web balance roundrobin mode http log global option httplog option forwardfor cookie SERVERID insert indirect nocache default-server inter 3s rise 2 fall 3 option httpchk HEAD / # get connected on the application server using the user ip # provided in the X-Client-IP header setup by ft_waf frontend source 0.0.0.0 usesrc hdr_ip(X-Client-IP) timeout server 25s server server1 192.168.10.11:80 maxconn 100 weight 10 cookie server1 check server server2 192.168.10.12:80 maxconn 100 weight 10 cookie server2 check # backend dedicated to WAF checking (to avoid graph pollution) backend bk_waf_health_check balance roundrobin mode http log global option httplog option forwardfor default-server inter 3s rise 2 fall 3 timeout server 25s server server1 192.168.10.11:80 maxconn 100 weight 10 check server server2 192.168.10.12:80 maxconn 100 weight 10 check
Detecting attacks
On the load-balancer
The ft_waf frontend stick table tracks two information: http_req_rate and http_err_rate which are respectively the http request rate and the http error rate generated by a single IP address.
HAProxy would automatically block an IP which has generated more than 100 requests over a period of 10s or 10 errors (WAF detection 403 responses included) in 10s. The user is blocked for 1 minute as long as he keeps on abusing.
Of course, you can setup above values to whatever you need: it is fully flexible.
To know the status of IPs in your load-balancer, just run the command below:
echo show table ft_waf | socat /var/run/haproxy.stat - # table: ft_waf, type: ip, size:1048576, used:1 0xc33304: key=192.168.10.254 use=0 exp=4555 gpc0=0 http_req_rate(10000)=1 http_err_rate(10000)=1
Note: The ALOHA Load-balancer does not provide watch, but you can monitor the content of the table in live with the command below:
while true ; do echo show table ft_waf | socat /var/run/haproxy.stat - ; sleep 2 ; clear ; done
On the Waf
I have not setup anything particular on WAF logging, so every errors appears in /var/log/httpd/error_log. IE:
[Fri Oct 12 10:48:21 2012] [error] [client 192.168.10.254] ModSecurity: Access denied with code 403 (phase 2). Pattern match "(?:(?:[\\;\\|\\`]\\W*?\\bcc|\\b(wget|curl))\\b|\\/cc(?:[\\'\"\\|\\;\\`\\-\\s]|$))" at REQUEST_FILENAME. [file "/etc/httpd/modsecurity.d/rules/modsecurity_crs_40_generic_attacks.conf"] [line "25"] [id "950907"] [rev "2.2.5"] [msg "System Command Injection"] [data "/cc-"] [severity "CRITICAL"] [tag "WEB_ATTACK/COMMAND_INJECTION"] [tag "WASCTC/WASC-31"] [tag "OWASP_TOP_10/A1"] [tag "PCI/6.5.2"] [hostname "mywiki"] [uri "/dokuwiki/lib/images/license/button/cc-by-sa.png"] [unique_id "UHfZVcCoCg8AAApVAzsAAAAA"]
Seems to be a false positive 🙂
Conclusion
Today, we saw it’s easy to build a scalable and well performing WAF platform in front of our web application.
The WAF is able to communicate to HAProxy which IPs to automatically blacklist (throuth error rate monitoring), which is convenient since the attacker won’t bother the WAF for a certain amount of time 😉
The platform allows to detect WAF farm availability and to bypass it in case of total failure, we even saw it is possible to bypass the WAF for static content if the farm is running out of capacity. Purpose is to deliver a good end-user experience without dropping too much the security.
Note that it is possible to route all the static content to the web servers (or a static farm) directly, whatever the status of the WAF farm.
This make me say that the platform is fully scallable and flexible.
Also, bear in mind to monitor your WAF logs, as shown in the example above, there was a false positive preventing an image to be loaded from dokuwiki.
Related links
- HTTP request flood mitigation
- Use a load-balancer as a first row of defense against DDOS
- On-Demand Webinar: DDoS Attack and Bot Protection with HAProxy Enterprise
Thanks for your great post.
based on this post, i think five hosts are used as follows:
• Load Balancer and WAF (1), IP address: 192.168.10.15
• Load Balancer and WAF (2), IP address: 192.168.10.16
• Backend Server 1, IP address: 192.168.10.11
• Backend Server 2, IP address: 192.168.10.12
• IP address that floats between WAF1 and WAF2: 192.168.10.2
i can,t understand what is the role of client-ips: 192.168.10.1 and 192.168.10.3
i know that RPAF module is used to get client IPs for logging but i cant understand how it works in your examples. i think that 192.168.10.1 and 192.168.10.3 are two specific client IPs but i’m not sure.
Regards and thanks in advance
Fafa
Hi Fafa,
192.168.10.1 and 192.168.10.3 are the load-balancer IP addresses. 192.168.10.2 is the VIP used to load-balance against the WAFs.
cheers
Thanks for your reply Baptiste
Hi Baptiste
Could you give me an example of scaling scenario? Let’s say that the apache running the WAF is also doing the SSL termination, and some other security duties besides those of mod_security.
So I’ll want the apache to be a key player, but also make good use of haproxy. And let’s say that it is your scenario multiplied by 3. What would you suggest?
Thanks for reading!
Alex
Hi,
If you want to be scalable on the WAF part, you must decipher the traffic before HAProxy (or inside it).
So the suggested design will hardly work.
You could manage a SSL offloading farm (the apache) then forward clear traffic inro haproxy to load-balance the WAF (apache again).
Baptiste
I saw that you forwarded request from Client to website application, what about response from web application to Client, i didn’t see you forward over WAF farm?
Thanks Baptiste Assmann
Why would you do that?
Hi Baptiste,
thank you for your post.
Is this scenario still the best choice for a WAF implementation on HAProxy?
I would love a tight waf integration, kind of mod_security fo HAProxy.
Kind regards
Well, for now, this is the best choice. There has been a tentative to integrate a WAF in HAProxy recently, but nobody from the community seemed to be interested: http://discourse.haproxy.org/t/ironbee-in-haproxy/92
Thanks Baptiste, this is a great tutorial. Is it possible to use this setup with SSL termination?