Pentesting ASP.NET Cookieless Sessions with Burp

We were recently testing a web application that used ASP.NET cookieless sessions. This meant that the session token was part of the URL as shown in the example below.

In this case, the session token is of the form (S(LongRandomToken), where LongRandomToken is a long, randomly generated alpha-numeric string and takes the place of  the session cookie.

This implementation makes for a messy site map when testing with Burp Suite because the changing tokens make it appear that there are limitless content paths in the application. For example, a site map that has one login page would show up as several different paths because of the changing token values as shown below.

Messy Site Map Due to Multiple Tokens

While we tested, we wanted to have a “clean” site map that essentially ignored all of the token values and mapped them as if they weren’t there. In addition, we wanted the Burp Suite spider and scanner tools to continue to work. So we came up with the following hack, read on . . .

The solution:

Use two instances of Burp.

Browser uses Burp1 as a proxy.

Burp1 uses Burp2 as a proxy.

Create a Match/Replace rule in Burp1 to pull the problematic token out of the request URL and tack it onto the end of the URL as a URL parameter.

Create a Match/Replace rule in Burp2 to grab the parameter off the end and put it back.

Use Burp1 when you want to see the nice site map.

Use Burp2 when you want to see the requests without tampering.

Burp1 listens on the default port of 8080 and we configured Burp2 to listen on port 8090.

Burp2 Listens on Port 8090

Then we configured Burp1 to use Burp2 as an upstream proxy.

 Now we have the web application connecting to Burp1, Burp1 connecting to Burp2, and Burp2 connecting to the ASP.NET Server we were testing.

Proxy Configuration

Here are the match and replace rules needed.

Burp1 Match/Replace Rule:

Match: (.*)/(S\(.*?\)\))/([^ ]*)(.*)

Replace: $1/$3\?zzzz=$2$4

This yanks the token out of the URL path and tacks it onto the end of the URL as the zzzz parameter. Don’t worry about whether there were already URL parameters and you just added another question mark, we will undo this in the Burp2 proxy before sending to the web server.

Burp2 Match/Replace Rule:

Match: (\w* /)(.*)\?zzzz=([^ ]*)(.*)

Replace: $1$3/$2$4

This works and makes a clean site map, but . . .

This only affects things that pass through the Burp proxy. If you’re using the spider, the scanner, or other tools, those rules don’t apply. For this second problem, we’ll use Burp’s macros and its session tools. If the parameter had a name, the macros are all we’d need. But because it shows up as a bare value with no name, we’re going to need that upstream instance of Burp again.

Go to Options > Sessions, and scroll down to the Macros section to add one. You can pull requests from your proxy history, or you can send those requests now and capture them as you go. You want the set of requests necessary to create a new session – the login steps.

For each URL that you need, configure the item so that Burp sends the correct inputs (e.g. username & password) and notices the right things in the responses. Usually, you want it to update the cookie jar and that’s enough. Here, though, there’s no session cookie, and the thing we want doesn’t even have a name.

We configured the first item so that it would send a request with no token in it, which causes the server to return HTTP 302 to a URL that does have the token. We configured Burp to extract the token from the HTTP 302 response. Because the actual token has no name, we made up a new name that wasn’t already used anywhere.

Next, we manually added that parameter to the second request in the query string – just typed “&aaaa=asdf” at the end of the GET string. Then, we needed to configure that second URL so that it would replace our ‘asdf’ with whatever was found in the response to the first request.

Click the “Test Macro” button to make sure that Burp is extracting the right value and putting it in the right place. Look for the “Derived parameters” in the second item. In our test, there was no way to avoid it being URL-encoded. That’s OK here because we’re going to change it in the upstream proxy anyhow. It would have been cleaner if we’d captured only the part between the inner parenthesis.

 In the upstream proxy, configure a Match and Replace rule to rewrite that URL into the form the application is expecting.

 That rule turns something like this:

GET /(S(3gmt4o45krcb0bfbzvh2ud55))/Pages/default.aspx?locId=1&aaaa=%28S%28awtsga551hn1bb550xmy2n2i%29%29 HTTP/1.1

Into something like this:

/(S(awtsga551hn1bb550xmy2n2i))/Pages/default.aspx?locId=1 HTTP/1.1

There’s a problem with this, though – it’s short-lived. There is no actual parameter called “aaaa” anywhere in this application, much less right here where we need it. If this were a normal, named parameter, we wouldn’t need to make up a temporary parameter name, and wouldn’t need that upstream proxy to rewrite the URL.

This also results in a bit of extra traffic when it’s used, and it can pollute the sitemap a bit. But for a targeted scan of one function at a time, it sure beats having to re-explore the site and run the scan all on one unbroken session.