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

时间:2011-10-12 11:18:44

标签: asp.net asp.net-mvc asp.net-mvc-3 iis iis-7

在我的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下运行。

6 个答案:

答案 0 :(得分:50)

您可能打开的安全漏洞与代码注入有关 - HTML注入,JavaScript注入或SQL注入。

默认设置通过不允许常用注入策略工作来半保护地免受攻击。您删除的默认安全性越多,您就越需要考虑通过URL提供的输入,GET请求查询字符串,POST请求数据,HTTP标头等等...

例如,如果要根据操作方法的id参数构建动态SQL查询,请执行以下操作:

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

(... 一个好主意),.NET框架实施的默认保护可能会阻止一些更危险的方案,例如请求此URL的用户:

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

整个想法是将网址的每个部分和行动方法的其他输入视为可能的威胁。默认安全设置确实为您提供了一些保护。您更改的每个默认安全设置都会打开您需要手动处理的更多潜在危害。

我假设您没有以这种方式构建SQL查询。但是,当您将用户输入存储在数据库中,然后显示它们时,会出现更多偷偷摸摸的东西。恶意用户可以在您的数据库中存储未编码的JavaScript或HTML,这反过来会威胁到系统的其他用户。

答案 1 :(得分:2)

安全风险

设置allowDoubleEscaping仅适用于path(cs-uri-stem),最好用OWASP Double Encoding来解释。该技术用于通过两次对请求进行URL编码来避开安全控件。以您的网址为例:

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

假设有专门针对/product/tags/for+families的安全控制。尽管没有被上述安全控件检查,但到达/product/tags/for%252Bfamilies的请求是相同的资源。我之所以使用安全控制的广义术语,是因为它们可以是诸如要求经过身份验证的用户,检查SQLi等之类的任何东西。

为什么IIS会阻止?

加号(+)是RFC2396的保留字符:

  

许多URI包含由某些内容组成或由某些内容界定的组件      特殊的角色。这些字符被称为“保留”,因为      它们在URI组件中的使用仅限于保留      目的。如果URI组件的数据与      保留目的,那么必须先对冲突的数据进行转义      形成URI。

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

韦德·希尔莫(Wade Hilmo)的出色帖子标题为How IIS blocks characters in URLs。提供了很多信息和背景。专门用于加号的部分如下:

  

因此allowDoubleEscaping / VerifyNormalization看起来非常简单。为什么我说这会引起混乱?问题是URL中出现“ +”字符时。 “ +”字符似乎并未转义,因为它不包含“%”。另外,RFC 2396将该字符记为保留字符,当它以转义形式(%2b)可以包含在URL中。但是,在allowDoubleEscaping设置为默认值false的情况下,即使以转义的形式我们也将阻止它。原因是历史性的:早在HTTP早期,“ +”字符就被认为是空格字符的简写。一些规范化工具在提供包含“ +”的网址时会将其转换为空格。因此,我们认为“ +”在网址中不规范。我找不到任何引用这种RFC的引用,但是网络上有很多引用都把它称为历史行为。

根据我的经验,我知道当IIS记录请求时,请求空间将被加号代替。在解析日志时,名称中带有加号可能会引起混淆。

解决方案

有三种方法可以解决此问题,还有两种方法仍可以使用加号。

  1. allowDoubleEscaping=true-这将允许整个网站/应用程序两次转义。至少根据内容,这可能是不希望的。以下命令将设置allowDoubleEscaping=true

    appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /allowDoubleEscaping:True
    
  2. alwaysAllowedUrls-请求过滤提供了一种白名单方法。通过将该URL路径添加到alwaysAllowedUrls中,该请求将不会被任何其他“请求筛选”设置检查并继续在IIS请求管道中继续。这里的问题是请求过滤将不会检查以下请求:

    • 请求限制:maxContentLength,maxUrl,maxQueryString
    • 动词
    • 查询-将不检查查询字符串参数
    • 双重转义
    • 高位字符
    • 请求过滤规则
    • 请求标头限制

    以下命令会将/product/tags/for+families添加到默认网站上的alwaysAllowedUrls

    appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"alwaysAllowedUrls.[url='/product/tags/for+families']"
    
  3. 重命名-是的,只需重命名文件/文件夹/控制器/等。如果可能的话。这是最简单的解决方案。

答案 2 :(得分:0)

因此,当我从MVC应用程序调用API时遇到了这个问题。我没有打开安全漏洞,而是修改了路径。

首先,我建议不要禁用此设置。修改应用程序/资源的设计(例如,对路径进行编码,将数据传递到标头或正文中)更合适。

尽管这是一篇较旧的文章,但我想我将分享如何使用HttpUtility.UrlPathEncode中的System.Web方法从对API的调用中接收到此错误的方法。

我使用RestSharp进行呼叫,所以我的示例使用RestRequest:

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

这将产生一条等于:

的路径。
  

/ product / tags / for%2Bfamilies

另一方面,请勿基于用户的输入来构建动态查询。您应该始终使用SqlParameter。另外,从安全角度来看,使用适当的编码返回值以防止注入攻击也非常重要。

〜干杯

答案 3 :(得分:0)

我为此做了很多工作。 因此,当您要将标记的字符串放在(IIS)的url中时 您必须将其清理干净:{“;”,“ /”,“?”,“:”,“ @”,“&”,“ =”,“ +”,“ $”,“,”}; 当您想对其进行抄写并再次使用时,必须在再次抄写之前使其变脏(以获得所需的结果)。

这是我的代码,希望对您有所帮助:

 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 :(得分:0)

此错误的替代原因:确保您正在等待异步方法!

  

请求的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]]

     

物理路径
  C:{项目} \模型{特定模型} \ System.Runtime.CompilerServices.AsyncTaskMethodBuilder'1 + AsyncStateMachineBox'1 [System.Guid,Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler + d__21'1 [System.Guid]] < / p>

当我打电话给return RedirectToAction(nameof(GrandParents), new { id = grandParentId });时,并没有得到应有的Guid,因为没有等待获得Guid的电话,即grandParentId

下面的示例代码:

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

答案 5 :(得分:0)

对加密的字符串进行编码:

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

解码分隔的加密字符串:

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