Как простая обработка XML-файлов может стать дефектом безопасности? Каким образом блог, развёрнутый на вашей машине, может стать причиной утечки данных? Сегодня мы ответим на эти вопросы и разберём, что такое XXE и как эта уязвимость выглядит в теории и на практике.


0918_XXE_BlogEngine_ru/image1.png


Сразу хочется отметить, что возможных типов уязвимостей, связанных с обработкой XML, несколько. Наиболее популярными, пожалуй, являются XXE, XEE, XPath injection. Сегодня мы поговорим про XXE. Если вам интересно почитать, в чём суть XEE, предлагаю ознакомиться со статьёй "Как Visual Studio 2022 съела 100 Гб памяти и при чём здесь XML бомбы?". До XPath injection доберёмся как-нибудь в следующий раз. :)


Что такое XXE?


XXE (акроним от XML eXternal Entities) – дефект безопасности приложения, который может образоваться в результате парсинга скомпрометированных данных небезопасно сконфигурированным XML-парсером. Последствием атаки может быть, например, раскрытие данных с целевой машины или SSRF (server-side request forgery).


Стандарт XML предусматривает возможность использования DTD (document type definition), описывающего структуру XML-документа. DTD даёт нам возможность определять и использовать XML-сущности.


Выглядеть это может, например, так:


<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE order [
  <!ENTITY myEntity "lol">
]>
<order>&myEntity;</order>

В этом XML мы определили сущность myEntity, которую дальше используем – &myEntity;. В данном случае сущность является внутренней и определена как литерал. Если XML-парсер выполнит раскрытие этой сущности, то вместо &myEntity; будет подставлено фактическое значение – lol. Кроме того, внутренние сущности могут раскрываться через другие. Таким образом могут создаваться XML-бомбы и проводиться XEE-атаки.


Однако сущности могут быть и внешними. Они могут ссылаться на какие-то локальные файлы или обращаться к внешним ресурсам:


<!ENTITY myExternalEntity SYSTEM "https://test.com/target.txt">

Пример XML-файла, в котором внешняя сущность ссылается на локальный файл:


<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE order [
  <!ENTITY myExternalEntity SYSTEM "file:///D:/HelloWorld.cs">
]>
<order>&myExternalEntity;</order>

В данном случае XML-парсер вместо сущности myExternalEntity подставит содержимое файла D:/HelloWorld.cs. При условии, что он сконфигурирован соответствующим образом, конечно.


Суть XXE-атаки заключается в том, чтобы эксплуатировать описанную выше особенность.


Разберём на примере. Предположим, что есть приложение, которое принимает запросы в виде XML-файлов и обрабатывает товары с соответствующим идентификатором.


Формат XML-файла, с которым работает приложение:


<?xml version="1.0" encoding="utf-8" ?>
<order>
  <itemID>62</itemID>
</order>

Упрощённый C# код:


static void ProcessItemWithID(XmlReader reader, String pathToXmlFile)
{
  ....
  while (reader.Read())
  {
    if (reader.Name == "itemID")
    {
      var itemIdStr = reader.ReadElementContentAsString();
      if (long.TryParse(itemIdStr, out var itemIdValue))
      {
        // Process item with the 'itemIdValue' value
        Console.WriteLine(
          $"An item with the '{itemIdValue}' ID was processed.");
      }
      else
      {
        Console.WriteLine($"{itemIdStr} is not valid 'itemID' value.");
      }
    }
  }
}

Логика тривиальна:


  • если ID является числом, приложение сообщит о том, что соответствующий товар был обработан;
  • если ID не является числом, приложение сообщит об ошибке.

Соответственно, для приведённого ранее XML-файла приложение распечатает следующую строку:


An item with the '62' ID was processed.

Если вместо номера в ID будет записано что-то другое (например, строка "Hello world"), приложение сообщит об ошибке:


"Hello world" is not valid 'itemID' value.

Если XML-парсер (reader), который обрабатывает файлы, разбирает внешние сущности, возникает дефект безопасности. Ниже представлен XML-файл, с помощью которого можно скомпрометировать приложение:


<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE order [
  <!ENTITY xxe SYSTEM "file:///D:/MySecrets.txt">
]>
<order>
  <itemID>&xxe;</itemID>
</order>

В этом файле объявляется внешняя сущность xxe. При обработке вместо &xxe; будет подставлено содержимое файла D:/MySecrets.txt (например, такое: "This is an XXE attack target."). Соответственно, вывод приложения будет следующим:


"This is an XXE attack target." is not valid 'itemID' value.

Получается, что приложение уязвимо к XXE, если:


  • XML-парсер настроен на разбор внешних сущностей и не обрабатывает их безопасным образом;
  • злоумышленник имеет возможность подать на вход парсеру скомпрометированные данные прямо или косвенно.

Если подставленное значение сущности как-то возвращается обратно злоумышленнику, он может получать содержимое файлов со скомпрометированного устройства. Понятно, что это уже опасно. Дополнительная же опасность состоит в том, что таким образом можно попробовать собрать больше данных о системе в целом и, как следствие, найти другие дефекты безопасности.


Кроме того, XXE может являться мостиком к SSRF-атаке. Суть в том, что у самого хакера может не быть доступа к каким-то ресурсам (ограничение доступа для внешних пользователей), но они могут быть у эксплуатируемого приложения. Так как XXE позволяет выполнять запросы и по сети, скомпрометированное приложение является брешью в защите ресурсов от внешнего мира.


Говоря про важность и опасность XXE следует вспомнить о том, что этот дефект безопасности часто фигурирует в различных стандартах, топах и перечислениях.


CWE


В Common Weakness Enumeration для XXE есть отдельная запись: CWE-611: Improper Restriction of XML External Entity Reference.


CWE Top 25


Каждый год из CWE отбираются 25 наиболее распространённых и опасных дефектов безопасности, из которых составляется CWE Top 25.


В 2021 году XXE потеряла 4 позиции по сравнению с 2020-ым, но всё ещё осталась в топе, обосновавшись на 23-ем месте.


OWASP ASVS


В OWASP ASVS (Application Security Verification Standard) приведён список требований к безопасной разработке. Тему XXE в нём также не обошли стороной: OWASP ASVS 4.0.3 (ID 5.5.2): Verify that the application correctly restricts XML parsers to only use the most restrictive configuration possible and to ensure that unsafe features such as resolving external entities are disabled to prevent XML eXternal Entity (XXE) attacks.


OWASP Top 10


В OWASP Top 10 2017 для XXE была выделена отдельная категория: A4:2017-XML External Entities (XXE). В OWASP Top 10 2021 отдельная категория для XXE была устранена, и теперь XXE входит в категорию A05:2021-Security Misconfiguration.


0918_XXE_BlogEngine_ru/image2.png


Составляющие XXE в C#


Как мы писали выше, для осуществления XXE нужно минимум 2 составляющих: опасно сконфигурированный парсер и данные от злоумышленника, которые он обрабатывает.


Небезопасные данные


В принципе, здесь всё достаточно просто – часто в приложении есть несколько точек, в которых оно принимает внешние данные. Обрабатывать такие данные нужно со всей строгостью, так как не все пользователи используют ваше приложение по назначению и так, как вы задумывали.


Подобными точками входа являются аргументы консольного приложения, различные поля форм, данные запросов и тому подобное. Первое, самое простое и очевидное, что приходит в голову – консольный ввод.


var taintedVar = Console.ReadLine();

Мы не знаем, что хранится в taintedVar – лежат ли там данные в ожидаемом формате или же переменная содержит какую-нибудь строку для компрометации системы. Доверия к ней быть не должно.


Немного более подробно эта тема раскрыта в статье "OWASP, уязвимости и taint анализ в PVS-Studio C#. Смешать, но не взбалтывать" в разделе "Taint sources". Также с подозрением стоит относиться к параметрам публично-доступных методов. Сами по себе данные в них могут быть как безопасными, так и не очень. Про это писали здесь.


XML-парсеры


Для того чтобы парсер был уязвимым к XXE, он должен:


  • обрабатывать DTD;
  • использовать небезопасный XmlResolver.

Если в XML-парсере не задано ограничение на максимальный размер сущностей (или этот размер велик), это может усилить негативные последствия атаки, так как позволит злоумышленнику извлекать данные большего объёма.


Настройки парсинга


Интересующее нас поведение задаётся с помощью следующих свойств:


  • ProhibitDtd;
  • DtdProcessing;
  • XmlResolver;
  • MaxCharactersFromEntities.

В одних XML-парсерах можно встретить все эти опции, в других – только некоторые. Их смысловое значение от типа к типу не изменяется.


ProhibitDtd


Свойство ProhibitDtd декорировано атрибутом Obsolete и на замену ему пришло свойство DtdProcessing. Тем не менее, оно всё так же может использоваться в старом коде. Значение true запрещает обработку DTD, false – разрешает.


DtdProcessing


Свойство DtdProcessing имеет тип System.Xml.DtdProcessing и может принимать значения Prohibit, Ignore и Parse:


  • Prohibit – запрещает обработку DTD. В случае, если при разборе XML-файла парсер встретит DTD, будет выброшено исключение типа XmlException;
  • Ignore – парсер просто пропустит DTD;
  • Parse – парсер будет выполнять разбор DTD.

Сразу отвечу на возможный вопрос. Свойства ProhibitDtd и DtdProcessing, если они встречаются вместе (например, в XmlReaderSettings) связаны друг с другом. Так что, даже если вы запретите обработку DTD в одном свойстве и разрешите в другом, актуальной будет только последняя выставленная опция.


XmlResolver


Свойство XmlResolver отвечает за то, какой объект используется для обработки внешних сущностей. Самый безопасный вариант – отсутствие резолвера вовсе (значение null). В таком случае, даже если включена обработка DTD, внешние сущности раскрываться не будут.


MaxCharactersFromEntities


Ещё одна интересующая нас опция – MaxCharactersFromEntities – отвечает за максимально допустимый размер сущностей. Чем больше значение, тем потенциально больший объём информации получится извлечь при проведении XXE-атаки.


Типы XML-парсеров


Пожалуй, наиболее распространёнными стандартными типами, с которыми можно наткнуться на XXE, являются XmlReader, XmlTextReader, XmlDocument. Однако призываю помнить, что ими список не ограничивается.


Ещё раз подчеркну, что опасной считается конфигурация парсера, при которой он:


  • обрабатывает DTD;
  • имеет опасный резолвер (например, XmlUrlResolver в его дефолтном состоянии).

XmlReader


За поведение XmlReader отвечает объект XmlReaderSettings, создаваемый явно или неявно. Тип XmlReaderSettings содержит все настройки, перечисленные нами ранее.


Парсер с опасной конфигурацией может выглядеть так:


var settings = new XmlReaderSettings()
{
  DtdProcessing = DtdProcessing.Parse,
  XmlResolver = new XmlUrlResolver(),
  MaxCharactersFromEntities = 0
};

using (var xmlReader = XmlReader.Create(xmlFileStringReader, settings))
  ....

Здесь разработчик явно разрешил обработку DTD, установил резолвер внешних сущностей, ещё и ограничение на их размер снял.


XmlTextReader


В случае с этим типом мы имеем дело всё с теми же знакомыми свойствами: ProhibitDtd, DtdProcessing, XmlResolver.


Пример парсера с опасной конфигурацией:


using (var xmlTextReader = new XmlTextReader(xmlFileStringReader))
{
  xmlTextReader.XmlResolver = new XmlUrlResolver();
  xmlTextReader.DtdProcessing = DtdProcessing.Parse;
  ....
}

XmlDocument


В случае с типом XmlDocument единственное интересующее нас свойство — XmlResolver. Парсер с опасной конфигурацией в данном случае может выглядеть так:


XmlDocument xmlDoc = new XmlDocument();
xmlDoc.XmlResolver = new XmlUrlResolver();

xmlDoc в такой конфигурации будет раскрывать внешние сущности, а значит, может считаться опасным.


Настройки парсеров по умолчанию


Выше мы рассматривали примеры, где настройки XML-парсеров задавались явно. Однако все перечисленные типы имеют какие-то настройки по умолчанию, и с ними есть пара интересных моментов.


Первый – в разных версиях .NET эти настройки могут отличаться.


Второй – настройки отличаются от типа к типу. Например, где-то обработка DTD по умолчанию будет выключена, где-то – включена.


То есть в определённых случаях XML-парсер может иметь опасную конфигурацию по умолчанию, даже если опасные настройки не прописаны явно.


Если совместить всё это — разные типы парсеров, отличие настроек по умолчанию в разных типах и версиях .NET — получаем неплохой такой объём информации, который может быть сложно удержать в голове (особенно поначалу).


Выходит, смотря только на код, порой нельзя сказать, сконфигурирован ли XML-парсер устойчивым к XXE или нет:


XmlDocument doc = new XmlDocument();
doc.Load(xmlReader);

Из этого кода непонятно, может ли doc обрабатывать внешние сущности или нет – нужно знать информацию о версии фреймворка.


Значения 'опасных' настроек поменялись между версиями .NET Framework 4.5.1 и .NET Framework 4.5.2. Ниже привожу таблицу, в которой указано, в каких версиях .NET парсеры с настройками по умолчанию устойчивы к XXE, в каких – нет.


Экземпляры типов .NET Framework 4.5.1 и ниже .NET Framework 4.5.2 и выше (включая .NET Core и .NET)
XmlReader (XmlReaderSettings) Safe Safe
XmlTextReader Vulnerable Safe
XmlDocument Vulnerable Safe

Да, XmlReader (созданный на основе настроек – XmlReaderSettings) безопасен в .NET Framework 4.5.1 и ниже за счёт того, что в нём выключена обработка DTD.


Несмотря на то, что в новых версиях фреймворка по умолчанию парсеры настроены безопасно, лучшим решением может быть всё же явно прописывать необходимые настройки. Да, кода станет немного больше, но в то же время он будет более очевиден и устойчив при переносе между разными версиями .NET Framework.


Итак, с базовой теорией разобрались. Дальше мы посмотрим на реальную уязвимость, но перед этим предлагаю заварить чашечку кофе.


Пример уязвимости в BlogEngine.NET


Выше мы разобрали теоретическую составляющую XXE, чуть более конкретно поговорили про эти дефекты безопасности в рамках .NET, рассмотрели, как выглядят небезопасные составляющие уязвимости с точки зрения кода. Теперь пришло время перейти к практике. В этом нам поможет BlogEngine.NET.


0918_XXE_BlogEngine_ru/image3.png


Описание проекта с сайта: BlogEngine is an open source blogging platform since 2007. Easily customizable. Many free built-in Themes, Widgets, and Plugins.


Исходный код проекта доступен на GitHub.


Для нас этот проект интересен в первую очередь тем, что в нём было найдено целых 3 уязвимости XXE. Все они были исправлены в BlogEngine.NET v3.3.8.0. Значит, мы для экспериментов возьмём предыдущую версию проекта – v3.3.7.0. При желании вы можете повторить описанные шаги и самостоятельно 'пощупать' реальную XXE.


Для начала выгружаем соответствующую версию проекта — v3.3.7.0. Со сборкой проекта никаких проблем возникнуть не должно – она тривиальна. Я собирал через Visual Studio 2022.


После сборки запускаем на исполнение проект BlogEngine.NET, и если всё удалось, то увидим сайт примерно следующего вида:


0918_XXE_BlogEngine_ru/image4.png


Если сайт не будет доступен для других машин в той же сети "из коробки", я бы посоветовал немножко заморочиться и настроить это – так XXE 'щупать' будет интереснее.


При поиске уязвимостей у вас могут быть разные входные данные. Например, система может представлять для вас чёрный ящик, и тогда нужно будет собирать сведения о системе, искать точки воздействия на неё и тому подобное. Если же система представляет собой белый ящик, это меняет подход и используемые для достижения цели инструменты (или как минимум расширяет их список).


С Open Source проектами интересная штука выходит, как мне кажется. Вроде бы любой заинтересованный может работать с кодом и внести свой вклад в качество / безопасность. Однако с этим есть нюансы. С другой стороны, больше карт в руки попадёт и злоумышленникам – так как есть доступ к исходному коду, находить уязвимости должно быть легче. Вот только будут ли они зарепорчены?


Но оставим эти философские рассуждения и вернёмся к нашему делу.


Так как проект имеет открытый исходный код, воспользуемся этим преимуществом. Для поиска дефектов безопасности кроме собственных знаний вооружимся PVS-Studio (решение для поиска ошибок и дефектов безопасности). Нам понадобится группа диагностик, связанных с security, – OWASP. О том, как включить соответствующие предупреждения, можно почитать здесь.


В Visual Studio достаточно выставить значение "Show All" для группы OWASP на вкладке "Detectable Errors (C#)": Extensions > PVS-Studio > Options > Detectable Errors (C#).


0918_XXE_BlogEngine_ru/image5.png


После этого нужно убедиться, что у вас включено отображение соответствующих предупреждений. В данном случае нас интересуют предупреждения группы 'OWASP' уровня достоверности 'High'. Следовательно, необходимые кнопки должны быть нажаты (обведены рамочкой).


0918_XXE_BlogEngine_ru/image6.png


Далее нужно запустить анализ решения (Extensions > PVS-Studio > Check > Solution) и дождаться результатов.


По CWE (помним, что XXE соответствует CWE-611) или OWASP ASVS ID (OWASP ASVS 5.5.2) легко найти то, что нас интересует, – 3 предупреждения V5614.


0918_XXE_BlogEngine_ru/image7.png


С точки зрения кода проблемы похожи. Мы разберём наиболее интересную (разнесённую по нескольким методам), а по остальным я просто предоставлю основную информацию.


XMLRPCRequest.cs


Предупреждение: V5614 [CWE-611, OWASP-5.5.2] Potential XXE vulnerability inside method. Insecure XML parser is used to process potentially tainted data from the first argument: 'inputXml'. BlogEngine.Core XMLRPCRequest.cs 41


На самом деле анализатор указывает на 3 строки для того, чтобы предупреждение было легче понять: 'опасный' вызов метода, источник потенциально скомпрометированных данных и место их использования опасно сконфигурированным парсером.


public XMLRPCRequest(HttpContext input)
{
  var inputXml = ParseRequest(input);

  // LogMetaWeblogCall(inputXml);
  this.LoadXmlRequest(inputXml); // Loads Method Call 
                                 // and Associated Variables
}

Из сообщения следует, что inputXml может содержать 'заражённые' данные (см. taint checking), которые используются небезопасно сконфигурированным парсером внутри метода LoadXmlRequest. Таким образом здесь получился достаточно комплексный 'межпроцедурный' кейс: данные приходят из одного метода (ParseRequest) и затем передаются в другой (LoadXmlRequest), где уже и используются.


Начнём с данных – для этого нам понадобится код метода ParseRequest.


private static string ParseRequest(HttpContext context)
{
  var buffer = new byte[context.Request.InputStream.Length];

  context.Request.InputStream.Position = 0;
  context.Request.InputStream.Read(buffer, 0, buffer.Length);

  return Encoding.UTF8.GetString(buffer);
}

Сопроводим код трассой распространения заражения, чтобы было более очевидно, о чём речь.


0918_XXE_BlogEngine_ru/image8.png


Начинается всё со свойства context.Request, имеющего тип HttpRequest. Анализатор считает его источником заражения, так как данные, поступившие как запрос, могут быть скомпрометированы.


Есть различные способы эти данные извлечь, и работа с потоком (свойство InputStream) – один из них. Следовательно, taint-статус 'переходит' и на InputStream.


Далее для этого потока вызывается метод System.IO.Stream.Read, который считывает данные из потока InputStream в массив байт – buffer. Как следствие, buffer теперь тоже может содержать заражённые данные.


После этого вызывается метод Encoding.UTF8.GetString, который конструирует строку из массива байт (buffer). Так как исходные данные для создания строки заражены, сама строка также будет заражена. После конструирования строка возвращается из метода.


Выходит, что возвращаемое методом ParseRequest значение может быть скомпрометировано злоумышленником. По крайней мере в теории.


Вернёмся к исходному методу:


public XMLRPCRequest(HttpContext input)
{
  var inputXml = ParseRequest(input);

  // LogMetaWeblogCall(inputXml);
  this.LoadXmlRequest(inputXml); // Loads Method Call 
                                 // and Associated Variables
}

Мы разобрались с ParseRequest. Предположим, что переменная inputXml действительно может содержать заражённые данные. Следующий шаг – проанализировать метод LoadXmlRequest, принимающий inputXml в качестве аргумента.


Сам метод достаточно большой (больше 100 строк), так что приведём его сокращённую версию и отметим место, на которое указывает анализатор.


private void LoadXmlRequest(string xml)
{
  var request = new XmlDocument();
  try
  {
    if (!(xml.StartsWith("<?xml") || xml.StartsWith("<method")))
    {
      xml = xml.Substring(xml.IndexOf("<?xml"));
    }

    request.LoadXml(xml);              // <=
  }
  catch (Exception ex)
  {
    throw new MetaWeblogException("01", 
                                  $"Invalid XMLRPC Request. ({ex.Message})");
  }
  ....
}

Как видим, аргумент действительно обрабатывается XML-парсером: request.LoadXml(xml). PVS-Studio здесь считает, что request сконфигурирован так, что является уязвимым к XXE. Наша задача – подтвердить это. Или же опровергнуть и тогда отметить предупреждение как false positive. Здесь нам пригодится теория, которую мы разбирали в начале статьи.


Тип объекта, на который указывает ссылка requestXmlDocument. Парсер имеет дефолтные настройки, а значит, нам понадобится узнать используемую версию .NET. Посмотреть её можно в свойствах проекта.


0918_XXE_BlogEngine_ru/image9.png


Смотрим табличку, которую мы разбирали ранее, и видим, что по умолчанию экземпляры типа XmlDocument в приложениях под .NET Framework 4.5.1 и ниже уязвимы к XXE.


Получается, что у нас совпали все факторы для потенциальной XXE:


  • есть данные, которые могут быть скомпрометированы: ParseRequest -> inputXml -> xml;
  • есть парсер с опасной конфигурацией, который с этими данными работает: request.LoadXml(xml).

В теории XXE здесь есть, но это всё ещё потенциальная уязвимость. Нам нужно убедиться в том, что атака возможна на практике. Для этого понадобится ещё немного копнуть код.


Мы начинали наш анализ с конструктора типа XMLRPCRequest. Он вызывается в одном месте:


internal class MetaWeblogHandler : IHttpHandler
{
  ....
  public void ProcessRequest(HttpContext context)
  {
    try
    {
      var rootUrl = Utils.AbsoluteWebRoot.ToString();

      // context.Request.Url.ToString().Substring(0,   
      // context.Request.Url.ToString().IndexOf("metaweblog.axd"));

      var input = new XMLRPCRequest(context); // <=
      ....
     }
     ....
   }
   ....
}

Ага, мы наткнулись на HTTP handler. Для него же находим запись в конфиге:


<add name="MetaWeblog" 
     verb="*" 
     path="metaweblog.axd" 
     type="BlogEngine.Core.API.MetaWeblog.MetaWeblogHandler, BlogEngine.Core" 
     resourceType="Unspecified" 
     requireAccess="Script" 
     preCondition="integratedMode" />

Теперь мы знаем, куда нужно стучаться, чтобы сработал нужный нам обработчик. Давайте попробуем провести саму атаку.


Прежде всего нам понадобится XML-файл, через который мы и попытаемся 'стащить' данные с машины, где развёрнут блог:


<?xml version="1.0"?>
<!DOCTYPE xxe [
 <!ENTITY externalEntity SYSTEM 
   "file:///C:/Windows/System32/drivers/etc/hosts">
]>
<xxe>&externalEntity;</xxe>

Если XML-парсер обрабатывает внешние сущности, то вместо &externalEntity; он должен подставить содержимое файла hosts.


Выполняем запрос, отправляем XML и исследуем, как будет отрабатывать наш обработчик. Для удобства есть смысл сохранить XML в файл (в данном примере — xxe.xml), чтобы при необходимости можно было легко менять его содержимое, не изменяя самой команды запроса.


curl -d "@xxe.xml" -X POST http://vasiliev-pc:8081/metaweblog.axd

Итак, хендлер поймал наш запрос и вызвал конструктор XMLRPCRequest, который мы рассматривали ранее.


0918_XXE_BlogEngine_ru/image10.png


Заходим внутрь конструктора и проверяем данные в переменной inputXml.


0918_XXE_BlogEngine_ru/image11.png


Ага, всё идёт по плану – данные оказались 'заражёнными', как мы предполагали (и хотели) и передаются в метод LoadXmlRequest в качестве аргумента. Наблюдаем дальше.


0918_XXE_BlogEngine_ru/image12.png


За счёт опасных дефолтных настроек парсер отработал в точности так, как мы ожидали, – загрузил содержимое файла hosts. Далее выполняется следующий фрагмент кода:


// Method name is always first
if (request.DocumentElement != null)
{
  this.MethodName = request.DocumentElement.ChildNodes[0].InnerText;
}

По счастливому (для злоумышленника :)) стечению обстоятельств в MethodName будет записано как раз то, что нам нужно – hosts. Следующий интересующий нас фрагмент кода – большой switch, в котором в зависимости от имени метода выполняются те или иные действия:


switch (this.MethodName)
{
  case "metaWeblog.newPost":
    ....
    break;
  case "metaWeblog.editPost":
    ....
    break;
  case "metaWeblog.getPost":
    ....
    break;
  ....
    default:
      throw new MetaWeblogException("02", $"Unknown Method. ({MethodName})");
}

Здесь нас интересует default-ветвь, в которую и перейдёт исполнение, так как подходящий метод найден не будет. В этой ветви выбрасывается исключение, в сообщение которого будет подставлено имя метода, для которого не удалось выполнить сопоставление. Напоминаю, что в нашем случае имя метода – содержимое файла hosts.


В результате выброса исключения мы снова возвращаемся в хендлер и попадаем в обработчик, который сообщает о неизвестном методе:


0918_XXE_BlogEngine_ru/image13.png


В итоге на наш первоначальный запрос:


curl -d "@xxe.xml" -X POST http://vasiliev-pc:8081/metaweblog.axd

Получаем следующий ответ:


0918_XXE_BlogEngine_ru/image14.png


Получается, что с помощью XXE-атаки нам удалось вытащить содержимое hosts файла с машины, на которой просто был развёрнут блог. Зная расположение других файлов, можно попробовать получить и их содержимое. Причём не только с атакуемой машины, но и с других машин сети, к которым есть доступ. Здесь же, в контексте сетевых запросов, можно вспомнить и про SSRF.


Итак, только что мы с вами увидели XXE как с точки зрения приложения (кода), так и с точки зрения пользователя (злоумышленника). Это реальная уязвимость – CVE-2018-14485 (запись в базе NVD).


Что нужно делать с уязвимостями? Правильно, закрывать. Соответствующий коммит можно найти здесь. В исправлении поменяли конфигурацию XML-парсера, запретив ему обрабатывать внешние сущности. Для этого достаточно установить значение свойства XmlResolver в null:


var request = new XmlDocument() { XmlResolver = null };

Теперь, если попробовать достать всё тот же файл hosts, в вывод он не попадёт.


0918_XXE_BlogEngine_ru/image15.png


PVS-Studio, кстати, тоже учитывает, что парсер с такой конфигурацией (когда значение XmlResolvernull) не будет обрабатывать внешние сущности, и не выдаёт предупреждение на исправленный код.


Два других предупреждения, которые мы видели раньше, также указывают на уязвимости. Их разбирать не будем (с точки зрения кода суть похожа), но ниже я приведу основную информацию о них.


CVE-2019-10718


  • Предупреждение: V5614 [CWE-611, OWASP-5.5.2] Potential XXE vulnerability. Insecure XML parser 'doc' is used to process potentially tainted data from the 'xml' variable. PingbackHandler.cs 341
  • Записи в базах: NVD, CVE.
  • Коммит с исправлением: ссылка.

CVE-2019-11392


  • Предупреждение: V5614 [CWE-611, OWASP-5.5.2] Potential XXE vulnerability. Insecure XML parser 'doc' is used to process potentially tainted data from the 'stream' variable. SyndicationHandler.cs 191
  • Записи в базах: NVD, CVE.
  • Коммит с исправлением: ссылка.

Как защититься?


  1. Знать про проблему. Факт того, что уязвимости могут появляться из-за обработки XML-файлов, может стать неожиданным открытием. Поэтому, чем больше людей знает про проблематику, тем лучше.
  2. Используйте более новые версии фреймворков. Разработчики стремятся к повышению безопасности продуктов 'из коробки'. В случае с .NET новые версии фреймворка являются более безопасными.
  3. Явно прописывайте выставление безопасных настроек для XML-парсеров. Запрещайте обработку DTD и внешних сущностей, если они не нужны. Это минимизирует возможный риск (в частности – при копировании кода), а также более явно обозначит ваши намерения. Если же вам необходима обработка DTD, накладывайте как можно больше ограничений.
  4. Используйте специализированные средства для поиска дефектов безопасности: SAST, DAST и т.п. Использование тех же SAST-решений на регулярной основе позволит находить подобные дефекты ещё на этапе написания кода. Кстати, попробовать PVS-Studio, упомянутый в статье, можно здесь.

Заключение


Теперь вы немного больше подкованы в вопросах безопасности и XXE, а также знаете, что даже простой блог, развёрнутый на вашей машине, может стать источником уязвимостей.


На самом деле, тема XXE глубже и, безусловно, ещё есть, куда копнуть. Но хотя бы просто знать об этом дефекте безопасности и понимать его на базовом уровне уже будет полезно.


Praemonitus, praemunitus.


По доброй традиции приглашаю подписаться на мой Twitter, чтобы не пропустить ничего интересного.


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Sergey Vasiliev. Vulnerabilities due to XML files processing: XXE in C# applications in theory and in practice.

Комментарии (2)


  1. navferty
    11.02.2022 13:50
    +1

    На дотнексте был интересный доклад про уязвимости в десериализации Deserialization vulns: past, present, and future


    1. foto_shooter Автор
      11.02.2022 15:14
      +2

      Эх, оффлайн-конференции - славные были времена! Хотел тогда сходить на этот доклад, но что-то не сложилось. :(

      Значит, надо наверстать! Спасибо за ссылку. ;)