Вопросы безопасности для данных

При работе с данными в Windows Communication Foundation (WCF) необходимо учитывать ряд категорий угроз. В следующем списке показаны наиболее важные классы угроз, связанные с обработкой данных. WCF предоставляет средства для устранения этих угроз.

  • Отказ в обслуживании

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

  • Выполнение вредоносного кода

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

  • Раскрытие информации

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

Предоставляемый пользователем код и управление доступом для кода

Ряд мест в коде запуска инфраструктуры Windows Communication Foundation (WCF), предоставленном пользователем. Например, ядро сериализации DataContractSerializer может вызывать предоставляемые пользователем методы доступа к свойству set и get . Инфраструктура канала WCF также может вызывать производные классы класса, предоставляемые пользователем Message .

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

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

Следует гарантировать, что вредоносный код не попадет в систему в разных точках расширяемости. Это особенно уместно в случае частичного доверия, когда ведется работа с частично доверенными сборками или создаются компоненты, используемые частично доверенным кодом. Дополнительные сведения см. далее в разделе "Угрозы частично доверенных компонентов".

Обратите внимание, что при частичном доверии инфраструктура сериализации контракта данных поддерживает только ограниченный набор модели программирования контрактов данных (например элементы закрытых данных или типы, использующие атрибуты SerializableAttribute , не поддерживаются). Дополнительные сведения см. в разделе "Частичное доверие".

Примечание.

Безопасность доступа к коду (CAS) устарела во всех версиях платформа .NET Framework и .NET. Последние версии .NET не учитывают заметки CAS и создают ошибки, если используются API, связанные с CAS. Разработчики должны искать альтернативные средства выполнения задач безопасности.

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

При разработке сериализуемых типов с учетом безопасности следует обратить внимание на проблему раскрытия информации.

Примите во внимание следующее:

  • Модель программирования DataContractSerializer допускает раскрытие во время сериализации закрытых и внутренних данных за пределы типа или сборки. Кроме того, во время экспорта схемы возможно раскрытие формы типа. Важно понимать проекцию сериализации типа. Чтобы предотвратить раскрытие любой информации, необходимо отключить сериализацию (например не применяя атрибут DataMemberAttribute в случае контракта данных).

  • Помните, что один и тот же тип может иметь несколько проекций сериализации в зависимости от используемого сериализатора. Один и тот же тип может раскрывать один набор данных при использовании с DataContractSerializer и другой - при использовании с XmlSerializer. Случайное использование неправильного сериализатора может стать причиной раскрытия информации.

  • Использование XmlSerializer в режиме вызова устаревших удаленных процедур (RPC)/encoded может случайно раскрыть форму графа объекта на отправляющей стороне в отношении получающей стороны.

Предотвращение атак типа "отказ в обслуживании"

Планы продаж

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

Атаки типа "отказ в обслуживании" обычно предотвращаются путем использования квот. При превышении квоты, как правило, выдается исключение QuotaExceededException . Без использования квот вредоносное сообщение может задействовать всю доступную память (что приведет к исключению OutOfMemoryException ) или все доступные стеки (как следствие - исключение StackOverflowException).

В сценарии превышенных квот предусмотрено устранение ошибок. При возникновении ошибки в работающей службе, обрабатываемое в настоящий момент сообщение удаляется, и служба продолжает работать и обрабатывать последующие сообщения. Однако сценарии переполнения и переполнения стека не восстанавливаются в любом месте платформа .NET Framework; служба завершает работу при возникновении таких исключений.

Квоты в WCF не включают предварительное выделение. Например, если квота MaxReceivedMessageSize (в различных классах) задана как 128 КБ, это не означает, что для каждого сообщения автоматически выделяется 128 КБ. Выделяемый объем зависит от фактического размера входящего сообщения.

На транспортном уровне предусмотрено большое количество квот. Эти квоты принудительно применяются в соответствии с используемым специальным каналом транспорта (HTTP, TCP и т. д.). В этом разделе представлено описание только некоторых из этих квот, подробную информацию по квотам см. в разделе Transport Quotas.

Уязвимость, связанная с использованием хэш-таблиц

Если контракты данных содержат хэш-таблицы или коллекции, возникает потенциальная уязвимость. Проблема обнаруживается при вставке большого количества значений в хэш-таблицу, если для значительной части этих значений создается одно и то же значение хэша. Это можно использовать для DOS-атаки. Эта уязвимость может быть устранена, задав квоту привязки MaxReceivedMessageSize. Следует задавать эту квоту с осторожностью, чтобы предотвратить атаки. Эта квота устанавливает верхний предел на размер сообщения WCF. Кроме того, избегайте использования хэш-таблиц или коллекций в контрактах данных.

Ограничение потребления памяти без потоковой передачи

Модель безопасности в отношении больших сообщений зависит от того, используется ли потоковая передача. В основном, в непотоковом случае сообщения буферизуются в память. В этом случае следует использовать квоту MaxReceivedMessageSize в TransportBindingElement или в предоставленных системой привязках, чтобы обеспечить защиту от больших сообщений, ограничив максимальный размер сообщения, для которого выделяется память. Обратите внимание, что служба может обрабатывать несколько сообщений одновременно, в таком случае все они находятся в памяти. Чтобы устранить эту угрозу, необходимо воспользоваться функцией регулирования.

Также учтите, что MaxReceivedMessageSize не задает верхнюю границу на выделение памяти для каждого сообщения, а ограничивает размер памяти постоянным значением. Например, если MaxReceivedMessageSize равно 1 МБ, и получено и затем десериализовано сообщение размером 1 МБ, для хранения десериализованного графа объекта требуется больший объем памяти, что приводит к общему потреблению памяти больше 1 МБ. По этой причине следует избегать создания сериализуемых типов, которые могут стать причиной значительного потребления памяти при небольшом объеме входящих данных. Например, контракт данных "MyContract" с 50 необязательными полями элементов данных и дополнительными 100 частными полями можно создать с помощью xml-конструкции "<MyContract/".> Такой XML выделяет память для 150 полей. Обратите внимание, что по умолчанию элементы данных необязательны. Проблема усложняется, если такой тип является частью массива.

Самого по себеMaxReceivedMessageSize недостаточно, чтобы избежать всех атак типа "отказ в обслуживании". Например, десериализатор может принудительно десериализовывать граф объекта с глубоким вложением (объект, содержащий другой объект, который в свою очередь содержит еще один объект, и т. д.) посредством входящего сообщения. Чтобы десериализовать такие графы, DataContractSerializer и XmlSerializer вызывают методы вложенным образом. Глубокое вложение вызовов методов может стать причиной невозможности восстановления StackOverflowException. Эта угроза устраняется путем настройки квоты MaxDepth с целью ограничения уровня вложений XML, как описано в разделе "Безопасное использование XML" далее в этой теме.

Настройка дополнительных квот для MaxReceivedMessageSize особенно важна при использовании двоичного кодирования XML. Использование двоичного кодирования в некотором смысле аналогично сжатию: небольшая группа байтов во входящем сообщении может представлять большой объем данных. Поэтому даже если сообщение соответствует ограничению MaxReceivedMessageSize , для полностью расширенной формы может потребоваться намного больший объем памяти. Чтобы устранить такие угрозы, относящиеся к XML, следует правильно задать все квоты средства чтения XML, как описано в разделе "Безопасное использование XML" далее в этой теме.

Ограничение потребления памяти с потоковой передачей

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

Чтобы устранить эту угрозу, определенные параметры квоты существуют в различных компонентах обработки данных WCF, ограничивающих буферизацию. Наибольшее значение имеет свойство MaxBufferSize для различных элементов привязки транспорта и стандартных привязок. При потоковой передаче эта квота должна быть задана с учетом максимального объема памяти, который следует выделить для каждого сообщения. Как и в случае MaxReceivedMessageSizeпараметр не задает абсолютный максимальный объем потребляемой памяти, а только ограничивает его в пределах определенного диапазона. Кроме того, как и в случае MaxReceivedMessageSizeпомните о возможности одновременной обработки нескольких сообщений.

Подробные сведения о MaxBufferSize

Свойство MaxBufferSize ограничивает любое массовое буферизация WCF. Например, WCF всегда буферизирует заголовки SOAP и ошибки SOAP, а также любые части MIME, которые не находятся в естественном порядке чтения в сообщении механизма оптимизации передачи сообщений (MTOM). Этот параметр ограничивает объем буферизации во всех этих случаях.

WCF выполняет это путем передачи MaxBufferSize значения различным компонентам, которые могут буферировать. Например, некоторые перегрузки метода CreateMessage класса Message принимают параметр maxSizeOfHeaders . WCF передает MaxBufferSize значение этому параметру, чтобы ограничить объем буферизации заголовков SOAP. Важно задать этот параметр при непосредственном использовании класса Message . Как правило, при использовании компонента в WCF, принимающем параметры квоты, важно понимать последствия безопасности этих параметров и правильно задавать их.

Кодировщик сообщения MTOM также имеет параметр MaxBufferSize . При использовании стандартных привязок он задается автоматически как значение на транспортном уровне MaxBufferSize . Однако при использовании элемента привязки кодировщика сообщения MTOM для создания пользовательской привязки важно задать свойство MaxBufferSize как безопасное значение в случае потоковой передачи.

Атаки при потоковой передаче, основанной на XML

MaxBufferSize в одиночку недостаточно, чтобы убедиться, что WCF не может быть принудительно включена в буферизацию при ожидаемой потоковой передаче. Например, средства чтения XML WCF всегда буферизует весь начальный тег XML-элемента при начале чтения нового элемента. Это осуществляется для правильной обработки пространств имен и атрибутов. Если MaxReceivedMessageSize задан как большой (например, чтобы реализовать сценарий потоковой передачи большого диска непосредственно на диск), может быть создано вредоносное сообщение, в котором все тело сообщения представляет собой большой открывающий тег элемента XML. Попытка чтения приводит к исключению OutOfMemoryException. Это одна из многих возможных атак отказов в обслуживании на основе XML, которые могут быть устранены с помощью квот чтения XML, рассмотренных в разделе "Использование XML-Сейф ly" далее в этом разделе. При потоковой передаче очень важно задать все такие квоты.

Совместное использование моделей программирования потоковой передачи и буферизации

Причина многих потенциальных атак заключается в совместном использовании моделей программирования потоковой и непотоковой передачи в одной службе. Допустим, существует контракт службы с двумя операциями: одна принимает Stream , а другая - массив какого-либо пользовательского типа. Также предположим, что для MaxReceivedMessageSize задано большое значение, чтобы при первой операции обрабатывались большие потоки. К сожалению, это означает, что большие сообщения могут быть отправлены и во вторую операцию, и десериализатор буферизует данные в память как массив до вызова операции. Это потенциальная атака типа "отказ в обслуживании": квота MaxBufferSize не ограничивает размер тела сообщения, с которым работает десериализатор.

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

  • Выключите функцию IExtensibleDataObject , задав свойство IgnoreExtensionDataObjectServiceBehaviorAttribute как true. Это обеспечит десериализацию только элементов, являющихся частью контракта.

  • Задайте для свойства MaxItemsInObjectGraphDataContractSerializer безопасное значение. Эта квота также доступна для атрибута ServiceBehaviorAttribute или через конфигурацию. Такая квота ограничивает число объектов, десериализуемых в одном эпизоде десериализации. Как правило, каждый параметр операции или часть тела сообщения в контракте сообщения десериализуется в одном эпизоде. При десериализации массивов каждая запись массива считается отдельным объектом.

  • Задайте для всех квот средства чтения XML безопасные значения. Обратите внимание на MaxDepth, MaxStringContentLengthи MaxArrayLength и избегайте строк в непотоковых операциях.

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

  • Не используйте какие-либо типы, реализующие интерфейс IXmlSerializable , который буферизует большой объем данных. Не добавляйте такие типы в список известных типов.

  • Не используйте массивы XmlElement, XmlNode , Byte или типы, реализующие ISerializable в контракте.

  • Не используйте массивы XmlElement, XmlNode , Byte или типы, реализующие ISerializable в списке известных типов.

Указанные выше меры предосторожности принимаются, когда непотоковая операция использует DataContractSerializer. Запрещается совместное использование потоковых и непотоковых моделей программирования в одной и той же службе, если используется XmlSerializer, поскольку для него не предусмотрена защита квотой MaxItemsInObjectGraph .

Атаки типа "медленная потоковая передача"

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

Чтобы устранить такие атаки, следует правильно задать значения времени ожидания транспорта. Дополнительные сведения см. в разделе "Квоты транспорта". Во-вторых, никогда не используйте синхронные Read операции или Write операции при работе с потоками в WCF.

Безопасное использование XML

Примечание.

Несмотря на то что этот раздел посвящен XML, информация также относится к документам JavaScript Object Notation (JSON). При использовании Mapping Between JSON and XMLпринципы действия квот аналогичны.

Безопасные средства чтения XML

Xml Infoset формирует основу всей обработки сообщений в WCF. Во время получения XML-данных из ненадежного источника существует вероятность применения ряда атак типа «отказ в обслуживании», которых следует избегать. WCF предоставляет специальные, безопасные средства чтения XML. Эти средства чтения создаются автоматически при использовании одной из стандартных кодировк в WCF (текст, двоичный или MTOM).

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

При работе непосредственно с средствами чтения XML (например, при написании собственного пользовательского кодировщика или при работе непосредственно с Message классом) всегда используйте средства чтения WCF, когда есть вероятность работы с ненадежными данными. Создайте безопасные средства чтения, вызвав одну из перегрузок статического фабричного метода CreateTextReader, CreateBinaryReaderили CreateMtomReader для класса XmlDictionaryReader . При создании средства чтения передайте безопасные значения квот. Не вызывайте перегрузки метода Create . Они не создают средство чтения WCF. Вместо этого создается средство чтения, не защищенное функциями безопасности, описанными в этом разделе.

Квоты средства чтения

Для безопасных средств чтения XML предусмотрено пять настраиваемых квот. Они, как правило, задаются свойством ReaderQuotas для элементов привязки кодирования или стандартных привязок или с помощью объекта XmlDictionaryReaderQuotas , передаваемого при создании средства чтения.

MaxBytesPerRead

Эта квота ограничивает число байтов, которые считываются за одну операцию Read при чтении открывающего тега элемента и его атрибутов. (В случае непотоковой передачи данных само имя элемента не учитывается в квоте.) MaxBytesPerRead важно по следующим причинам.

  • Во время чтения имя элемента и его атрибуты всегда буферизуются в память. Поэтому в режиме потоковой передачи важно правильно задать эту квоту, чтобы предотвратить чрезмерную буферизацию, если ожидается потоковая передача. Сведения о фактическом объеме буферизации см. в разделе квоты MaxDepth .

  • Наличие слишком большого числа атрибутов XML может привести к неоправданно большому времени обработки, поскольку требуется проверка уникальности имен атрибутов. MaxBytesPerRead устраняет эту возможную проблему.

MaxDepth

Эта квота ограничивает максимальную глубину вложенности XML-элементов. Например, документ "<A><><C/B/><B></A>" имеет вложенную глубину 3. ЗначениеMaxDepth важно по следующим причинам.

  • MaxDepth взаимодействует с MaxBytesPerRead: средство чтения всегда сохраняет в памяти данные по текущему элементу и всем его предкам, поэтому максимальный потребляемый средством чтения объем памяти пропорционален произведению этих двух параметров.

  • При десериализации графа объекта с глубоким вложением десериализатор принудительно получает доступ ко всему стеку, и выдается неисправимое исключение StackOverflowException. Между вложением XML и вложением объекта существует прямая связь как для DataContractSerializer , так и для XmlSerializer. Квота MaxDepth позволяет устранить эту угрозу.

MaxNameTableCharCount

Эта квота ограничивает размер таблицы именсредства чтения. В таблице имен содержатся определенные строки (например пространства имен и префиксы), возникающие при обработке документа XML. Поскольку эти строки буферизуются в память, задайте эту квоту, чтобы предотвратить чрезмерную буферизацию, если ожидается потоковая передача.

MaxStringContentLength

Эта квота ограничивает максимальный размер строки, возвращаемой средством чтения XML. Эта квота ограничивает потребление памяти не в самом средстве чтения XML, а в компоненте, использующем средство чтения. Например, когда DataContractSerializer использует средство чтения, защищенное MaxStringContentLength, он не десериализует строки, чей размер превышает указанное в этой квоте значение. При непосредственном использовании класса XmlDictionaryReader не все методы учитывают эту квоту, а только те, которые специально созданы для чтения строк, например метод ReadContentAsString . Эта квота не влияет на свойство Value средства чтения, и поэтому оно не должно использоваться, если требуется защита, обеспечиваемая этой квотой.

MaxArrayLength

Эта квота ограничивает максимальный размер массива примитивов, возвращаемых средством чтения XML, включая байтовые массивы. Эта квота ограничивает потребление памяти не в самом средстве чтения XML, а в компоненте, использующем средство чтения. Например, когда DataContractSerializer использует средство чтения, защищенное MaxArrayLength, оно не десериализует байтовые массивы, чей размер превышает указанное в этой квоте значение. Важно задать эту квоту при попытке совместного использования моделей программирования потоковой передачи и буферизации в одном контракте. Помните, что при непосредственном использовании класса XmlDictionaryReader этой квоте соответствуют только методы, специально созданные для чтения массивов произвольного размера определенных примитивных типов, например ReadInt32Array.

Угрозы, относящиеся к двоичному кодированию

Двоичное кодирование XML WCF поддерживает функцию словаря строк . Большую строку можно закодировать с помощью всего нескольких байтов. В результате значительно повышается производительность, но при этом возникают новые угрозы атак типа "отказ в обслуживании", которые следует устранить.

Существует два вида словарей: статический и динамический. Статический словарь представляет собой встроенный список длинных строк, которые можно представить с помощью короткого кода в двоичном кодировании. При создании средства чтения список строк становится постоянным и не подлежит изменению. Ни одна из строк статического словаря, используемого WCF по умолчанию, достаточно велика, чтобы представлять серьезную угрозу типа "отказ в обслуживании", хотя они по-прежнему могут использоваться в атаке расширения словаря. В сложных сценариях, в которых используется собственный статический словарь, при вводе больших строк словаря следует соблюдать осторожность.

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

Первой угрозой, которую следует устранить, является вероятность чрезмерного увеличения размера динамического словаря (таблицы сопоставлений "строка-код"). Такой словарь может расширяться по мере накопления сообщений, а квота MaxReceivedMessageSize не обеспечивает защиту, поскольку применяется только к каждому отдельному сообщению. Поэтому в MaxSessionSize существует отдельное свойство BinaryMessageEncodingBindingElement , ограничивающее размер словаря.

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

Угрозы типа "расширение словаря"

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

Свойства MaxNameTableCharCount, MaxStringContentLengthи MaxArrayLength ограничивают только потребление памяти. Как правило, они не требуются для устранения каких-либо угроз непотокового использования, поскольку выделение памяти уже ограничено MaxReceivedMessageSize. Однако MaxReceivedMessageSize учитывает байты предварительного расширения. При использовании двоичного кодирования потребление памяти потенциально может выйти за рамки MaxReceivedMessageSize, ограничиваясь только MaxSessionSize. По этой причине, если используется двоичное кодирование, важно всегда задавать все квоты средства чтения (особенно MaxStringContentLength).

При использовании двоичного кодирования наряду с DataContractSerializerвозможно неправильное использование интерфейса IExtensibleDataObject , что может вызвать атаку типа "расширение словаря". По существу этот интерфейс предоставляет неограниченное пространство для произвольных данных, не являющихся частью контракта. Если невозможно задать достаточно низкие квоты, чтобы произведение MaxSessionSize и MaxReceivedMessageSize не представляло бы проблемы, при использовании двоичного кодирования отключите функцию IExtensibleDataObject . Задайте для свойства IgnoreExtensionDataObject значение true для атрибута ServiceBehaviorAttribute . В качестве альтернативы можно не реализовывать интерфейс IExtensibleDataObject . Дополнительные сведения о создании контрактов данных, обладающих прямой совместимостью, см. в разделе Контракты данных, совместимые с любыми будущими изменениями.

Сводка по квотам

В следующей таблице приводятся сводные правила использования квот.

Condition Важные квоты, которые следует задать
Отсутствие потоковой передачи или потоковая передача небольших сообщений, кодирование text или MTOM MaxReceivedMessageSize, MaxBytesPerRead и MaxDepth
Отсутствие потоковой передачи или потоковая передача небольших сообщений, двоичное кодирование MaxReceivedMessageSize, MaxSessionSizeи все ReaderQuotas
Потоковая передача больших сообщений, текстовое кодирование или кодирование MTOM MaxBufferSize и все ReaderQuotas
Потоковая передача больших сообщений, двоичное кодирование MaxBufferSize, MaxSessionSizeи все ReaderQuotas
  • Всегда должны быть заданы значения времени ожидания на транспортном уровне, запрещается синхронное чтение/запись при использовании потоковой передачи независимо от того, какие сообщения передаются потоком: большие или малые.

  • В случае возникновения сомнений в отношении квот задавайте безопасное значение, но не оставляйте их пустыми.

Предотвращение выполнения вредоносного кода

Угрозы следующих общих классов могут выполнять код и приводить к незапланированным последствиям.

  • Десериализатор загружает вредоносный, небезопасный тип или тип, относящийся к безопасности.

  • Входящее сообщение заставляет десериализатор создавать экземпляр безопасного в нормальных условиях типа, который приведет к нежелательным последствиям.

В следующих разделах приводится подробное описание таких классов угроз.

DataContractSerializer

(Сведения о XmlSerializerбезопасности см. в соответствующей документации.) Модель безопасности для модели XmlSerializer безопасности похожа на DataContractSerializerмодель безопасности и отличается в основном в деталях. Например, для включения типа используется атрибут XmlIncludeAttribute вместо атрибута KnownTypeAttribute . Однако далее в этой теме рассматриваются угрозы, уникальные для XmlSerializer .

Предотвращение загрузки непредусмотренных типов

Загрузка непредусмотренных типов может привести к серьезным последствиям, будь этот тип вредоносным или всего лишь косвенно влияющим на безопасность. Тип может содержать уязвимость системы безопасности, выполнять действия, относящиеся к безопасности, в конструкторе или конструкторе класса, занимать большой объем памяти, что способствует атакам типа "отказ в обслуживании", или выводить неустранимые исключения. Типы могут содержать конструкторы классов, которые начинают работать сразу после загрузки типа и до создания каких-либо экземпляров. По этим причинам важно контролировать набор типов, которые может загружать десериализатор.

DataContractSerializer выполняет десериализацию слабо связанным образом. Он никогда не считывает тип среды CLR и имена сборок из входящих данных. Такое поведение аналогично поведению XmlSerializer, но отличается от поведения NetDataContractSerializer, BinaryFormatterи SoapFormatter. Слабое связывание предполагает некоторый уровень безопасности, поскольку удаленный злоумышленник не может указать произвольный тип, чтобы выполнить загрузку только путем именования этого типа в сообщении.

Для DataContractSerializer всегда разрешено загружать тип, который в текущий момент ожидается в соответствии с контрактом. Например, если контракт данных содержит элемент данных типа Customer, для DataContractSerializer разрешено загрузить тип Customer , когда он выполняет десериализацию этого элемента данных.

Кроме того, DataContractSerializer поддерживает полиморфизм. Элемент данных может быть объявлен как Object, но входящие данные могут содержать экземпляр Customer . Такое возможно только, если тип Customer был отмечен для десериализатора как "известный" посредством одного из следующих механизмов:

  • атрибутKnownTypeAttribute , примененный к типу;

  • атрибутKnownTypeAttribute , указывающий метод, который возвращает список типов;

  • Атрибут ServiceKnownTypeAttribute.

  • раздел конфигурации KnownTypes ;

  • список известных типов, явно переданных в DataContractSerializer во время создания при непосредственном использовании сериализатора.

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

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

  • ЗагружаетсяMyDangerousType , и запускается его конструктор класса.

  • Даже при десериализации контракта данных с элементом данных строки вредоносное сообщение все равно может создать экземпляр MyDangerousType . Возможно выполнение кода в MyDangerousType(например в методах задания свойств). По окончании десериализатор пытается назначить этот экземпляр для элемента данных строки и завершает работу с выводом исключения.

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

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

При наличии слишком большого числа известных типов возникает другое следствие: DataContractSerializer создает кэш кода сериализации/десериализации в домене приложения с записью для каждого типа, который требуется сериализовать или десериализовать. Этот кэш никогда не очищается, пока работает домен приложения. Поэтому злоумышленник, знающий, что приложение использует большое число известных типов, может вызвать десериализацию всех этих типов, в результате чего кэш будет использовать неограниченный объем памяти.

Избежание непредусмотренного состояния типов

Тип может иметь внутренние ограничения согласованности, которые обязательны к соблюдению. Следует соблюдать осторожность, чтобы не нарушить эти ограничения во время десериализации.

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

[DataContract]
public class SpaceStationAirlock
{
    [DataMember]
    private bool innerDoorOpenValue = false;
    [DataMember]
    private bool outerDoorOpenValue = false;

    public bool InnerDoorOpen
    {
        get { return innerDoorOpenValue; }
        set
        {
            if (value & outerDoorOpenValue)
                throw new Exception("Cannot open both doors!");
            else innerDoorOpenValue = value;
        }
    }
    public bool OuterDoorOpen
    {
        get { return outerDoorOpenValue; }
        set
        {
            if (value & innerDoorOpenValue)
                throw new Exception("Cannot open both doors!");
            else outerDoorOpenValue = value;
        }
    }
}
<DataContract()> _
Public Class SpaceStationAirlock
    <DataMember()> Private innerDoorOpenValue As Boolean = False
    <DataMember()> Private outerDoorOpenValue As Boolean = False

    Public Property InnerDoorOpen() As Boolean
        Get

            Return innerDoorOpenValue
        End Get
        Set(ByVal value As Boolean)
            If (value & outerDoorOpenValue) Then
                Throw New Exception("Cannot open both doors!")
            Else
                innerDoorOpenValue = value
            End If
        End Set
    End Property

    Public Property OuterDoorOpen() As Boolean
        Get
            Return outerDoorOpenValue
        End Get
        Set(ByVal value As Boolean)
            If (value & innerDoorOpenValue) Then
                Throw New Exception("Cannot open both doors!")
            Else
                outerDoorOpenValue = value
            End If
        End Set
    End Property
End Class

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

<SpaceStationAirlock>
    <innerDoorOpen>true</innerDoorOpen>
    <outerDoorOpen>true</outerDoorOpen>
</SpaceStationAirlock>

Такой ситуации можно избежать, если учитывать следующие моменты.

  • Когда DataContractSerializer десериализует большинство классов, конструкторы не работают. Поэтому не следует полагаться на какое-либо управление состоянием, осуществленное в конструкторе.

  • Чтобы убедиться в действительном состоянии объекта, используйте обратные вызовы. Обратный вызов, отмеченный атрибутом OnDeserializedAttribute , особенно полезен, поскольку он запускается по завершении сериализации и позволяет изучить и исправить общее состояние. Дополнительные сведения см. в разделе обратных вызовов сериализации version-Tolerant.

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

  • Соблюдайте осторожность при использовании устаревших типов, отмеченных атрибутом SerializableAttribute . Многие из них предназначены для работы с платформа .NET Framework удаленного взаимодействия для использования только с доверенными данными. Существующие типы, отмеченные этим атрибутом, могут быть созданы и без учета обеспечения безопасности состояния.

  • Чтобы гарантировать наличие данных с учетом обеспечения безопасности состояния, не следует полагаться на свойство IsRequired атрибута DataMemberAttribute . Данные всегда могут быть null, zeroили invalid.

  • Никогда не доверяйте графу объекта, десериализованному из источника ненадежных данных, не определив сначала его действительность. Каждый отдельный объект может находиться в согласованном состоянии, однако весь граф объекта в таком состоянии может и не находиться. Более того, даже если выключен режим сохранения графа объекта, десериализованный граф может содержать несколько ссылок на небольшой объект или содержать циклические ссылки. Дополнительные сведения см. в разделе Сериализация и десериализация.

Безопасное использование NetDataContractSerializer

NetDataContractSerializer представляет собой ядро сериализации, использующее тесное соединение с типами. Это аналогично BinaryFormatter и SoapFormatter. То есть определяет, какой тип следует создать, считывая платформа .NET Framework сборку и имя типа из входящих данных. Хотя она является частью WCF, нет предоставленного способа подключения в этом механизме сериализации; пользовательский код должен быть написан. Предоставляется NetDataContractSerializer в основном для упрощения миграции из платформа .NET Framework удаленного взаимодействия в WCF. Дополнительные сведения см. в соответствующем разделе сериализации и десериализации.

Поскольку само сообщение может указывать любой тип, который можно загрузить, механизм NetDataContractSerializer по своей природе небезопасен и должен использоваться только с надежными данными. Дополнительные сведения см. в статье Руководство по безопасности BinaryFormatter.

Даже при использовании с надежными данными входящие данные могут недостаточно точно указывать загружаемый тип, особенно если свойство AssemblyFormat задано как Simple. Любой, у кого есть доступ к каталогу приложения или глобальному кэшу сборок, может заменить один из загружаемых типов вредоносным. Всегда защищайте каталог приложения и глобальный кэш сборок, правильно назначая разрешения.

В общем, если открыть для частично доверенного кода доступ к своему экземпляру NetDataContractSerializer или иным образом разрешить ему управление суррогатным селектором (ISurrogateSelector) или связывателем сериализации (SerializationBinder), код может получить значительные возможности управления процессом сериализации/десериализации. Например, он может ввести произвольные типы, привести к раскрытию информации, подделать получаемый граф объекта или сериализованные данные или переполнить итоговый сериализованный поток.

Еще одним вопросом безопасности, связанным с NetDataContractSerializer , является отказ от обслуживания, не угроза выполнения вредоносного кода. При использовании NetDataContractSerializerвсегда задавайте для квоты MaxItemsInObjectGraph безопасное значение. Очень просто создать небольшое вредоносное сообщение, выделяющее массив объектов, чей размер ограничен только этой квотой.

Угрозы, связанные с XmlSerializer

Модель безопасности XmlSerializer аналогична модели DataContractSerializer. Однако существует несколько угроз, уникальных для XmlSerializer.

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

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

XmlSerializer может подвергаться атаке типа "отказ в обслуживании". У XmlSerializer отсутствует квота MaxItemsInObjectGraph (которая предусмотрена для DataContractSerializer). Поэтому он десериализует произвольное число объектов, ограничиваясь только размером сообщения.

Угрозы частичного доверия

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

  • При использовании любых компонентов сериализации никогда не назначайте какие-либо разрешения до начала такого использования, даже если весь сценарий сериализации находится в рамках назначения, и используются только доверенные данные и объекты. Такое использование может стать причиной уязвимостей системы безопасности.

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

  • Если вы разрешаете частично доверенный доступ к коду экземпляра DataContractSerializer или управляете суррогатами контракта данных, он может выполнять большую часть контроля над процессом сериализации или десериализации. Например, он может ввести произвольные типы, привести к раскрытию информации, подделать получаемый граф объекта или сериализованные данные или переполнить итоговый сериализованный поток. Аналогичная угроза для NetDataContractSerializer описана в разделе "Безопасное использование NetDataContractSerializer".

  • Если атрибут DataContractAttribute применяется к типу (или типу, отмеченному SerializableAttribute , но не ISerializable), десериализатор может создать экземпляр такого типа, даже если все конструкторы являются закрытыми или защищены требованиями.

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

  • Если передается экземпляр DataContractSerializer с суррогатом, добавленным в частично доверенный код, код может изменить любые изменяемые параметры этого суррогата.

  • Что касается десериализованного объекта, если средство чтения XML (или данных) используется с частично доверенным кодом, считайте полученный десериализованный объект ненадежным.

  • Тот факт, что у типа ExtensionDataObject отсутствуют открытые элементы, не означает, что его данные защищены. Например, если выполняется десериализация из источника привилегированных данных в объект, содержащий некоторое количество данных, а затем этот объект передается частично доверенному коду, этот код может считать данные в ExtensionDataObject путем сериализации объекта. Рассмотрите возможность задания параметра IgnoreExtensionDataObject как true при десериализации из источника привилегированных данных в объект, который затем передается частично доверенному коду.

  • DataContractSerializer и DataContractJsonSerializer поддерживают сериализацию частных, защищенных, внутренних и открытых членов в условиях полного доверия. Однако в среде с частичным уровнем доверия возможна сериализация только открытых членов. Если приложение пытается сериализировать член, отличный от открытого, вызывается SecurityException .

    Чтобы сериализировать внутренние или защищенные внутренние члены в условиях частичного уровня доверия, используйте атрибут сборки InternalsVisibleToAttribute . Этот атрибут позволяет сборке объявлять, что ее внутренние элементы являются видимыми для некоторых других сборок. В этом случае сборка, внутренние члены которой нужно сериализировать, объявляет свои внутренние члены видимыми для System.Runtime.Serialization.dll.

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

    Но есть у него и два существенных недостатка.

    Первый заключается в том, что включаемое по требованию свойство атрибута InternalsVisibleToAttribute действует на уровне сборки. Следовательно, нельзя разрешить сериализацию внутренних членов только одного определенного класса. Чтобы не сериализировать любой конкретный внутренний член, достаточно не добавлять к нему атрибут DataMemberAttribute . Аналогично разработчик может сделать член внутренним, а не частным или защищенным, немного затруднив обеспечение видимости.

    Второй недостаток заключается в том, что частные и защищенные члены все еще не поддерживаются.

    В качестве иллюстрации использования атрибута InternalsVisibleToAttribute в условиях частичного уровня доверия может послужить следующая программа:

        public class Program
        {
            public static void Main(string[] args)
            {
                try
                {
    //              PermissionsHelper.InternetZone corresponds to the PermissionSet for partial trust.
    //              PermissionsHelper.InternetZone.PermitOnly();
                    MemoryStream memoryStream = new MemoryStream();
                    new DataContractSerializer(typeof(DataNode)).
                        WriteObject(memoryStream, new DataNode());
                }
                finally
                {
                    CodeAccessPermission.RevertPermitOnly();
                }
            }
    
            [DataContract]
            public class DataNode
            {
                [DataMember]
                internal string Value = "Default";
            }
        }
    

    В предыдущем примере PermissionsHelper.InternetZone соответствует PermissionSet для частичного уровня доверия. Теперь, без атрибута InternalsVisibleToAttribute , приложение завершится ошибкой SecurityException , создав исключение, указывающее, что недоступные члены не могут быть сериализованы в частичном доверии.

    Однако, если добавить в исходный код следующую строку, программа будет выполнена успешно.

    [assembly:System.Runtime.CompilerServices.InternalsVisibleTo("System.Runtime.Serialization, PublicKey = 00000000000000000400000000000000")]
    

Другие вопросы управления состоянием

Следует упомянуть еще ряд соображений касательно управления состоянием объекта.

  • При использовании модели программирования, основанной на потоковой передаче, с потоковым каналом транспорта сообщение обрабатывается после его получения. Отправитель сообщения может отменить операцию отправки посередине потока, оставив код в непредсказуемом состоянии, если планировалась передача большего содержимого. В целом не следует полагаться на завершаемый поток и выполнять какие-либо действия по операции, основанной на потоке, для которых невозможно выполнить откат в случае отмены потока. Это также применимо к ситуации, когда сообщение может быть неправильно сформировано после потокового тела (например, в нем может отсутствовать закрывающий тег конверта SOAP или может присутствовать второе тело сообщения).

  • Использование функции IExtensibleDataObject может привести к раскрытию конфиденциальных данных. При принятии данных из недоверенного источника в контракты данных с IExtensibleObjectData и последующим повторным их раскрытием для безопасного канала, в котором подписываются сообщения, данные, о которых ничего не известно, потенциально подтверждаются. Более того, отправляемое общее состояние может быть недействительным, если в учетную запись передаются известные и неизвестные блоки данных. Такой ситуации можно избежать, выборочно задав свойство расширения данных как null или выборочно отключив функцию IExtensibleObjectData .

Импорт схемы

Как правило, процедура импорта схемы для создания типов выполняется только на этапе разработки, когда для создания класса клиента в веб-службе используется ServiceModel Metadata Utility Tool (Svcutil.exe) . Однако в более сложных сценариях можно обрабатывать схему во время выполнения. Помните, что такая процедура может стать причиной атак типа "отказ в обслуживании". Импортирование некоторых схем может занять много времени. Никогда не используйте в таких сценариях компонент импорта схемы XmlSerializer , если схемы могут происходить из недоверенного источника.

Угрозы, относящиеся к интеграции ASP.NET AJAX

Когда пользователь реализует WebScriptEnablingBehavior или WebHttpBehaviorпредоставляет конечную точку, которая может принимать сообщения XML и JSON. Однако существует только один набор квот средства чтения, используемых средствами чтения XML и JSON. Некоторые параметры квот могут подходить для одного средства чтения, но при этом быть слишком большими для другого.

При реализации WebScriptEnablingBehaviorпользователю предоставляется вариант раскрытия прокси JavaScript в конечной точке. Следует принимать во внимание следующие вопросы безопасности.

  • Информацию о службе (имена операций, параметров и т. д.) можно получить, изучив прокси JavaScript.

  • При использовании конечной точки JavaScript конфиденциальная и важная информация может сохраняться в кэше веб-браузера клиента.

Замечание по компонентам

WCF — это гибкая и настраиваемая система. Большая часть содержимого этого раздела посвящена наиболее распространенным сценариям использования WCF. Однако можно создавать компоненты WCF различными способами. Важно понимать последствия нарушения безопасности при использовании каждого компонента. В частности:

  • Если требуется использование средств чтения XML, используйте средства чтения, обеспечиваемые классом XmlDictionaryReader , а не любые другие средства чтения. Безопасные средства чтения создаются с помощью методов CreateTextReader, CreateBinaryReaderили CreateMtomReader . Не используйте метод Create . Для средств чтения всегда задавайте безопасные квоты. Механизмы сериализации в WCF защищены только при использовании с безопасными средствами чтения XML из WCF.

  • Если для десериализации потенциально ненадежных данных используется DataContractSerializer , всегда задавайте свойство MaxItemsInObjectGraph .

  • При создании сообщения задавайте параметр maxSizeOfHeaders , если MaxReceivedMessageSize не обеспечивает достаточный уровень защиты.

  • При создании кодировщика всегда настраивайте соответствующие квоты, например MaxSessionSize и MaxBufferSize.

  • Если используется фильтр сообщений XPath, задавайте NodeQuota , чтобы ограничить число XML-узлов, посещаемых фильтром. Не используйте выражения XPath, которые могут потребовать длительного времени для вычисления без посещения большого числа узлов.

  • В целом при создании любого компонента, принимающего квоту, следует понимать последствия нарушения безопасности и задавать для него безопасное значение.

См. также