﻿# Почему моё приложение при открытии SVG\-файла отправляет сетевые запросы?

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

![0923_SVG_XXE_ru/image1.png](https://import.viva64.com/docx/blog/0923_SVG_XXE_ru/image1.png)

В современном мире на каждый случай жизни есть библиотека\. Поэтому для своего приложения мы также не будем изобретать велосипед, а возьмём готовое решение\. Например, SVG\.NET\. Исходный код проекта [доступен на GitHub](https://github.com/svg-net/SVG)\. Сама библиотека дистрибьютится как NuGet\-пакет, что очень удобно в плане подключения к проекту\. Кстати, на [странице проекта в NuGet Gallery](https://www.nuget.org/packages/svg) можно увидеть, что библиотеку загрузили 2\.5 миллиона раз – впечатляет\! 

Рассмотрим синтетический пример описанного ранее приложения:

```cpp
void ProcessSvg()
{
  using var svgStream = GetSvgFromUser();    
  var svgDoc = SvgDocument.Open<SvgDocument>(svgStream);    
  
  // SVG document processing...

  SendSvgToUser(svgDoc);
}
```

Суть проста:

1. Получаем от пользователя картинку\. Как именно – не принципиально\.
1. Создаётся экземпляр _SvgDocument_, с которым дальше осуществляются какие\-то действия\. Например, некоторые преобразования\.
1. Изменённый объект отправляется обратно пользователю\.

Реализация методов _GetSvgFromUser_ и _SendSvgToUser_ в данном случае не столь важна\. Будем считать, что первый принимает картинку по сети, а второй отправляет её обратно\.

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

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

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

![0923_SVG_XXE_ru/image2.png](https://import.viva64.com/docx/blog/0923_SVG_XXE_ru/image2.png)

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

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

![0923_SVG_XXE_ru/image3.png](https://import.viva64.com/docx/blog/0923_SVG_XXE_ru/image3.png)

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

Первое – приложение отправило незапланированный запрос к [pvs\-studio\.com](https://pvs-studio.ru/ru/)\. Это можно было увидеть, например, отмониторив сетевую активность приложения\.

![0923_SVG_XXE_ru/image4.png](https://import.viva64.com/docx/blog/0923_SVG_XXE_ru/image4.png)

Второе – пользователь приложения получил файл [hosts](https://en.wikipedia.org/wiki/Hosts_(file)) с машины, на которой открывался SVG\. 

Как? Где этот файл? Давайте посмотрим на текстовое представление SVG\-файла, полученного от приложения\. Ненужные части сократим, чтобы не мешались\.

```cpp
<?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\-атакой](https://pvs-studio.ru/ru/blog/terms/6546/), возможно, уже поняли, в чём дело\. Если про XXE вы не слышали или подзабыли, что это такое – настоятельно рекомендую ознакомиться со статьёй "[Уязвимости из\-за обработки XML\-файлов: XXE в C\# приложениях в теории и на практике](https://pvs-studio.ru/ru/blog/posts/csharp/0918/)"\. В ней я рассказываю о сути XXE, причинах и последствиях\. Эта информация потребуется для понимания дальнейшего изложения\.

Напомню, что для проведения XXE\-атаки необходимы:

* данные от пользователя, которые могут быть скомпрометированы;
* небезопасно сконфигурированный XML\-парсер\.

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

В данном случае "все звёзды совпали":

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

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

Первое, что нужно вспомнить – [формат SVG основан на XML](https://ru.wikipedia.org/wiki/SVG)\. Это даёт возможность определять в SVG\-файлах XML\-сущности, которые и нужны для проведения XXE\.

Несмотря на то, что в браузере SVG\-файл "подставной" выглядит обычным образом, внутри он содержит объявление двух сущностей:

```cpp
<?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\>_\. Его исходный код выглядит так:

```cpp
public static T Open<T>(Stream stream) where T : SvgDocument, new()
{
  return Open<T>(stream, null);
}
```

Этот метод, в свою очередь, вызывает другую перегрузку:

```cpp
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_\.

```cpp
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\-парсера\.

```cpp
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_:

```cpp
/// <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_:

```cpp
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](https://pvs-studio.ru/ru/blog/posts/csharp/0918/) мы помним, что использование экземпляра _XmlUrlResolver_ для обработки внешних сущностей чревато проблемами безопасности\. Выходит, что с _SvgDtdResolver_ та же ситуация\.

Получаем выполнение всех необходимых условий:

* обработка DTD включена \(свойство _DtdProcessing_ имеет значение _DtdProcessing\.Parse_\);
* в парсере используется опасный резолвер \(свойство _XmlResolver_ ссылается на экземпляр небезопасного _SvgDtdResolver_\)\.

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

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

На странице проекта на GitHub по поводу этой проблемы был открыт issue – "[Security: vulnerable to XXE attacks](https://github.com/svg-net/SVG/issues/869)"\. Через неделю – [ещё один](https://github.com/svg-net/SVG/issues/872)\. Для каждого issue был сделан PR: [первый](https://github.com/svg-net/SVG/pull/870), [второй](https://github.com/svg-net/SVG/pull/873)\.

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

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

![0923_SVG_XXE_ru/image5.png](https://import.viva64.com/docx/blog/0923_SVG_XXE_ru/image5.png)

Во втором PR кода докинули побольше, а булев флаг заменили на перечисление\. По умолчанию резолвинг внешних сущностей всё так же запрещён\. Изменений в коде побольше, если интересно – посмотреть их можно [здесь](https://github.com/svg-net/SVG/pull/873/files)\.

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

Приложение больше не выполняет сетевых запросов, равно как и не "крадёт" файлы\. Если посмотреть результирующий SVG\-файл, можно заметить, что сущности просто ни во что не раскрылись:

```cpp
<?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, почитать можно [здесь](https://pvs-studio.ru/ru/blog/posts/csharp/0876/)\. Цель таких инструментов – отслеживать использование зависимостей с известными уязвимостями и предупреждать об этом\. Здесь, конечно, важную роль играет база этих самых уязвимых компонентов\. Чем она больше, тем лучше\. 

И, естественно, не забывайте обновлять программные компоненты\. Ведь кроме новых фич и баг\-фиксов в новых версиях исправляются и дефекты безопасности\. Например, в SVG\.NET обозреваемый дефект безопасности был закрыт в релизе [3\.3\.0](https://www.nuget.org/packages/Svg/3.3.0)\. 

## Заключение

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

По доброй традиции приглашаю подписываться на [меня в Twitter](https://twitter.com/_SergVasiliev_), чтобы не пропускать тематические публикации\.