Scott Hanselman

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

June 9, '10 Comments [12] Posted in ASP.NET | ASP.NET MVC | IIS
Sponsored By

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 < and %3E 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:

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

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

<%@ 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="<,>,*,%,:,&"

So, then I might be able to change


requestPathInvalidCharacters="<,>,*,%,:,&,\"
/>

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


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"





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 < or >, 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.


requestPathInvalidCharacters="*,:,&,\"
relaxedUrlToFileSystemMapping="true"
/>

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

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/%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.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Thursday, June 10, 2010 2:32:40 AM UTC
Thank you very much for your answer!:)
Thursday, June 10, 2010 7:14:23 AM UTC
Interesting solution , Thank's Scott :0
Thursday, June 10, 2010 9:45:45 AM UTC
All that work so people can send evil scrips to your code.
I was just kidding seams to be a very interesting approuch i'm expecialy interested in use the : it can make your urls looks nice
Thursday, June 10, 2010 2:01:33 PM UTC
Great summary of dispersed information. I actually had to figure all of that out a few days ago in order to have very very dangerous urls (file share service) ;-)
tobi
Thursday, June 10, 2010 2:08:48 PM UTC
Thanks for this. I have a customer that is insisting on using an asterisk as part of a querystring parameter.

This gets a 5 ninja rating:)
pat
Thursday, June 10, 2010 9:39:13 PM UTC
I just wish I could intercept the ugly 400 error page and display something more user friendly.
brian
Friday, June 11, 2010 8:17:06 AM UTC
Hi Scott,
a lot of interesting things, as usual, thanks !

@Brian: you can do this in the global.asax:
Example:

void Application_Error(object sender, EventArgs e)
{
if (Server.GetLastError().GetType() == typeof (HttpRequestValidationException))
{
Server.ClearError();
Response.Redirect( "nicerErrorPage.aspx?aspxerrorpath=" + Server.UrlEncode( Context.Request.UrlReferrer.PathAndQuery ), true );
}
}


Or you can do it in the web.config.:

<customErrors defaultRedirect="error.aspx" mode="RemoteOnly">
<error statusCode="404" redirect="error404.aspx"/>
</customErrors>
Friday, June 11, 2010 9:13:59 PM UTC
Please add support Visual Studio 2010 to RockScroll! (RockScroll post closed)
Friday, June 11, 2010 9:18:31 PM UTC
I encountered these issues when porting my existing blog to MVC to utilize URLs for titles. Many of the titles had "odd" characters in them and I ran into the URL validation errors myself. Ultimately, I ended up writing a custom method to replace the odd characters before using them in the URL, so that I could enjoy the benefit of not going through the pain described above and utilize the built-in validation provided by IIS, ASP.NET and MVC.
Monday, June 14, 2010 9:19:40 AM UTC
Hi, on a related note is it possible to disable/tweak validation for an individual 'button' on a page?

I've been trying to create a working 'HTML button element', which as everyone knows doesn't play nice in IE6 (and maybe IE7) because it posts back it's innerHTML rather than it's value attribute. This is fine until you put HTML inside of your HTML Button element, and then the validation kicks in and says it's an unsafe request. Of course validation could be disabled on the page, but I'd rather keep validation, and just ignore it for the HTML Button post data.

The main reason of using a HTML Button (pure without js hacks) is that is allows for a lot more flexibility with styling.

Any thoughts?
Jon B
Thursday, November 18, 2010 6:37:43 AM UTC
Very helpful and nice article. This blog is really helpfull like this one, http://geekswithblogs.net/mbridge/archive/2010/11/02/error-message---a-potentially-dangerous-request.querystring-value-was-detected.aspx. I get many good instruction from this blog too. Hope it helps!!
Jonathan Green
Tuesday, November 23, 2010 12:48:18 PM UTC
It didn't work.
Is it be related with 64bit windows?
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.