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 :)

php multiuser system – the www-data problem

On a lot of multi-user systems, like the one at the school where we have 300+ users all with usermod enabled, we also happen to have other web services running. It’s inconvenient and in insecure for everyone to be running their dynamic web stuff as the same user. I understand this is nearly impossible to do with good security, but this is a university and the point of this server is to let students learn, which means being able to host code.

One security problem in particular is php. suexec was built for cgi-bin stuff – but php is a whole other beast. That’s what I’m talking about here – getting php to run as the user who owns it. More specifically, this will show how /home/user/public_html/myphp.php will run as “user”, but stuff in /var/www will still run as www-data.

One good article I found describing this is here: http://alain.knaff.lu/howto/PhpSuexec/

First things first – mod_php needs to be disabled. This can be done globally, but it’s better to just disable it for public_html dirs. This can be done by adding the following to /etc/apache2/apache2.conf.

<Directory /home>
 php_admin_flag engine off
</Directory>

Now, to enable suphp.

First install php-cgi. and the apache2 prefork which has some things we’ll need later on.

apt-get install php-cgi apache2-prefork-dev

Do not install libapache2-mod-suphp – at least not on 8.04. This older version lacks some of the things most people need… like having more than one directory.

Download the latest suphp module from http://www.suphp.org/Home.html.  Compile this like:

tar xfzv suphp-SNAPSHOT-2008-03-31.tar.gz
cd suphp-SNAPSHOT-2008-03-31
./configure --with-apxs=/usr/bin/apxs2 --with-setid-mode=owner
make
make install

Modify apache’s config

LoadModule suphp_module /usr/lib/apache2/modules/mod_suphp.so
<Directory /home>
AddHandler application/x-httpd-php .php .php3 .php4 .php5 .phtml
suPHP_AddHandler application/x-httpd-php
suPHP_Engine on
</Directory>

Now in /usr/local/etc/suphp.conf

[global]
webserver_user=www-data
docroot=${HOME}/public_html
check_vhost_docroot=false

[handlers]
;Handler for php-scripts
application/x-httpd-php="php:/usr/bin/php-cgi"

Restart apache. To debug, check /var/log/apache2/errors.log.  To test create scripts in public_html directories and in /var/www that exec(‘whoami’) and make sure they’re called with the correct permissions.

It’s a start, but then there’s always stuff like XSS, etc.

Simple Beauty Website Baker Template

This is a port of simple beauty found at oswd.com to websitebaker with some modifications.

You can download the template here.

Here are some Screenshots

First – this is what you get with a simple install. I made it so you don’t need a banner – though it certainly looks better with one I think.

Here it is with a banner.  Though I suppose you don’t need a screenshot for this.  OTOH maybe I’ll change the template again so…

websitebaker module: Random pic with text

This module includes a function you can call to randomly pick an image from a directory. It is based on a module written by John Maats, and I just added the captioning. 

Here is a link to the zip.

<?php
/* Random image snippet
   Call this nsippet with:
   RandomImage ('/media');
   in your template */

function RandomImage($dir) {
        //read folder and get the picture names
        $folder=opendir(WB_PATH.$dir.'/.');
        while ($file = readdir($folder))
        $names[count($names)] = $file;
        closedir($folder);

        //remove any non-images from array
        $tempvar=0;
        for ($i=0;$names[$i];$i++){
                $ext=strtolower(substr($names[$i],-4));
                if ($ext==&quot;.jpg&quot;||$ext==&quot;.gif&quot;||$ext==&quot;.png&quot;){
			$names1[$tempvar]=$names[$i];$tempvar++;
		}
        }

        //random
        srand ((double) microtime() * 10000000);
        $rand_keys = array_rand ($names1, 2);

        //random image from array
        $image=$names1[$rand_keys[0]];

        //name of image for alt text
        $name=substr($image,0,-4);

        //print associated Text
        echo &quot;<p><b>$name</b></p>";

        //read in the file if it exists

        if(file_exists(WB_PATH.$dir . '/' . "$name" . ".txt"))
        {
                $myfile=file(WB_PATH.$dir . '/' . "$name" . ".txt");
                echo '<p>';

                foreach ($myfile as $val)
                {
                  echo "$val ";
                }
                echo '</p>';
        }

        //image dimensions
        $dimensions = GetImageSize(WB_URL.$dir.'/'.$image);
        echo '<img src="'.WB_URL.$dir.'/'.$image.'" alt="'.$name.' image" />';
}
?>

websitebaker modules

Here are two modules I wrote for website baker. One allows you to sort
news arbitrarily, the other allows you to post multiple groups.

Writing these is how I wiped out this very website :) I didn’t have the installer quite right at the time. If you’re installing this, back things up just in case. But they really shouldn’t break anything. I swear.


anews.zip

gnewswrapper.zip

Here is a screenshot of anews. It allows you to sort news posts by title, date, reverstitle, or reversedate. Plus everything else the news module does, as it is just an extension of that.

Here is a screenshot of gnewswrapper. It allows you to post stuff by section from arbitrary groups.

From the readme:

Although the news module is extremely versitile, it lacks an easy way to post products or data items. For example:

-if you have a website that lists various types of faculty
-if you have a page that lists your various types of products
-if you have a page that lists courses for the current term

The news module can almost handle many of these problems. However, there are several shortcomings when the news module is ued for this purpose.

1. Although entries can be reordered, they are ordered by date. It can be a pain to click the up or down arrows until you get an order tham makes sense. With products, it might be better to have the entries ordered by price, for example. For faculty it would most likely be by their name.
2. Multiple groups, and only the groups selected cannot be easily specified. See the example below.

The group wrapper and anews modules provide a solution to this problem.

Example Usage
———-
Problem: I have hundreds of faculty, council members, and staff I want to specify at various points on the webpage. I have a CS page where I want to list the CS faculty. Elsewhere on the website, I have a University Contact page where I want to list everyone.

Solution:

-Create a single anews page that stores all people. Create different groups for CS Faculty, Council Members, etc. Make this page hidden.
-Sort it as desired, probably by name
-Create a group wrapper page and add the groups that you want displayed.

Todo
———–
It’s definitely conceivable of a time where this is insufficient.

-if you want to show items, but not necessarily the whole group
-if you want to show items in an order not specified

It should be pretty easy to extend the module to include these cases

————
These modules were written by Rich Lundeen

Follow

Get every new post delivered to your Inbox.