double escape sequence inside a url : The request

2020-01-30 06:37发布

问题:

On my ASP.NET MVC application, I am trying to implement a URL like below :

/product/tags/for+families

When I try to run my application with default configurations, I am getting this message with 404.11 Response Code :

HTTP Error 404.11 - Not Found

The request filtering module is configured to deny a request that contains a double escape sequence.

I can get around with this error by implementing the below code inside my web.config :

  <system.webServer>
    <security>
      <requestFiltering allowDoubleEscaping="true" />
    </security>
  </system.webServer>

So, now I am not getting any 404.11.

What I am wondering is that what kind of security holes I am opening with this implementation.

BTW, my application is under .Net Framework 4.0 and running under IIS 7.5.

回答1:

The security holes that you might open up have to do with code injection - HTML injection, JavaScript injection or SQL injection.

The default settings protect you from attacks semi-efficiently by not allowing common injection strategies to work. The more default security you remove, the more you have to think about what you do with the input provided through URLs, GET request querystrings, POST request data, HTTP headers and so on...

For instance, if you are building dynamic SQL queries based on the id parameter of your action method, like this:

public ActionResult Tags(string id)
{
    var sql = "SELECT * FROM Tags Where tagName = '" + id + "'";
    // DO STUFF...
}

(...which is NOT a good idea), the default protection, put in place by the .NET framework, might stop some of the more dangerous scenarios, like the user requesting this URL:

/product/tags/1%27;drop%20table%20Tags;%20--

The whole idea is to treat every part of urls and other inputs to action methods as possible threats. The default security setting does provide some of that protection for you. Each default security setting you change opens up for a little more potential badness that you need to handle manually.

I assume that you are not building SQL queries this way. But the more sneaky stuff comes when you store user input in your database, then later displaying them. The malevolent user could store JavaScript or HTML in your database that go out unencoded, which would in turn threaten other users of your system.



回答2:

Security Risk

The setting allowDoubleEscaping only applies to the path (cs-uri-stem) and is best explained by OWASP Double Encoding. The technique is used to get around security controls by URL Encoding the request twice. Using your URL as an example:

/product/tags/for+families --> /product/tags/for%2Bfamilies --> /product/tags/for%252Bfamilies

Suppose there are security controls specifically for /product/tags/for+families. A request arrives for /product/tags/for%252Bfamilies which is the same resource though is unchecked by the aforementioned security controls. I used the generalized term of security controls because they could be anything such as requiring an authenticated user, checking for SQLi, etc.

Why Does IIS Block?

The plus sign (+) is a reserved character per RFC2396:

Many URI include components consisting of or delimited by, certain special characters. These characters are called "reserved", since their usage within the URI component is limited to their reserved purpose. If the data for a URI component would conflict with the reserved purpose, then the conflicting data must be escaped before forming the URI.

  reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
                "$" | ","

Wade Hilmo has an excellent post titled How IIS blocks characters in URLs. There's lots of information and background provided. The part specifically for the plus sign is as follows:

So allowDoubleEscaping/VerifyNormalization seems pretty straightforward. Why did I say that it causes confusion? The issue is when a ‘+’ character appears in a URL. The ‘+’ character doesn’t appear to be escaped, since it does not involve a ‘%’. Also, RFC 2396 notes it as a reserved character that can be included in a URL when it’s in escaped form (%2b). But with allowDoubleEscaping set to its default value of false, we will block it even in escaped form. The reason for this is historical: Way back in the early days of HTTP, a ‘+’ character was considered shorthand for a space character. Some canonicalizers, when given a URL that contains a ‘+’ will convert it to a space. For this reason, we consider a ‘+’ to be non-canonical in a URL. I was not able to find any reference to a RFC that calls out this ‘+’ treatment, but there are many references on the web that talk about it as a historical behavior.

From my own experience I know that when IIS logs a request spaces are substituted with a plus sign. Having a plus sign in the name may cause confusion when parsing logs.

Solution

There are three ways to fix this and two ways to still use the plus sign.

  1. allowDoubleEscaping=true - This will allow double escaping for your entire website/application. Depending on the content, this could be undesirable to say the least. The following command will set allowDoubleEscaping=true.

    appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /allowDoubleEscaping:True
    
  2. alwaysAllowedUrls - Request Filtering offers a whitelist approach. By adding that URL path to alwaysAllowedUrls, the request will not be checked by any other Request Filtering settings and continue on in the IIS request pipeline. The concern here is that Request Filtering will not check the request for:

    • Request Limits: maxContentLength, maxUrl, maxQueryString
    • Verbs
    • Query - query string parameters will not be checked
    • Double Escaping
    • High Bit Characters
    • Request Filtering Rules
    • Request Header Limits

    The following command will add /product/tags/for+families to alwaysAllowedUrls on the Default Web Site.

    appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"alwaysAllowedUrls.[url='/product/tags/for+families']"
    
  3. Rename - yes, just rename the file/folder/controller/etc. if possible. This is the easiest solution.



回答3:

I have made a work arround for this . so when you want to put the encripted string inside the url for(IIS) you have to clean it from dirty :{ ";", "/", "?", ":", "@", "&", "=", "+", "$", "," }; and when you want to decript it and use it again , you have to make it dirty again before decript it (to get the wanted result) .

Here is my code , i hope it helps somebody :

 public static string cleanUpEncription(string encriptedstring)
        {
            string[] dirtyCharacters = { ";", "/", "?", ":", "@", "&", "=", "+", "$", "," };
            string[] cleanCharacters = { "p2n3t4G5l6m","s1l2a3s4h","q1e2st3i4o5n" ,"T22p14nt2s", "a9t" , "a2n3nd","e1q2ua88l","p22l33u1ws","d0l1ar5","c0m8a1a"};

            foreach (string dirtyCharacter in dirtyCharacters)
            {
                encriptedstring=encriptedstring.Replace(dirtyCharacter, cleanCharacters[Array.IndexOf(dirtyCharacters, dirtyCharacter)]);
            }
            return encriptedstring;
        }

        public static string MakeItDirtyAgain(string encriptedString)
        {
            string[] dirtyCharacters = { ";", "/", "?", ":", "@", "&", "=", "+", "$", "," };
            string[] cleanCharacters = { "p2n3t4G5l6m", "s1l2a3s4h", "q1e2st3i4o5n", "T22p14nt2s", "a9t", "a2n3nd", "e1q2ua88l", "p22l33u1ws", "d0l1ar5", "c0m8a1a" };
            foreach (string symbol in cleanCharacters)
            {
                encriptedString = encriptedString.Replace(symbol, dirtyCharacters[Array.IndexOf(cleanCharacters,symbol)]);
            }
            return encriptedString;
        }


回答4:

So I ran into this when I was calling an API from an MVC app. Instead of opening the security hole, I modified my path.

First off, I recommend NOT disabling this setting. It is more appropriate to modify the design of the application/resource (e.g. encode the path, pass the data in a header or in the body).

Although this is an older post, I thought I would share how you could resolve this error if you are receiving this from a call to an API by using HttpUtility.UrlPathEncode method in System.Web.

I use RestSharp for making calls out, so my example is using the RestRequest:

var tags = new[] { "for", "family" };
var apiRequest = new RestRequest($"product/tags/{HttpUtility.UrlPathEncode(string.Join("+", tags))}");

This produces a path equal to:

/product/tags/for%2Bfamilies

On another note, do NOT build a dynamic query based on a user's inputs. You SHOULD always use a SqlParameter. Also, it is extremely important from a security perspective to return the values with the appropriate encoding to prevent injection attacks.

~Cheers



回答5:

Alternative reason for this error: Make sure you are awaiting your async methods!

Requested URL
http://localhost:XXXXX/{controller}/{action}/System.Runtime.CompilerServices.AsyncTaskMethodBuilder'1+AsyncStateMachineBox'1[System.Guid,Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler+d__21'1[System.Guid]]

Physical Path
C:{project}\models{specific model}\System.Runtime.CompilerServices.AsyncTaskMethodBuilder'1+AsyncStateMachineBox'1[System.Guid,Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler+d__21'1[System.Guid]]

When I was calling return RedirectToAction(nameof(GrandParents), new { id = grandParentId }); what should have been a Guid wasn't, because the call to get the Guid aka grandParentId wasn't awaited

Sample code below:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateThing(ThingViewModel thingVM)
{
  if (ModelState.IsValid)
  { //I forgot to await here, which wasn't the worst thing...
    var toCreate = await CreateThingAsync(thingVM);

    if(toCreate == null){ return View(thingVM); }

    // I forgot to await here too, so instead of returning a Guid, it returned
    // System.Runtime.CompilerServices.AsyncTaskMethodBuilder'1+AsyncStateMachineBox'1[System.Guid...
    var grandParentId = await _context.ParentThings.AsNoTracking().Where(x => x.Id == thingVM.ParentId).Select(y => y.ParentId).FirstOrDefaultAsync();

    // So this call was unhappy
    return RedirectToAction(nameof(GrandParents), new { id = grandParentId });
  }
  return View(thingVM);
}