Microsoft quietly patched a fairly nasty little bug (MS15-034) in IIS last month: A simple HTTP request with an invalid range header field value to either kill IIS, reveal data or remotely execute code! We haven’t seen one of these in a while and obviously you are safe if you have automatic security patching turned on. However, with our renewed focus on web application security, I thought this would be a good example to show how easy virtual patching is with the industry standard tools used in the Loadbalancer.org appliance.
I’m cheating by using our pre-release Loadbalancer.org appliance v8 software as it has a built in WAF aka. ModSecurity. By default the WAF is obviously handling the blocking for the OWASP 10 threats and adding customized rules is simply a matter of editing the custom rules config file:
# Do not allow an invalid range from ping of death attack MS15034 SecRule REQUEST_HEADERS:Range "@rx (?i)^(bytes\s*=)(.*?)(([0-9]){10,})(.*)" \ "id:'100007',phase:1,t:none,block,setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},msg:'Invalid header range MS15034 attack'" |
Then restart the WAF in the interface (service httpd reload which is transaction safe).
You can test the new rule by triggering the rule with the following curl command:
curl -v http://192.168.64.28/ -H "Host: www.myhost.com" -H "Range: bytes = 10-18446744073709551615" -k |
You should get an HTML response of 403 Forbidden, and the mod security error log as follows:
[Mon May 18 14:57:27 2015] [error] [client 82.70.17.214] ModSecurity: Warning. Pattern match "(?i)^(bytes\\\\s*=)(.*?)(([0-9]){10,})(.*)" at REQUEST_HEADERS:Range. [file "/etc/httpd/modsecurity.d/lb_rules_waf1.conf"] [line "41"] [id "100007"] [msg "Invalid range MS15034 attack"] [hostname "www.myhost.com"] [uri "/"] [unique_id "VVn9138AAAEAAEC3LVAAAAAC"] [Mon May 18 14:57:27 2015] [error] [client 82.70.17.214] ModSecurity: Access denied with code 403 (phase 2). Pattern match "(.*)" at TX:0. [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_49_inbound_blocking.conf"] [line "26"] [id "981176"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5, SQLi=, XSS=): Last Matched Message: "] [data "Last Matched Data: X-Forwarded-For"] [hostname "www.myhost.com"] [uri "/"] [unique_id "VVn9138AAAEAAEC3LVAAAAAC"] [Mon May 18 14:57:27 2015] [error] [client 82.70.17.214] ModSecurity: Warning. Operator GE matched 5 at TX:inbound_anomaly_score. [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_60_correlation.conf"] [line "37"] [id "981204"] [msg "Inbound Anomaly Score Exceeded (Total Inbound Score: 5, SQLi=, XSS=): "] [hostname "www.myhost.com"] [uri "/"] [unique_id "VVn9138AAAEAAEC3LVAAAAAC"] |
Notice that we are using anomaly score based blocking, which is much more flexible and effective than simply blocking on the first error. In the Loadbalancer.org appliance we have implemented ModSecurity with mod_proxy & mod_rapf.
We use an HAProxy front end for incoming traffic and an HAProxy backend for the web application cluster. This allows the flexibility to implement traffic handling rules at any point in the chain.
- If you need to turn off the WAF simply hit Halt on the real server WAF_1 and the fallback will automatically pass through unsecured traffic to the default backend Waf1_Backend
- If you want a hard fail then remove the fallback server from WAF1_Front_End to kill all traffic, or point it to a holding page.
- If you need to add more WAF’s to the cluster for scalabilty, you simply add them to the WAF1 front end.
The flexibility of the Loadbalancer.org solution allows you to handle security in the ideal location for your network. For this instance, when doing a simple block based on the value in the range header field, you are almost certainly better off doing it in the HAProxy front end. This is very simple to do in all the Loadbalancer.org products. Just change the configuration to a manual layer 7 config and add the following two lines to your HAProxy config:
listen Waf1_Backend bind 192.168.64.28:81 transparent mode http # The following two lines do the security check tcp-request inspect-delay 5s http-request deny if { hdr_reg(Range) -i ^(bytes\s*=)(.*?)(([0-9]){10,})(.*) } balance leastconn cookie SERVERID insert nocache indirect server backup 127.0.0.1:9081 backup non-stick option accept-invalid-http-request option http-keep-alive option forwardfor option redispatch option abortonclose maxconn 40000 server lb.org 176.34.178.134:80 weight 100 cookie lb.org agent-check agent-port 3389 agent-inter 6000 check inter 14000 rise 2 fall 3 minconn 0 maxconn 0 on-marked-down shutdown-sessions |
To test this we can turn the WAF off and run the earlier curl command to check that HAProxy is blocking the malicious request:
curl -v http://192.168.64.28/ -H "Host: www.myhost.com" -H "Range: bytes = 10-18446744073709551615" -k |
And we still get the expected response:
403 Forbidden Request forbidden by administrative rules. * Closing connection 0 |
For reference, if anyone wants the rest of the ModSecurity configuration defaults, they are as follows:
SecRuleEngine On SecRequestBodyAccess On SecResponseBodyAccess On SecResponseBodyLimitAction ProcessPartial SecRequestBodyLimitAction ProcessPartial SecAuditLog /var/log/httpd/modsec_audit_waf1.log ErrorLog /var/log/httpd/error_waf1.log LogLevel error SecAuditEngine Off # Don’t log to user_access CustomLog /dev/null common SecDefaultAction "phase:1,pass,log,auditlog" RPAFenable On RPAFsethostname Off RPAFproxy_ips 127.0. RPAFheader X-Forwarded-For SecAction "id:'100003', phase:1, t:none, setvar:tx.outbound_anomaly_score_level=4, nolog, pass" SecAction "id:'100004', phase:1, t:none, setvar:tx.anomaly_score_blocking=on, nolog, pass" SecAction "id:'100002', phase:1, t:none, setvar:tx.inbound_anomaly_score_level=5, nolog, pass" |