Zero False Positive XSS Detection

By: Ajin Abraham

XSS or Cross Site Scripting is a code injection vulnerability that existed from the time when javascript was created. This class of vulnerability still exists in various forms like Reflected, Stored, DOM, mXSS, rPO and is found in most of the modern web applications. According to OWASP XSS is the most prevalent web application security flaw.

At IMMUNIO we are constantly pushing our limits to achieve zero false positive detection and prevention of XSS. When it comes to web vulnerability scanners, detecting XSS is never perfect because of reasons like non-standard browser behaviour due to different rendering engines, unable to correctly determine various XSS contexts. Most of the scanners out there uses a request to response regex matching to see if a payload is getting reflected to confirm an XSS vulnerability. This approach can bring both false positives and false negatives.

The best way to detect XSS is to detect it from the place where the javascript in the XSS payload executes, i.e., inside the browser. This post will introduce a method to detect XSS with Zero False Positives across multiple browsers. We will write a python script that make use of Selenium WebDriver to access different browsers, payloads from OWASP Xenotix and also take advantage from JavaScript function overriding.

Architecture

This architecture consists of

  • Selenium Core: The Selenium WebDriver component that fires up the browser and loads the page with payloads from the XSS Payload database.
  • Xenotix XSS Payload: This consists of the XSS payloads from OWASP Xenotix.
  • Reporting Server: The HTTP server that collects and reports the identified XSS vulnerabilities.
  • JavaScript Callback: A set of overridden JavaScript functions that do a callback to Reporting Server on successful XSS payload execution.

Implementation

We are using Selenium WebDriver instead of headless JavaScript engines like PhantomJS (based on WebKit) because XSS payloads can have slightly different behaviour in different browsers due to differences in JavaScript rendering engines like WebKit (used by Safari, Chrome, Opera), Gecko (used by Firefox), Trident (used by Internet Explorer), EdgeHTML (used by Microsoft Edge). If we have the right WebDriver, Selenium can work with multiple browsers thereby supporting multiple rendering engines.

As per this architecture, we will inject a callback JavaScript to the page that we need to test. This callback JavaScript overrides the inbuilt JavaScript functions like alert(), prompt(), confirm() with the capability to send an XHR request to Reporting Server on successful execution of these functions. Additionally they will remove the capability to show a dialog and add more information about the payload used for XSS.

(function() { //Overridden Alert var proxied = window.alert; window.alert = function() { var http = new XMLHttpRequest(); var url = "http://localhost:8787/?b64="+window.btoa(window.location.href); http.open("GET", url, true); http.onreadystatechange = function() { if(http.readyState == 4 && http.status == 200) { console.log(http.responseText); } } http.send(); }; //Overridden Prompt var proxied = window.prompt; window.prompt = function() { var http = new XMLHttpRequest(); var url = "http://localhost:8787/?b64="+window.btoa(window.location.href); http.open("GET", url, true); http.onreadystatechange = function() { if(http.readyState == 4 && http.status == 200) { console.log(http.responseText); } } http.send(); }; //Overridden Confirm var proxied = window.confirm; window.confirm = function() { var http = new XMLHttpRequest(); var url = "http://localhost:8787/?b64="+window.btoa(window.location.href); http.open("GET", url, true); http.onreadystatechange = function() { if(http.readyState == 4 && http.status == 200) { console.log(http.responseText); } } http.send(); }; })();

The Selenium WebDriver will fireup the Browser, load the page and inject XSS payloads one by one.

#Firefox Driver Configuration fp = webdriver.FirefoxProfile() fp.set_preference("general.useragent.override","immunio-fuzzer") fp.update_preferences() DRIVER=webdriver.Firefox(firefox_profile=fp) with open(PAYLOADS,'r') as f: payload_collection=f.readlines() count=0 for payload in payload_collection: count+=1 DAT="No: "+ str(count) +" Data: " +str(payload) LOG(DAT) try: DRIVER.get(BASE_URL+payload) time.sleep(DELAY) except Exception as e: print "[ERROR] Selenium - " + str(e) pass print "Fuzzing Completed" DRIVER.quit() sys.exit(0)

When a particular payload that contains a call to alert()/prompt()/confirm() executes, a callback is made to the Reporting Server.

class MainHandler(tornado.web.RequestHandler): def get(self): try: b64 = self.get_argument('b64', True) DAT="[EXECUTED] Payload Executed: " + base64.b64decode(b64) LOG(DAT) self.set_header('Access-Control-Allow-Origin', '*') self.write(b64) except: self.write("ERROR") application = tornado.web.Application([ (r"/", MainHandler), ]) application.listen(8787)

The Reporting Server will log down the executed Payload obtained from the callback.

Conclusion

The advantage of this method is that it can guarantee Zero False Positive results as the payload execution happens in real browser. Again with proper WebDriver, Selenium supports multiple browsers allowing us to perform the tests in different rendering engines. You can download the fully functional script from immunio-xss-fuzzer repository.


Originally published at www.immun.io on December 15, 2015.

Like what you read? Give IMMUNIO a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.