Поделиться через


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

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

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

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

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

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

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

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

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

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

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

Необходимо учитывать следующее.

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

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

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

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

Квоты

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ms733135.note(ru-ru,VS.100).gifПримечание
Несмотря на то что этот раздел посвящен XML, информация также относится к документам JavaScript Object Notation (JSON). При использовании Сопоставление JSON и XML принципы действия квот аналогичны.

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

Набор сведений XML формирует основу обработки всех сообщений в 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><B><C/></B></A>” равна трем. Использование 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 не обеспечивает защиту, поскольку применяется только к каждому отдельному сообщению. Поэтому в BinaryMessageEncodingBindingElement существует отдельное свойство MaxSessionSize, ограничивающее размер словаря.

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

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

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

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

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

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

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

Условие Важные квоты, которые следует задать

Отсутствие потоковой передачи или потоковая передача небольших сообщений, кодирование 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 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
[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;
        }
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

Даже при использовании с надежными данными входящие данные могут недостаточно точно указывать загружаемый тип, особенно если свойство 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 применяется к типу (или типу, отмеченному [Serializable], но не ISerializable), десериализатор может создать экземпляр такого типа, даже если все конструкторы являются закрытыми или защищены требованиями.

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

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

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

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

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

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

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

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

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

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

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

        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, WCF раскрывает конечную точку, которая может принимать сообщения 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, которые могут потребовать длительного времени для вычисления без посещения большого числа узлов.

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

См. также

Справочник

DataContractSerializer
XmlDictionaryReader
XmlSerializer

Основные понятия

Известные типы контрактов данных