ValidateRequest should probably still be Enabled

I noticed this post on reddit a couple weeks back, and it’s called “new .net xss bypass”. I look at .net apps more than anything else right now as part of my day job, so this new bypass is something I was already aware of. There are quite a few comments I think are a bit off, and the article calls this a “vulnerability”. This is an interesting subject, and like a lot of things, people say things that can be misleading or not tell the entire story…

What is validateRequest?

validateRequest kind of works like a WAF, and it’s main purpose is to mitigate reflected XSS. It’s enabled by default, and you can see it in action on most .NET websites. For example, check out http://windows.microsoft.com/en-US/windows/home. If you send it http://windows.microsoft.com/en-US/windows/home?myparam=aaa or http://windows.microsoft.com/en-US/windows/home?myparam=aaa%3c it’s fine, but then if you send it http://windows.microsoft.com/en-US/windows/home?myparam=aaa%3ca it gives an error (note the only difference between the last two is < vs <a). What's going on here?

Well, on the server, the stack trace probably looks something like this (this is what it looks like in my test application).

[HttpRequestValidationException (0x80004005): A potentially dangerous Request.QueryString value was detected from the client (test="<a").]
   System.Web.HttpRequest.ValidateString(String value, String collectionKey, RequestValidationSource requestCollection) +9665149
   System.Web.<>c__DisplayClass5.<ValidateHttpValueCollection>b__3(String key, String value) +18
   System.Web.HttpValueCollection.EnsureKeyValidated(String key) +9664565
   System.Web.HttpValueCollection.GetValues(Int32 index) +29
   System.Web.HttpValueCollection.ToString(Boolean urlencoded, IDictionary excludeKeys) +206
   System.Web.UI.Page.get_ClientQueryString() +411
   System.Web.UI.HtmlControls.HtmlForm.GetActionAttribute() +259
   System.Web.UI.HtmlControls.HtmlForm.RenderAttributes(HtmlTextWriter writer) +724
   System.Web.UI.HtmlControls.HtmlControl.RenderBeginTag(HtmlTextWriter writer) +41
   System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) +20
   System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) +53
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
   System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) +40
   System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +128
   System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
   System.Web.UI.Control.Render(HtmlTextWriter writer) +10
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
   System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +128
   System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
   System.Web.UI.Page.Render(HtmlTextWriter writer) +29
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +6704
   System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +245
   System.Web.UI.Page.ProcessRequest() +72
   System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context) +21
   System.Web.UI.Page.ProcessRequest(HttpContext context) +58

Tracing down the line from the validateString method, eventually it calls this for all values (not keys).

// System.Web.CrossSiteScriptingValidation
internal static bool IsDangerousString(string s, out int matchIndex)
{
	matchIndex = 0;
	int startIndex = 0;
	while (true)
	{
		int num = s.IndexOfAny(CrossSiteScriptingValidation.startingChars, startIndex);
		if (num < 0)
		{
			break;
		}
		if (num == s.Length - 1)
		{
			return false;
		}
		matchIndex = num;
		char c = s[num];
		if (c != '&')
		{
			if (c == '<' && (CrossSiteScriptingValidation.IsAtoZ(s[num + 1]) || s[num + 1] == '!' || s[num + 1] == '/' || s[num + 1] == '?'))
			{
				return true;
			}
		}
		else
		{
			if (s[num + 1] == '#')
			{
				return true;
			}
		}
		startIndex = num + 1;
	}
	return false;
}

Obvious Edge Cases

So clearly, validateRequest makes no attempt to stop most DOM XSS, and there’s no attempt at all with stored XSS. It’s also clearly limited in preventing reflected XSS too. There’s no attempt at detecting encoding, so there’s kind of an assumption that the encoding is UTF-8. Not to mention if there’s an xss in the whole query string, in which case you could just put it in the keys. Not to mention if there’s user input being reflected in the context of an attribute, validateRequest does not stop it. For example:

<img src="" title="USERINPUT">

In this context, validateRequest doesn’t stop reflected XSS. It does, however, try to stop at least this case:

<html><body>
USERINPUT
</body></html>

If you can get script to execute in this context with UTF-8 encoding, you’ve bypassed something validateRequest was meant to prevent.

The bypass I saw on reddit

The bypass looks like this:

‹%tag style="xss:expression(alert(123))" ›

This can make it through validateRequest’s filter because the < is not followed by a bad char (% is fine). The tags execute in IE9 in compatibility mode in the context above (it doesn’t seem to execute in IE 8- or IE 10).

There are some limitations in this attack though. First, remember we’re talking about just IE9. Second is the fact that by default IE9’s xss filter is turned on, and IE 9's xss filter will prevent the above from executing. There might be a generic way to bypass both validateRequest and IE9’s xss filter, but I am not aware of it.

IE_filter

Things I’ve Tried

In the past, I’ve tried a few bypasses of validateRequest myself.

Unit Test with single separators

I’ve tried the following test cases. This does catch the <% bypass with IE9 in compatability mode, but nothing else fires. One thing that I thought was a bypass at first was %00, but validaterequest does actually catch that.

def bad_char(m):
    if (m == "<" or m== "!" or m == "?" or m == "/" or (ord(m) >= 0x41 and ord(m) <= 0x5a) or
        (ord(m) >= 0x61 and ord(m) <= 0x7a)):
        return False
    return True

def singlechars_img(f):
    for i in range(1,256):
        if not bad_char(chr(i)):
            continue
        f.write("<" + chr(i) + "img src='' onerror=alert(" + hex(i) + ") /> <br />\n")

def singlechars_style(f):
    for i in range(1,256):
        if not bad_char(chr(i)):
            continue
        f.write("<" + chr(i) + "tag style=\"xss:expression(open(alert(" + str(i) + ")))\" > <br />\n")

Trying to be clever (overlong UTF-8, etc)

One thing I tried was to backspace one of the tags with the ascii value 0x08: <<0x08script>. This doesn’t execute in any browser tried

I tried the shift JIS prefix code %e0 which should consume the next character as part of a multibyte literal. The idea is that if validaterequest tries to decode any data, it may consume an extra %3c, whereas some browsers (like chrome) do not consume an extra %3c and there would be a mismatch. But validaterequest is much simpler, and just iterates over all the bytes like we can see from the reflected snipped above.

I also tried several overlong UTF-8 combinations, but these don’t execute with content-type UTF-8

#overlong UTF-8
def overlong(f):
    #attempt to delete a <
    f.write("<<\x08script>alert(1)</script>")
    #some overlong UTF
    f.write("<\xFC\x80\x80\x80\x81\xA9\xFC\x80\x80\x80\x81\xAD\xFC\x80\x80\x80\x81\xA7 src='' onerror=alert(2) />")
    f.write("<\xFC\x80\x80\x80\x81\xA9mg src='' onerror=alert(2) />")
    f.write("\xC1\xA9mg src='' onerror=alert(3) />")
    f.write("\xF8\x80\x80\x81\xA9mg src='' onerror=alert(3) />")

Conclusions

The ‹%tag style=”xss:expression(alert(123))” › validaterequest bypass is interesting and something to keep in the back of your pentesting pocket, but if you’re on defense don’t panic. I don’t think this really breaks validaterequest in a meaningful way. Pulling numbers out of my ass, I would say that if 30% of xss were prevented by validaterequest without this, then maybe this knocks the successfully prevented attacks down like a percent or two, to 28% (exploitable against users who use IE9 and have disabled the xss filter (or where you can get around the xss filter)). validaterequest does have it’s problems, and there have been generic bypasses in the past, but I think it’s still worthwhile to enable.

One Response to ValidateRequest should probably still be Enabled

  1. The most common edge case I’ve seen is when there’s an open redirect on the site – then IE’s xss filter won’t fire. Using that and the <% trick this would be exploitable.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: