Url 中的双转义序列: 请求过滤模块被配置为拒绝包含双转义序列的请求

在我的 ASP.NET MVC 应用程序中,我试图实现如下 URL:

+ 家庭/产品/标签/

当我尝试使用默认配置运行我的应用程序时,我得到的是404.11响应代码:

HTTP 错误404.11-找不到

请求过滤模块配置为拒绝以下请求 包含一个双转义序列。

我可以通过在 web.config 中实现以下代码来避免这个错误:

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

所以,现在我没有得到任何 404.11

我想知道的是,我用这个实现打开了什么样的安全漏洞。

顺便说一下,我的应用程序在 .Net Framework 4.0下运行,在 IIS 7.5下运行。

100086 次浏览

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.

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.

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']"
  1. Rename - yes, just rename the file/folder/controller/etc. if possible. This is the easiest solution.

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;
}

Encode the encrypted string separated:

return HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes("string"));

Decode the encrypted string separated:

string x = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(id));