Двойная escape-последовательность внутри URL-адреса: модуль фильтрации запросов настроен на отказ в запросе, который содержит двойную escape-последовательность

В моем приложении ASP.NET MVC я пытаюсь реализовать URL-адрес, как показано ниже:

/продукт/теги/для + семей

Когда я пытаюсь запустить мое приложение с настройками по умолчанию, я получаю это сообщение с кодом ответа 404.11:

Ошибка HTTP 404.11 - не найдено

Модуль фильтрации запросов настроен на отказ от запроса, который содержит двойную escape-последовательность.

Я могу обойти эту ошибку, выполнив приведенный ниже код внутри моего web.config:

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

Итак, теперь я не получаю никаких 404.11.

Что мне интересно, так это то, какие ядро ​​безопасности я открываю с помощью этой реализации.

Кстати, мое приложение находится под .Net Framework 4.0 и работает под IIS 7.5.

Ответ 1

Случаи безопасности, которые вы можете открыть, связаны с инъекцией кода - инъекцией HTML, инъекцией JavaScript или инъекцией SQL.

Настройки по умолчанию защищают вас от атак полуэффективно, не позволяя общим стратегиям инъекций работать. Чем больше безопасности по умолчанию вы удаляете, тем больше вы должны думать о том, что вы делаете с помощью ввода, предоставляемого через URL-адреса, запросы запроса GET, данные запроса POST, заголовки HTTP и т.д....

Например, если вы создаете динамические SQL-запросы на основе параметра id вашего метода действий, например:

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

Вся идея заключается в том, чтобы рассматривать каждую часть URL-адресов и других материалов для методов действий как возможные угрозы. Установленный по умолчанию параметр безопасности предоставляет некоторую часть этой защиты для вас. Каждый параметр безопасности по умолчанию, который вы меняете, открывает немного больше потенциальной опасности, с которой вам нужно обращаться вручную.

Я предполагаю, что вы не создаете SQL-запросы таким образом. Но более проницательный материал приходит, когда вы храните пользовательский ввод в своей базе данных, а затем показываете их. Злонамеренный пользователь может хранить JavaScript или HTML в вашей базе данных, которые выходят из системы без права доступа, что в свою очередь угрожает другим пользователям вашей системы.

Ответ 2

Риск безопасности

Параметр allowDoubleEscaping применяется только к path (cs-uri-stem) и лучше всего объясняется с помощью двойного кодирования OWASP. Техника используется для обхода мер безопасности с помощью URL-кодирования запроса дважды. Используя ваш URL в качестве примера:

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

Предположим, существуют средства безопасности специально для /product/tags/for+families. /product/tags/for%252Bfamilies запрос для /product/tags/for%252Bfamilies который является тем же ресурсом, хотя и не /product/tags/for%252Bfamilies безопасности. Я использовал обобщенный термин управления безопасностью, потому что это может быть что угодно, например, требование аутентифицированного пользователя, проверка SQLi и т.д.

Почему IIS блокирует?

Знак плюс (+) является зарезервированным символом согласно RFC2396:

Многие URI включают компоненты, состоящие из определенных специальных символов или разделенные ими. Эти символы называются "зарезервированными", поскольку их использование в компоненте URI ограничено их зарезервированным назначением. Если данные для компонента URI будут конфликтовать с зарезервированной целью, то конфликтующие данные должны быть экранированы перед формированием URI.

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

У Уэйда Хилмо есть отличный пост под названием " Как IIS блокирует символы в URL". Там много информации и фона предоставлено. Часть специально для знака плюс выглядит следующим образом:

Так что allowDoubleEscaping/VerifyNormalization кажется довольно простым. Почему я сказал, что это вызывает путаницу? Проблема в том, что в URL появляется символ "+". Символ "+", по-видимому, не экранирован, поскольку не содержит символа "%". Кроме того, RFC 2396 отмечает его как зарезервированный символ, который может быть включен в URL, если он находится в экранированной форме (% 2b). Но если для allowDoubleEscaping установлено значение по умолчанию false, мы заблокируем его даже в экранированном виде. Причина этого историческая: на заре существования HTTP, символ '+ считался сокращением для пробела. Некоторые канонизаторы, если дать URL-адрес, содержащий '+, преобразуют его в пробел. По этой причине мы считаем '+ неканоническим в URL. Мне не удалось найти какую-либо ссылку на 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 к alwaysAllowedUrls на веб-сайте по умолчанию.

    appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"alwaysAllowedUrls.[url='/product/tags/for+families']"
    
  3. Переименовать - да, просто переименовать файл/папку/контроллер/и т.д. если возможно. Это самое простое решение.

Ответ 3

Я сделал работу вокруг этого. поэтому, когда вы хотите поместить зашифрованную строку в URL для (IIS), вы должны очистить ее от грязного: {";", "/", "?", ":", "@", "&", " = "," + "," $ ",", "}; и когда вы хотите расшифровать его и использовать снова, вы должны снова испачкать его перед расшифровкой (чтобы получить желаемый результат).

Вот мой код, надеюсь, он кому-нибудь поможет:

 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

Поэтому я столкнулся с этим, когда я вызывал API из приложения MVC. Вместо того чтобы открыть отверстие для безопасности, я изменил свой путь.

Во-первых, я рекомендую НЕ отключить этот параметр. Уместнее изменить дизайн приложения/ресурса (например, закодировать путь, передать данные в заголовке или в теле).

Хотя это более старая должность, я думал, что поделился бы тем, как вы могли бы разрешить эту ошибку, если вы получаете это от вызова API, используя метод HttpUtility.UrlPathEncode в System.Web.

Я использую RestSharp для совершения вызовов, поэтому в моем примере используется RestRequest:

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

Это создает путь, равный:

/продукта/теги/для% 2Bfamilies

В другой заметке НЕ создавайте динамический запрос на основе пользовательских входов. Вы ДОЛЖНЫ всегда использовать SqlParameter. Кроме того, чрезвычайно важно с точки зрения безопасности возвращать значения с соответствующей кодировкой для предотвращения инъекционных атак.

~ Приветствия

Ответ 5

Альтернативная причина этой ошибки: убедитесь, что вы ожидаете асинхронных методов!

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]]

Когда я звонил return RedirectToAction(nameof(GrandParents), new { id = grandParentId });, то, что должно было быть Guid, не было, потому что звонок для получения Guid aka 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);
}