Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Normalizing Backslashes to Forward Slashes

ASP.NET is 'normalizing' backslashes in requests paths to forward slashes, and I need them to come through as backslashes (it is used to perform a look-up in the database). I don't mind if escaped forward-slashes come through as unescaped, unlike this question.

config.Routes.MapHttpRoute(
    name: "TransactionsApi",
    routeTemplate: "api/transactions/{*transaction}",
    defaults: new { controller = "transactions", transaction = RouteParameter.Optional }
);

Note that I have set up transaction to match the remainder of the path.

I tried the following URLs (both from a browser and Fiddler):

  • api/transactions/mscorlib.pdb\DFA83312EAB84F67BD225058B188F22B1\mscorlib.pdb
  • api/transactions/mscorlib.pdb\\DFA83312EAB84F67BD225058B188F22B1\\mscorlib.pdb
  • api/transactions/mscorlib.pdb%5CDFA83312EAB84F67BD225058B188F22B1%5Cmscorlib.pdb
  • api/transactions/mscorlib.pdb%5C%5CDFA83312EAB84F67BD225058B188F22B1%5C%5Cmscorlib.pdb

By the time they hit my Web API method they are all mscorlib.pdb/DFA83312EAB84F67BD225058B188F22B1/mscorlib.pdb. I inspected the current HttpContext and it looks like ASP.NET is doing this normalization (not MVC4).

Possible solutions:

  • When inserting transaction normalize '\' to '/' so that no matter what ASP.NET passes through the look-up will succeed. Seems a bit smelly
  • Base64 the {*transaction} part if it contains a backslash. Not really address-bar hackable

Any idea on how to get ASP.NET to not do this normalization?

like image 735
Jonathan Dickinson Avatar asked Nov 29 '12 11:11

Jonathan Dickinson


1 Answers

Short answer

You cannot prevent this behavior as it is hard-coded into IIS.

Investigation

I wanted to investigate this issue by decompiling the runtime and following the code. It's always nice to do that: you learn how the runtime works and sometimes you find the problem. Let's start the journey...

As a starting point, I'm decompiling System.Web with ILSpy, starting at the HttpRuntime class. Navigating through public static void ProcessRequest(HttpWorkerRequest wr), ProcessRequestNoDemand, ProcessRequestNow, ProcessRequestInternal...

Here I want to investigate the following lines:
httpContext = new HttpContext(wr, false);,
httpContext.Response.InitResponseWriter();,
httpAsyncHandler.BeginProcessRequest(httpContext, this._handlerCompletionCallback, httpContext);.

In HttpContext.HttpContext(HttpWorkerRequest wr, bool initResponseWriter) many things may cause this:
this.Init(request, response),
new HttpRequest(wr, this).

More precisely HttpContext.GetEurl() (looks suspicious),
Request.InternalRewritePath(VirtualPath.Create(virtualPath), null, true) (safe),
VirtualPath.Create(virtualPath) (looks very suspicious),
virtualPath = UrlPath.FixVirtualPathSlashes(virtualPath); (the infamous!).

Let's write the stack trace that gets us here:

  • HttpRuntime.ProcessRequest... (multiple methods)
  • new HttpContext(wr, false)
  • this.Init(new HttpRequest(wr, this), new HttpResponse(wr, this));
  • if (!string.IsNullOrEmpty(eurl)) (can we prevent entering the if?)
  • this.Request.InternalRewritePath(VirtualPath.Create(virtualPath), null, true);
  • VirtualPath Create(string virtualPath)
  • unsafe static VirtualPath Create(string virtualPath, VirtualPathOptions options)

This last (unsafe) method is doing something to the path. First there is a loop over each character. If a char is below '.', and is different from '/' and is equal to '\', then flag = true. After the loop, if (flag) (src), then an exception may be thrown, and virtualPath = UrlPath.FixVirtualPathSlashes(virtualPath); (src).

Is seems for now that nothing will help us avoid going there (maybe the eurl thing?).

The string FixVirtualPathSlashes(string virtualPath) (src) replaces the backslashes into slashes and if removes the duplicate slashes. Shame.

What about the GetEurl method? When you read src, you find that is will not help you.

Conclusion

The http runtime is killing your backslashes for no documented reason. There is no way you can disable this behavior.

Workaround #1

Now, there must be a way. This guy referencing this page has a workaround. It seems that using the rewrite module, you can put the original URL back into the pipeline. I don't really like this solution because I don't know what's going on exactly. I have another idea...

I did not yet test this thing. Can you?

Searching for workaround #2 (none found)

What if there was a place where the original request path was stored?

Searching HttpRequest, none of the Url.OriginalString, RawUrl, Path, ServerVariables contains the desired value. Not even the _filePath, _path, _queryStringText, _rawUrl, _rewrittenUrl, _url private fields.

Search into the IIS7WorkerRequest, the value is already changed at runtime. I suspect IIS is doing the thing before pushing the request to the ASP.NET runtime. It looks like there is no hope.

like image 145
SandRock Avatar answered Oct 25 '22 07:10

SandRock