Blocking invalid range headers using ModSecurity and/or HAProxy (MS15-034 - CVE-2015-1635)

Security Published on 4 mins Last updated

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 appliance.

I'm cheating by using our pre-release 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},setvar:tx.%{}-CustomRule,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 -H "Host:" -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] 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 ""] [uri "/"] [unique_id "VVn9138AAAEAAEC3LVAAAAAC"]
[Mon May 18 14:57:27 2015] [error] [client] 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 ""] [uri "/"] [unique_id "VVn9138AAAEAAEC3LVAAAAAC"]
[Mon May 18 14:57:27 2015] [error] [client] 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 ""] [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 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 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 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 transparent
mode http

# The following two lines do the security check
tcp-request inspect-delay 5s
http-request deny if { hdr_reg(Range) -i ^(bytess*=)(.*?)(([0-9]){10,})(.*) }

balance leastconn
cookie SERVERID insert nocache indirect
server backup backup non-stick
option accept-invalid-http-request
option http-keep-alive
option forwardfor
option redispatch
option abortonclose
maxconn 40000
server weight 100 cookie 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 -H "Host:" -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"