Server-Side XSS Attack Detection with ModSecurity and PhantomJS
Client-Side JS Overriding Limitations
In a previous blog post, I outlined how you could use ModSecurity to inject defensive JS into the HTML response page sent to the client web browser. The goal of this technique was to override many common JS elements that are often used by security researchers/attackers when conducting reconnaissance testing for XSS flaws. While this was an interesting PoC for using ModSecurity in combination with defensive JS, it had one fatal design flaw: the detection logic was exposed to the attacker. By sending our defensive JS code to the client, we are tipping our hand to the attacker as to our intention and methods. This allows attackers to then play around with various evasion methods.
Ideally, we need to keep this type of detection logic server-side, away from the prying eyes of attackers. But how then do we detect Browser/DOM events server-side? This blog post will demonstrate another PoC that uses ModSecurity's Lua API to execute PhantomJS to evaluate HTML data sent to end users and thus gain insight into XSS payloads that actually execute in the web browser.
What is PhantomJS?
PhantomJS describes itself as:
PhantomJS is a headless WebKit with JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.
PhantomJS is created by Ariya Hidayat.
It is useful for:
- Headless Web Testing
- Screen Captures
- Automation Tesing
- Network Monitoring
The key browser functionality at play with PhantomJS is that it uses the WebKit browser engine that is found within Safari and Google Chrome. For our purposes, the fact that it is "headless" is key as we want to use it for server-side inspection of HTML data to help identify XSS attacks.
Using PhantomJS for Server-Side XSS Detection
Due to the fact that PhantomJS can utilize WebKit's full browser environment, we can detect successful reflected XSS attacks much more accurately than a server-side app/WAF regular expression filter. The main advantages are:
- De-obfuscation - many XSS (and malware/exploit) payloads utilize various encoding and obfuscation tricks to hide their true intention. Consider techniques such as using partial non-alphanumeric obfuscation. For example, if an inbound filter was looking for "alert" you could easily use the following non-alpha code to byass it:
By using PhantomJS, we can do our analysis at execution time within the Browser DOM after deobfuscation.
- Execution Detection - there is a distict difference between identifying an attack payload vs. one that executed successfully. Just because an attacker sent an XSS payload to the web application does not necessarily mean that it will execute properly within the target's web browser. The application may in fact apply proper contextual output encoding to the payload and thus neutralize it when it sends it to the browser. Rather than having a WAF playing the "Boy Who Cried Wolf" everytime some HTML/JS data comes into the application, it would be more useful to only signal an alert if certain data successfully executes within the browser.
In order to use PhantomJS to detect successful XSS attacks, we need to be able to pass it real data from live HTTP transactions.
Server-Side JS Overriding
Have I mentioned how completely awesome the Lua API is in ModSecurity? Well, it is. It allows you to hook in any external program you want which creates an endless number of integrations. With Lua, we can use ModSecurity to extract the required HTTP data and pass it off to PhantomJS for inspection. Here is an example rule that launches our js-overrides.lua script:
SecRule RESPONSE_HEADERS:Content-Type "@contains html" "chain,id:'85',phase:5,log,pass"
SecRule &ARGS "@gt 0" "chain"
SecRuleScript /usr/local/apache/conf/crs/lua/js-overrides.lua
This script will run at the end of the current transaction (phase:5 in ModSecurity) after the response has already been sent to the client. This is done to limit latency impacts on clients so the response is not held while PhantomJS tests are being run. If you want to do active response blocking, you could move this rule to phase:4. The rule will check the response content-type to verify it contains html data and then checks to see if there was any parameter data being sent to the web application (as we would see in a reflected XSS attack). If these conditions are met, it will then fire off the js-overrides.lua script:
The Lua script exports the RESPONSE_BODY content obtained by ModSecurity and dumps it into a temporary OS file. It then executes the highlighted PhantomJS command with two arguments:
- xss.js - this is the PhantomJS script file with our detection logic.
- tmp_response_body - is the temporary OS file that holds the HTML data.
Let's now take a look at the xss.js file contents:
The script creates a new PhantomJS WebPage object and populates it's contents with the data in the highlighted line. This line essentially mimicks our previous PoC example where we manipulated the outbound HTML response going to the real client and prepended our JS overrides code (in the js-overrides.js file) to the top. Here are the contents of the js-overrides.js file:
The script then calls up the wp.evaluate command to have PhantomJS/WebKit evaluate the contents of our web page.
Example Testing
In order to test this setup, we can take examples from the xssed.com archive such as this one that has been fixed:
The highlighted parameter data would then be reflected into the response body content like this:
And this would execute in the web browser and issue the JS Alert pop-up box:
After the PhantomJS script evaluates the web page data, the JS overrides code would identify the JS alert code execution and it would generate the following ModSecurity event:
As you can see, we now are able to accurately identify when actual JS code executes in a client-side context thanks to PhantomJS/WebKit without having to expose our detections to the end user!
Access Attempts for document.cookie DOM Object
In addition to our JS Override proxy code, we can also gain alert data generated by WebKit if client-side code attempts to violate security policies. One such can is when JS attempts to access the document.cookie data. Here is another example taken from xssed.com for an old flaw in the zdnet.com site that is fixed:
When this successfully executes, PhantomJS/WebKit will generate the following ModSecurity alert indicating a DOM Security violation:
Up to this point, we have only been using PhantomJS to evaluate the HTML response body and looking for certain events. There is another key piece of XSS protection that is available within WebKit that we can take advantage of: XSSAuditor.
Leveraging WebKit's XSSAuditor
The WebKit engine contains reflected XSS protection code called XSSAuditor (an excellent whitepaper by the developers is here). The XSSAuditor filter is located in between the HTML Parser and JS engines which helps with its accuracy.
XSSAuditor will analyze inbound client data with the outbound, parsed HTML data to see if there is any executable code found from the client within the response. If there is, then XSSAuditor would generate a console alert similar to the following:
Request Mimicking with PhantomJS
In order to use WebKit's XSSAuditor component, you must initiate an actual HTTP request. We do not want to replay the captured request data on the live web application as that could have negative impacts to the current users application flow. Fortunately, PhatomJS comes equipped with its own embedded web server! We can therefore use it to send/receive data captured by ModSecurity from the live transaction. Let's take a look at the updated ModSecurity rule as it is executing a new Lua script called xssdetect.lua:
SecRule RESPONSE_HEADERS:Content-Type "@contains html" "chain,id:'85',phase:5,log,pass"
SecRule &ARGS "@gt 0" "chain"
SecRuleScript /usr/local/apache/conf/crs/lua/xssdetect.lua
Here is the updated xssdetect.lua script contents:
Notice the new bolded/highlighted sections as we now also need to gather the inbound request URI data (including QUERY_STRING contents). We then execute PhantomJS again but we pass it a new file called xssdetect.js and two more arguments holding the names of the temp OS files holding the request/response data. Here is the contents of the xssdetect.js file:
The xssdetect.js script does the following:
- Initiates its own embedded web server module (based on Mongoose) listening on the loopback interface on a random 4-digit port.
- Sets XSSAuditingEnabled = true.
- Initiates a new HTTP request using the REQUEST_URI data exported from ModSecurity.
- The PhantomJS web server responds with the HTML response data exported from ModSecurity.
- PhantomJS/WebKit then evaluates the HTML page.
- Alerts are generated in two different scenarios - if the XSSAuditor triggers or if one of the JS events of interest execute.
XSSAuditor Alerts with PhantomJS
If this same request from the previous ZDnet XSS screenshot was sent through our server-side implementation, we would received the following alert indicating that WebKit's XSSAuditor blocked execution of reflected XSS code:
Future Testing
While I must admit that I am pretty excited about the possibilities for server-side XSS detection with this proof of concept, there are some considerations that must be addressed if you want to field this setup in a production environment.
- When to use it - As you can see from my example SecRules, I am only initiating this script under certain circumstances. You may only have very specific conditions or locations within your web application that you want to activate this analysis.
- Asynchronous Mode - in the current setup, we are actually waiting for the command line PhantomJS program to return results. This leads to an obvious latency/performance impact. One option for dealing with this would be to set the PhantomJS execution as a background process and then have the PhantomJS code actually initiate its own request back to the web server sort of like a RESTful API. When sending back this beacon request, it would be wise to pass the UNIQUE_ID variable data that was assigned to the original transaction. This would allow you to then tie the beacon alert with the actual data.
- PhantomJS N-tier Architecture - ideally you could setup and entire platform of PhantomJS nodes where you can pass job requests to them. There are other web automation frameworks that could be utilized here such as NodeJS, Selenium, WebDriver, etc...
Acknowledgments
There are many security researchers who have in part inspired this work and/or who directly helped me with developing this proof of concept:
- Ahamed Nafeez (@skeptic_fx)
- Krzysztof Kotowicz (@kkotowicz)
- Mario Heiderich (@0x6D6172696F)
- Neil Matatall (@ndm)
- Zane Lackey (@zanelackey)
ABOUT TRUSTWAVE
Trustwave is a globally recognized cybersecurity leader that reduces cyber risk and fortifies organizations against disruptive and damaging cyber threats. Our comprehensive offensive and defensive cybersecurity portfolio detects what others cannot, responds with greater speed and effectiveness, optimizes client investment, and improves security resilience. Learn more about us.