Saturday 3 July, 2010

Experiments in Wackiness: Allowing percents, angle-brackets, and other naughty things in the ASP.NET/IIS Request URL

Just because you CAN do something doesn't mean you SHOULD. However, it's always nice to do something crazy so that you can better understand a system.

Warning: There is no warranty implied here. I'm loading the gun and showing you where to point it. If you point it at your foot, that's your business. Safety mechanisms exist for a reason and if you're going to to use this tip to just "get an app to work" but you're not sure why it's broken and you're just flipping switches to "get'er done" then step backwards out of the minefield and hug your family. Don't trust user input and don't let users submit unencoded script or markup.

I got a question from a fellow at Webcamps China about why he couldn't put in a URL. He wanted to do something like : http://localhost:39090/Default/%3Cb%3E That's a as in "%3C b %3E" were the %3C is <>.

Notice he's asking to put this in the Request Path, the URL, not in the Query String. If I create a quick ASP.NET MVC 2 app (this could be done in WebForms also) and issue http://localhost:39090/Default/%3Cb%3E I get this HTTP Error 400. Notice it's not a standard YSOD, it's a simple error message.

Bad Request - Windows  Internet Explorer (2)

This error was caught and issued early in the pipeline, before ASP.NET proper got involved. In this case, I'm using the development web server, but I'd get a similar error from IIS proper.

Now, if I change the URL to use the QueryString instead of the Request Path: http://localhost:11965/Home/Index?id=%3Cb%3E

A potentially dangerous  Request.QueryString value was detected from the client (id=b). -  Windows Internet Explorer

This was caught in a different place, in ASP.NET managed code, specifically in input validation. I remember when I first used input validation 7 years ago. ;)

If you really know what you're doing and you know WHY you're doing it, you can turn Request Validation off in a number of ways.

However, this is a really bad idea so they've made it harder in ASP.NET 4 and you have to explicitly tell it you want the 2.0 style of request validation this first in the system.web section via requestValidationMode:

1
<httpRuntime requestValidationMode="2.0" />

Then, if you are using WebForms, you can turn it off totally at the application level in your web.config.

1
<pages validateRequest="false" />

Or, again, if you're using WebForms, you can turn it off for one page:

1
<%@ Page validateRequest="false" %>

If you're using MVC, you turn it off Input Validation using the [ValidateInput(false)] attribute on a class or a method.

In this case if I turn it off on my method, it works and I can pass the encoded as %3Cb%3E directly in the Query String, but STILL not in the Request Path as that's a different path through the server.

So, with request validation in 2.0 mode and also explicitly turned off on my method:

Now here I'm really telling you about the minefield. Best way to not get killed in a minefield is to avoid it all together, but, since you insist...

Stefan from the ASP.NET team says:

  • By default the "requestPathInvalidChars" attribute contains seven characters considered invalid (the less than and greater than signs as well as the ampersand are encoded since configuration is an Xml file). Those are on the httpRuntime element, and the attribute is requestPathInvalidCharacters="<,>,*,%,:,&amp"

So, then I might be able to change

1
2
3
<httpRuntime requestValidationMode="2.0"
requestPathInvalidCharacters="<,>,*,%,:,&,\"
/>

to this, removing the <, >, and %.

1
2
3
<httpRuntime requestValidationMode="2.0"
requestPathInvalidCharacters="*,:,&,\"
/>

But, remember that a % is a special thing used in URL Encoding (percent encoding) and you can say things that are encoded correctly like %3B or things that aren't like %ZZ. So it depends on the semantics you want. What do you intend for a % to mean? From Stefan:

Note though that the “%” character has special meaning as the beginning of a Url-encoded character sequence. You may run into a problem with IIS rejecting the Url depending on what comes after the % sign. For example if the inbound Url is:

http://whatever/blah%2512

IIS7 will complain because after it does a Url-decode the Url will look like:

http://whatever/blah%12

Which will trigger the double-decode detection alert since the Url will mutate again if it is Url-decoded a second time.

To suppress that error you need to add the following to configuration as well. requestFiltering allowDoubleEscaping="true"

1
2
3
4
5
<system.webServer>
<security>
<requestFiltering allowDoubleEscaping="true"/>
security>
system.webServer>

However, at this point, you're turning off all sorts of things, and are in danger of making URLs that just shouldn't be. ;) You may also have a latent canonicalization bug floating around in your code if you head down this road.

But does it work? It doesn't work under Visual Studio Development Web Server as the system.webServer section only applies to IIS7. If I deploy my ASP.NET MVC application to IIS, it starts to work as I get into my controller action, but then as I return the ViewResult via "return View()" it fails deep inside of ASP.NET proper as the WebFormsViewEngine's ViewPage implementation of RenderView() ends up calling Server.Execute which calls HttpRequest.MapPath. MapPath assumes there's an underlying file and calls InternalSecurityPermissions.PathDiscovery().Demand(). This contains a FileIOPermisson which checks for illegal characters. Since you can't have a file that contains a <>, it fails. Bummer.

System.IO.Path.CheckInvalidPathChars(String path) +142

System.Security.Permissions.FileIOPermission.HasIllegalCharacters(String[] str) +97
System.Security.Permissions.FileIOPermission.AddPathList(FileIOPermissionAccess access, AccessControlActions control, String[] pathListOrig, Boolean checkForDuplicates, Boolean needFullPath, Boolean copyPathList) +96
System.Security.Permissions.FileIOPermission.AddPathList(FileIOPermissionAccess access, String[] pathListOrig, Boolean checkForDuplicates, Boolean needFullPath, Boolean copyPathList) +38
System.Security.Permissions.FileIOPermission..ctor(FileIOPermissionAccess access, String path) +92
System.Web.HttpRequest.MapPath(VirtualPath virtualPath, VirtualPath baseVirtualDir, Boolean allowCrossAppMapping) +639
System.Web.HttpServerUtility.Execute(IHttpHandler handler, TextWriter writer, Boolean preserveForm, Boolean setPreviousPage) +195
System.Web.HttpServerUtilityWrapper.Execute(IHttpHandler handler, TextWriter writer, Boolean preserveForm) +94
System.Web.Mvc.ViewPage.RenderView(ViewContext viewContext) +403
...blah blah...
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +371

Is there a way to tell the system to "relax?" I mean, seriously, not every URL maps to a file system. If only there were an attribute called relaxedUrlToFileSystemMapping="true". ;)

Oy, if you're not afraid now, be afraid. If you're made it that far, again, you're on your own, turning safeties off, yada yada, mercy on your soul, etc.

1
2
3
4
<httpRuntime requestValidationMode="2.0"
requestPathInvalidCharacters="*,:,&,\"
relaxedUrlToFileSystemMapping="true"
/>

While we're in here, note that in ASP.NET 4 you can also change allowed path and queryString lengths:

1
<httpRuntime maxRequestPathLength="260" maxQueryStringLength="2048" />

At this point the only thing that is saving us is the list of characters in requestPathInvalidCharacters. For example, if I try to sneak a %3A by, it won't work as that's a colon (:) and it's in the list. Issuing http://localhost:11965/Home/Index?id=%3A gives me this error "A potentially dangerous Request.Path value was detected from the client (:)." I find it slightly funny that they output the offending evil character. Don't look directly at it! :)

A potentially dangerous  Request.Path value was detected from the client (). - Windows Internet  Explorer

But still, at this point, now I CAN do this URL: http://localhost:11965/Home/Index?id=%3Cb%3E

Home Page - Windows Internet  Explorer

And I'm saved, finally, by the new HtmlEncoding syntax <%: rather than <%=.

The <%: mystring %> syntax in ASP.NET 4 is equivalent to <%= Server.HtmlEncode (mystring) %> and since it's the default, my UrlEncoded was HtmlEncoded on the way back out.

After ALL this effort to get crazy stuff in the Request Path, it's worth mentioning that simply keeping the values as a part of the Query String (remember WAY back at the beginning of this post?) is easier, cleaner, more flexible, and more secure.

Enjoy, take care, and please don't point that at me.

1 comment:

  1. You stole this from Scott Hanselman's blog. You should atleast quote the original author or link to his work. Shameless Plagiarism

    ReplyDelete