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


Рекомендации по безопасности для данных

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

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

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

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

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

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

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

User-Provided Код и безопасность доступа к коду

Ряд мест в коде запуска инфраструктуры 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)/кодировании может непреднамеренно раскрыть форму графа объектов от отправляющей стороны принимающей стороне.

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

Квоты

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

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

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

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

Многие квоты доступны на транспортном уровне. Это квоты, применяемые определенным каналом транспорта (HTTP, TCP и т. д.). Хотя в этом разделе рассматриваются некоторые из этих квот, эти квоты подробно описаны в разделе " Квоты транспорта".

Уязвимость хэш-файла

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

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

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

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

MaxReceivedMessageSize в одиночку недостаточно для предотвращения всех DDoS-атак. Например, десериализатор может быть вынужден десериализировать глубоко вложенный граф объектов (объект, содержащий еще один объект, и т. д.) входящего сообщения. Оба метода вызова, 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-Based

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

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

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

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

  • Отключите функцию IExtensibleDataObject, установив для свойства IgnoreExtensionDataObject значение ServiceBehaviorAttributetrue. Это гарантирует десериализацию только членов, являющихся частью контракта.

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

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

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

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

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

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

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

** Атаки на медленный поток

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

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

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

Замечание

Хотя этот раздел относится к XML, сведения также применяются к документам нотации объектов JavaScript (JSON). Квоты работают аналогично, используя сопоставление между JSON и 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><B><C/><B></A>" имеет уровень вложенности три. MaxDepth важно по следующим причинам:

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

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

Максимальное количество символов в таблице имен

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

Максимальная длина содержимого строки

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

MaxArrayLength

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

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

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

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

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

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

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

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

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

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

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

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

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

Состояние Важные квоты для задания
Нет потоковой передачи или потоковой передачи небольших сообщений, текста или кодирования 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 предоставляется в основном для упрощения миграции от Remoting .NET Framework к WCF. Дополнительные сведения см. в соответствующем разделе сериализации и десериализации.

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

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

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

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

XmlSerializer-Specific угрозы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    В то же время существует два основных недостатка.

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

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

См. также