Compartir a través de


Consideraciones de seguridad para datos

Al tratar con datos en Windows Communication Foundation (WCF), debe considerar varias categorías de amenaza. La siguiente tabla enumera las principales clases de amenaza relacionadas con el procesamiento de datos. WCF proporciona herramientas para mitigar estas amenazas.

  • Denegación de servicio
    Al recibir datos que no son de confianza, los datos pueden hacer que el lado receptor tenga acceso a una cantidad desproporcionada de varios recursos, como memoria, subprocesos, conexiones disponibles o ciclos de procesador produciendo largos cálculos. Un ataque por denegación de servicio contra un servidor puede provocar que se bloquee y no pueda procesar los mensajes de otros clientes legítimos.
  • Ejecución de código malintencionado
    Los datos entrantes que no son de confianza provocan que el lado receptor ejecute código que no pretendía ejecutar.
  • Divulgación de información
    El atacante remoto obliga a la parte receptora a que responda a sus solicitudes de manera que divulga más información de la que pretende.

Código proporcionado por usuario y seguridad de acceso a código

Varios lugares de la infraestructura Windows Communication Foundation (WCF) ejecutan código proporcionado por el usuario. Por ejemplo, el motor de serialización DataContractSerializer puede llamar a los descriptores de acceso de propiedad proporcionados por el usuario set y a los descriptores de acceso get. La infraestructura de canal WCF también puede llamar a clases proporcionadas por el usuario derivadas de la clase Message.

Es responsabilidad del autor del código asegurarse de que no existan vulnerabilidades de seguridad. Por ejemplo, si crea un tipo de contrato de datos con una propiedad de miembro de datos de tipo entero, y en la implementación del descriptor de acceso set asigna una matriz basada en el valor de la propiedad, expondrá la posibilidad de un ataque por denegación de servicio si un mensaje malintencionado contiene un valor sumamente grande para este miembro de datos. En general, evite cualquier asignación basada en datos entrantes o el procesamiento largo en código proporcionado por el usuario (sobre todo si una cantidad pequeña de los datos entrantes puede provocar un procesamiento largo). Al realizar análisis de seguridad de código proporcionado por usuario, asegúrese de considerar también todos los casos de error (es decir, todas las bifurcaciones de código donde se produzcan excepciones).

El ejemplo último de código proporcionado por usuario es el código dentro de su implementación del servicio para cada operación. La seguridad de su implementación del servicio es su responsabilidad. Es fácil crear inadvertidamente implementaciones de la operación inseguras que puedan producir vulnerabilidades de la denegación de servicio. Por ejemplo, una operación que toma una cadena y devuelve la lista de clientes de una base de datos cuyo nombre empieza con esa cadena. Si está trabajando con una base de datos grande y la cadena que se pasa simplemente es una letra única, su código puede intentar crear un mensaje mayor que toda la memoria disponible, haciendo que se produzca un error en el servicio completo. (OutOfMemoryException no puede recuperarse en .NET Framework y siempre da lugar a la finalización de la aplicación.)

Debería asegurarse de que ningún código malintencionado esté conectado a los varios puntos de la extensibilidad. Esto es especialmente importante cuando la ejecución se realiza con confianza parcial, al tratar con tipos de ensamblados de confianza parcial, o al crear componentes que puede utilizar código de confianza parcial. Para obtener más información, vea "Amenazas de confianza parcial" en una sección posterior.

Tenga en cuenta que cuando se realiza la ejecución en confianza parcial, la infraestructura de serialización del contrato de datos sólo admite un subconjunto limitado de modelos de programación de contratos de datos, por ejemplo, no se admiten miembros de datos privados o tipos que utilizan el atributo SerializableAttribute. Para obtener más información, consulte Confianza parcial.

Evitar la divulgación involuntaria de información

Al diseñar los tipos serializables con seguridad en mente, la divulgación de información es una posible preocupación.

Considere las cuestiones siguientes:

  • El modelo de programación DataContractSerializer permite la exposición de datos privados e internos fuera del tipo o ensamblado durante la serialización. Además, la forma de un tipo puede exponerse durante la exportación del esquema. Asegúrese de entender la proyección de la serialización de su tipo. Si no desea nada expuesto, deshabilite la serialización (por ejemplo, no aplicando el atributo DataMemberAttribute en el caso de un contrato de datos).
  • Sea consciente de que el mismo tipo puede tener varias proyecciones de serialización, dependiendo del serializador en uso. El mismo tipo puede exponer un conjunto de datos cuando se utiliza con DataContractSerializer y otro conjunto de datos cuando se utiliza con XmlSerializer. La utilización accidental del serializador equivocado puede provocar la divulgación de información.
  • Al utilizar XmlSerializer en modo de llamada a procedimiento remoto (RPC)/codificado heredado, puede exponer involuntariamente la forma del gráfico de objeto en el lado emisor al lado receptor.

Prevenir ataques de denegación de servicio

Cuotas

Hacer que el lado receptor asigne una cantidad de memoria significativa es un ataque por denegación de servicio potencial. Mientras esta sección se concentra en problemas de consumo de la memoria emite derivados de los mensajes grandes, se pueden producir otros ataques. Por ejemplo, los mensajes pueden utilizar una cantidad desproporcionada de tiempo de proceso.

Los ataques por denegación de servicio normalmente se mitigan utilizando las cuotas. Cuando se supera una cuota, normalmente se produce una excepción QuotaExceededException. Sin la cuota, un mensaje malintencionado puede provocar que se tenga acceso a toda la memoria disponible, provocando una excepción OutOfMemoryException o que se tenga acceso a todas las pilas disponibles, lo que daría lugar a una StackOverflowException.

El escenario de cuota superada es recuperable; si se encuentra en un servicio en ejecución, se descarta el mensaje que se está procesando actualmente y el servicio sigue ejecutándose y procesa otros mensajes. Sin embargo, los escenarios de memoria insuficiente y desbordamiento de pila no se pueden recuperar en cualquier parte en .NET Framework; el servicio finaliza si encuentra tales excepciones.

Las cuotas en WCF no implican ninguna preasignación. Por ejemplo, si la cuota MaxReceivedMessageSize (presente en varias clases) está establecida en 128 KB, no significa que 128 KB se asigne automáticamente a cada mensaje. La cantidad real asignada depende del tamaño del mensaje entrante real.

Muchas cuotas están disponibles en el nivel de transporte. Son cuotas exigidas por el canal de transporte concreto en uso (HTTP, TCP, etc.). Aunque este tema aborda algunas de estas cuotas, estas cuotas se describen en detalle en Cuotas de transporte.

Limitar el consumo de la memoria sin transmisión por secuencias

El modelo de seguridad alrededor de los mensajes grandes depende de si se utiliza la transmisión por secuencias. En el caso básico, sin transmisión por secuencias, los mensajes están almacenados en el búfer de la memoria. En este caso, utilice la cuota MaxReceivedMessageSize de TransportBindingElement o en los enlaces proporcionados por el sistema, para protegerse frente a mensajes grandes limitando el tamaño máximo del mensaje al que se obtiene acceso. Tenga en cuenta que un servicio puede procesar varios mensajes al mismo tiempo, en cuyo caso todos estarán en memoria. Utilice la característica de limitación de peticiones para mitigar esta amenaza.

También observe que MaxReceivedMessageSize no coloca un límite superior en consumo de la memoria por mensaje, pero lo limita dentro de un factor constante. Por ejemplo, si el MaxReceivedMessageSize es de 1 MB, se recibe un mensaje de 1 MB y, a continuación, se deserializa, se necesitará memoria adicional para contener el gráfico de objeto deserializado, lo que provocará un consumo total de la memoria de más de 1 MB. Por esta razón, evite crear tipos serializable que podrían provocar un consumo significativo de la memoria sin muchos datos entrantes. Por ejemplo, se podría crear la instancia de un contrato de datos "MyContract" con 50 campos de miembro de datos opcionales y 100 campos privados adicionales con la construcción de XML "<MyContract/>". Este XML provocará que se obtenga acceso a la memoria para 150 campos. Observe que los miembros de datos son de forma predeterminada opcionales. El problema se produce cuando un tipo de estas características forma parte de una matriz.

MaxReceivedMessageSize solo no es bastante para evitar todos los ataques por denegación de servicio. Por ejemplo, un mensaje entrante puede obligar al deserializador a deserializar un gráfico de objetos anidado de manera profunda (un objeto que contiene otro objeto que también contiene otro, etc.). DataContractSerializer y XmlSerializer llaman a los métodos de una manera anidada para deserializar tales gráficos. El anidamiento profundo de llamadas al método puede producir un StackOverflowExceptionirrecuperable. Esta amenaza se mitiga estableciendo la cuota MaxDepth para limitar el nivel de anidamiento de XML, como se aborda en la sección "Utilizar XML con seguridad" más adelante en este tema.

Establecer las cuotas adicionales en MaxReceivedMessageSize es especialmente importante al utilizar codificación XML binaria. Utilizar la codificación binaria equivalente de algún modo a la compresión: un grupo pequeño de bytes en el mensaje entrante puede representar muchos datos. Por lo tanto, incluso un mensaje que se ajuste al límite MaxReceivedMessageSize puede ocupar mucha más memoria en su forma totalmente expandida. Para mitigar tales amenazas específicas del XML, todas las cuotas del lector XML se deben establecer correctamente, como se aborda en la sección en la "Utilizar XML con seguridad" más adelante en este tema.

Limitar el consumo de la memoria con transmisión por secuencias

Al tener transmisión por secuencias, puede utilizar un valor MaxReceivedMessageSize pequeño para protegerse contra los ataques por denegación de servicio. Sin embargo, con la transmisión por secuencias se pueden dar más escenarios complicados. Por ejemplo, un servicio de carga de archivo acepta archivos mayores que la memoria total disponible. En este caso, establezca el MaxReceivedMessageSize con un valor sumamente grande, esperando que casi ningún dato se almacene en búfer en memoria y que el mensaje se transmita por secuencias directamente al disco. Si en este caso un mensaje malintencionado puede, de algún modo, obligar a WCF a almacenar datos en búfer en lugar de transmitirlos por secuencias, MaxReceivedMessageSize ya no protegerá frente al mensaje que obtiene acceso a la memoria total disponible.

Para mitigar esta amenaza, la configuración de la cuota concreta existe en varios componentes de procesamiento de datos WCF que limitan el almacenamiento en búfer. El más importante es la propiedad MaxBufferSize en varios elementos de enlace del transporte y enlaces estándares. Al transmitir por secuencias, esta cuota se debería establecer teniendo cuenta la cantidad de memoria máxima que quiere asignar por mensaje. Como con MaxReceivedMessageSize, el valor no coloca un máximo absoluto en consumo de la memoria sino que sólo lo limita dentro de un factor constante. También, como con MaxReceivedMessageSize, sea consciente de la posibilidad de varios mensajes que se procesen simultáneamente.

Detalles de MaxBufferSize

La propiedad MaxBufferSize limita cualquier almacenamiento en búfer masivo WCF. Por ejemplo, WCF siempre almacena en búfer encabezados SOAP y errores de SOAP, así como cualquier parte de MIME encontrada que no esté en el orden de lectura natural en un mensaje del Mecanismo de optimización de transmisión del mensaje (MTOM). Este valor limita la cantidad de almacenamiento en búfer en todos estos casos.

WCF logra esto pasando el valor MaxBufferSize a los varios componentes que pueden almacenar en búfer. Por ejemplo, algunas sobrecargas CreateMessage de la clase Message toman un parámetro maxSizeOfHeaders. WCF pasa el valor MaxBufferSize a este parámetro para limitar la cantidad de almacenamiento en búfer de encabezados SOAP. Es importante establecer este parámetro al utilizar directamente la clase Message. En general, al utilizar un componente en WCF que toma parámetros de cuota, es importante entender las implicaciones de seguridad de estos parámetros y establecerlos correctamente.

El codificador del mensaje de MTOM también tiene un valor MaxBufferSize. Al utilizar enlaces estándares, esto se establece automáticamente en el valor del nivel de transporte MaxBufferSize. Sin embargo, al utilizar el elemento de enlace de codificador del mensaje de MTOM para construir un enlace personalizado, es importante establecer la propiedad MaxBufferSize en un valor seguro cuando se utiliza la transmisión por secuencias.

Ataques de la transmisión por secuencias basados en XML

MaxBufferSize solo no es bastante para garantizar que WCF no pueda ser forzado a almacenar en búfer cuando se espera transmisión por secuencias. Por ejemplo, los lectores XML WCF siempre almacenan en búfer la etiqueta inicial del elemento XML completa al empezar a leer un nuevo elemento. Se hace esto para que se procesen los espacios de nombres y atributos correctamente. Si MaxReceivedMessageSize se configura para ser grande (por ejemplo, para habilitar un escenario de transmisión por secuencias de archivo grande directo a disco), se puede construir un mensaje malintencionado donde el cuerpo entero del mensaje sea una etiqueta inicial de elemento XML grande. Un intento para leerlo provocará OutOfMemoryException. Éste es uno de muchos posibles ataques por denegación de servicio basados en XML que se puede mitigar utilizando las cuotas del lector XML, abordado en la sección "Utilizar XML con seguridad" más adelante en este tema. Al transmitir por secuencias, es especialmente importante establecer todas estas cuotas.

Mezclar los modelos de programación de transmisión por secuencias y almacenamiento en búfer

Muchos posibles ataques surgen de mezclar los modelos de programación de transmisión por secuencias y sin secuencias en el mismo servicio. Suponga que hay un contrato de servicios con dos operaciones: uno toma Stream y otro toma una matriz de algún tipo personalizado. También suponga que MaxReceivedMessageSize está establecido en un valor grande para habilitar la primera operación para procesar las secuencias grandes. Desgraciadamente, esto significa que los mensajes grandes se pueden enviar también a la segunda operación, y el deserializador almacena en búfer los datos en memoria como una matriz antes de que se llame a la operación. Éste es un ataque por denegación de servicio potencial: la cuota MaxBufferSize no limita el tamaño del cuerpo del mensaje, que es con lo que trabaja el deserializador.

Por esta razón, evite mezclar operaciones basadas en secuencias y operaciones sin secuencias en el mismo contrato. Si debe mezclar necesariamente los dos modelos de programación, utilice las precauciones siguientes:

  • Desactive la característica IExtensibleDataObject estableciendo la propiedad IgnoreExtensionDataObject de ServiceBehaviorAttribute en true. Esto garantiza que sólo se deserializan los miembros que forman una parte del contrato.
  • Establezca la propiedad MaxItemsInObjectGraph de DataContractSerializer en un valor seguro. Esta cuota también está disponible en el atributo ServiceBehaviorAttribute o a través de la configuración. Esta cuota limita el número de objetos que se deserializan en un episodio de deserialización. Normalmente, cada parámetro de operación o parte del cuerpo del mensaje en un contrato del mensaje se deserializa en un episodio. Al deserializar matrices, cada entrada de matriz se cuenta como un objeto independiente.
  • Establezca todas las cuotas del lector XML en valores seguros. Preste atención a MaxDepth, MaxStringContentLengthy MaxArrayLength, y evite las cadenas en operaciones de transmisión sin secuencias.
  • Revise la lista de tipos conocidos, teniendo presente que se puede crear una instancia de ellos en cualquier momento (vea la sección “Evitar que se carguen tipos imprevistos” más adelante en este tema).
  • No utilice tipos que implementen la interfaz IXmlSerializable que almacena en búfer muchos datos. No agregue tales tipos a la lista de tipos conocidos.
  • No utilice las matrices XmlElement, XmlNode, matrices Byte o tipos que implementen ISerializable en un contrato.
  • No utilice las matrices XmlElement, XmlNode, matrices Byte o tipos que implementen ISerializable en la lista de tipos conocidos.

Las precauciones anteriores se aplican cuando la operación sin transmisión por secuencias utiliza DataContractSerializer. Nunca mezcle los modelos de programación de transmisión por secuencias y sin secuencias en el mismo servicio si está utilizando el XmlSerializer, porque no tiene la protección de la cuota MaxItemsInObjectGraph.

Ataques de secuencia lentos

Una clase de ataques por denegación de servicio de transmisión por secuencias no implica el consumo de la memoria. En su lugar, el ataque implica un envío o una recepción lenta de los datos. Mientras se espera a que los datos se envíen o reciban, se agotan los recursos como subprocesos y las conexiones disponibles. Esta situación se podría presentar como resultado de un ataque malintencionado o de un remitente/receptor legítimo en una conexión de red lenta.

Para mitigar estos ataques, establezca correctamente los tiempos de espera de transporte. Para obtener más información, consulte Cuotas de transporte. En segundo lugar, nunca utilice Read sincrónicos u operaciones Write al trabajar con secuencias en WCF.

Utilizar XML con seguridad

Nota

Aunque esta sección trata acerca de XML, la información también se aplica a documentos de la notación de objetos JavaScript (JSON). Las cuotas funcionan de manera similar, utilizando Asignación entre JSON y XML.

Lectores XML seguros

El conjunto de información de XML constituye la base de todo el procesamiento de mensajes en WCF. Al aceptar los datos XML de un origen que no es de confianza, existen varias posibilidades de ataque por denegación de servicio que se deben mitigar. WCF proporciona lectores XML especiales, seguros. Estos lectores se crean automáticamente al utilizar una de las codificaciones estándares en WCF (texto, binario o MTOM).

Algunas de las características de seguridad en estos lectores siempre están activas. Por ejemplo, los lectores nunca procesan definiciones de tipo de documento (DTD), que son un origen potencial de ataques por denegación de servicio y nunca deberían aparecer en mensajes SOAP legítimos. Otras características de seguridad incluyen cuotas del lector que se deben configurar, que se describen en la sección siguiente.

Al trabajar directamente con lectores XML (como al escribir su propio codificador personalizado o al trabajar directamente con la clase Message ), utilice siempre WCF los lectores seguros cuando haya una oportunidad de trabajar con datos que no sean de confianza. Cree los lectores seguros llamando a una de las sobrecargas estáticas del método del generador de CreateTextReader, CreateBinaryReadero CreateMtomReader en la clase XmlDictionaryReader. Al crear un lector, pase valores de cuota seguros. No llame a las sobrecargas de método Create. Éstos no crean un lector WCF. En su lugar, se crea un lector que no está protegido por las características de seguridad descritas en esta sección.

Cuotas del lector

Los lectores XML seguros tienen cinco cuotas configurables. Normalmente se configuran utilizando la propiedad ReaderQuotas en los elementos de enlace de la codificación o enlaces estándares, o utilizando un objeto XmlDictionaryReaderQuotas pasado al crear un lector.

MaxBytesPerRead

Esta cuota limita el número de bytes que se leen en una operación Read única al leer la etiqueta de inicio de elemento y sus atributos. (En casos de transmisión sin secuencias, el propio nombre del elemento no se cuenta para la cuota.) MaxBytesPerRead es importante por los motivos siguientes:

  • El nombre de elemento y sus atributos siempre están almacenados en búfer en memoria cuando se leen. Por consiguiente, es importante establecer correctamente esta cuota en modo de transmisión por secuencias para evitar el almacenado en búfer excesivo. Vea la sección de la cuota MaxDepth para obtener información acerca de la cantidad real de almacenamiento en búfer que tiene lugar.
  • Tener demasiados atributos XML puede agotar el tiempo de proceso desproporcionado porque se tiene que comprobar la univocidad de los nombres de atributo. MaxBytesPerRead mitiga esta amenaza.

MaxDepth

Esta cuota limita la profundidad máxima del anidamiento de elementos XML. Por ejemplo, el documento “<A><B><C/></B></A>” tiene una profundidad de anidamiento de tres. MaxDepth es importante por los motivos siguientes:

  • MaxDepth interactúa con MaxBytesPerRead: el lector siempre mantiene los datos en memoria para el elemento vigente y todos sus antecesores, por lo que el consumo máximo de la memoria del lector es proporcional al producto de estos dos valores.
  • Al deserializar un gráfico de objetos profundamente anidado, el deserializador se ve obligado a obtener acceso a la pila completa e iniciar una StackOverflowException irrecuperable. Existe una correlación directa entre anidamiento de XML y anidamiento de objeto para DataContractSerializer y XmlSerializer. Utilice MaxDepth para mitigar esta amenaza.

MaxNameTableCharCount

Esta cuota limita el tamaño de la tabla de nombresdel lector. La tabla de nombres contiene ciertas cadenas (como espacios de nombres y prefijos) que se encuentra al procesar un documento XML. Puesto que estas cadenas están almacenadas en búfer en memoria, establezca esta cuota para evitar el almacenamiento en búfer excesivo cuando se espera la transmisión por secuencias.

MaxStringContentLength

Esta cuota limita el tamaño máximo de la cadena que el lector XML devuelve. Esta cuota no limita el consumo de la memoria en el propio lector XML, sino en el componente que está utilizando el lector. Por ejemplo, cuando DataContractSerializer utiliza un lector protegido con MaxStringContentLength, no deserializa cadenas mayores que esta cuota. Al utilizar directamente la clase XmlDictionaryReader, no todos los métodos respetan esta cuota, sólo los métodos que están diseñados específicamente para leer cadenas, como el método ReadContentAsString. Esta cuota no afecta a la propiedad Value en el lector y por lo tanto no se debería utilizar cuando la protección que proporciona esta cuota es necesaria.

MaxArrayLength

Esta cuota limita el tamaño máximo de una matriz de primitivas que devuelve el lector XML, incluidas las matrices de bytes. Esta cuota no limita el consumo de la memoria en el propio lector XML, sino en cualquier componente que esté utilizando el lector. Por ejemplo, cuando DataContractSerializer utiliza un lector protegido con MaxArrayLength, no deserializa matrices de bytes mayores que esta cuota. Es importante establecer esta cuota al intentar mezclar modelos de programación de transmisión por secuencias y almacenamiento en búfer en un contrato único. Tenga presente que al utilizar directamente la clase XmlDictionaryReader, sólo los métodos que están diseñados específicamente para leer matrices de tamaño arbitrario de ciertos tipos primitivos, como ReadInt32Array, respetan esta cuota.

Amenazas específicas a la codificación binaria

Los soportes de codificación XML binaria WCF incluyen una característica de cadenas de diccionario. Una cadena grande puede estar codificada utilizando sólo unos bytes. Esto permite considerables ganancias de rendimiento, pero introduce nuevas amenazas de la denegación de servicio que se deben mitigar.

Hay dos tipos de diccionarios: estático y dinámico. El diccionario estático es una lista integrada de cadenas largas que se puede representar utilizando un código corto en la codificación binaria. Esta lista de cadenas se fija cuando el lector se crea y no se puede modificar. Ninguna de las cadenas en el diccionario estático que WCF utiliza de forma predeterminada es suficientemente grande para provocar una amenaza seria de la denegación de servicio, aunque todavía se pueden utilizar en un ataque de expansión de diccionario. En escenarios avanzados donde proporciona su propio diccionario estático, vaya con cuidado al introducir cadenas de diccionario grandes.

La característica de diccionarios dinámicos permite a los mensajes definir sus propias cadenas y asociarlas a códigos cortos. Estas asignaciones de cadena a código se mantienen en memoria durante la sesión completa de comunicación, de manera que los mensajes subsiguientes no tienen que reenviar las cadenas y pueden utilizar los códigos ya definidos. Estas cadenas pueden ser de longitud arbitraria y por consiguiente provocar una amenaza más seria que aquellas en el diccionario estático.

La primera amenaza que se debe mitigar es la posibilidad de que el diccionario dinámico (la tabla de asignación de cadena a código) se vuelva demasiado grande. Este diccionario se puede expandir a lo largo del curso de varios mensajes y así que la cuota MaxReceivedMessageSize no proporciona ninguna protección porque sólo se aplica separadamente a cada mensaje. Por consiguiente, existe una propiedad MaxSessionSize independiente en BinaryMessageEncodingBindingElement que limita el tamaño del diccionario.

A diferencia de la mayoría de las otras cuotas, esta cuota también se aplica al escribir los mensajes. Si se supera al leer un mensaje, se inicia QuotaExceededException como de costumbre. Si se supera al escribir un mensaje, cualquier cadena que provoque que la cuota se supere se escribe como es, sin utilizar la característica de diccionarios dinámicos.

Amenazas de expansión de diccionario

Una clase significativa de ataques específicos binarios deriva de la expansión del diccionario. Un mensaje pequeño en formato binario se puede convertir en un mensaje muy grande en forma textual totalmente expandida si realiza uso extenso de la característica de diccionarios de cadena. La cuota MaxSessionSize limita el factor de expansión para las cadenas del diccionario dinámico, ya que ninguna cadena del diccionario dinámico supera el tamaño máximo del diccionario completo.

Las propiedades MaxNameTableCharCount, MaxStringContentLengthy MaxArrayLength sólo limitan el consumo de memoria. Normalmente no se necesitan para mitigar ninguna amenaza en el uso de transmisión sin secuencias porque MaxReceivedMessageSize ya limita la utilización de memoria. Sin embargo, MaxReceivedMessageSize cuenta los bytes de la pre-expansión. Cuando se utiliza la codificación binaria, el consumo de la memoria podría exceder potencialmente MaxReceivedMessageSize, limitado sólo por un factor de MaxSessionSize. Por esta razón, es importante establecer siempre todas las cuotas del lector (sobre todo MaxStringContentLength) al utilizar la codificación binaria.

Al utilizar la codificación binaria junto con DataContractSerializer, la interfaz IExtensibleDataObject se puede emplear mal para organizar un ataque de expansión de diccionario. Esta interfaz proporciona esencialmente el almacenamiento ilimitado para datos arbitrarios que no forman parte del contrato. Si no se pueden establecer las cuotas suficientemente bajas de manera que MaxSessionSize multiplicado por MaxReceivedMessageSize no suponga un problema, deshabilite la característica IExtensibleDataObject al utilizar la codificación binaria. Establezca la propiedad IgnoreExtensionDataObject true en el atributo ServiceBehaviorAttribute. Alternativamente, no implemente la interfaz IExtensibleDataObject. Para obtener más información, consulte Contratos de datos compatibles con el reenvío.

Resumen de las cuotas

La tabla siguiente resume la guía sobre las cuotas.

Condición Cuotas importantes a establecer

Ninguna transmisión por secuencias o transmisión por secuencias de mensajes pequeños, texto o codificación de MTOM

MaxReceivedMessageSize, MaxBytesPerRead y MaxDepth

Ninguna transmisión por secuencias o transmisión por secuencias de mensajes pequeños, codificación binaria

MaxReceivedMessageSize, MaxSessionSize y todas las ReaderQuotas

Transmisión por secuencias de mensajes grandes, texto o codificación de MTOM

MaxBufferSize y todas las ReaderQuotas

Transmisión por secuencias de mensajes grandes, codificación binaria

MaxBufferSize, MaxSessionSize y todas las ReaderQuotas

  • Siempre se deben establecer tiempos de espera del nivel de transporte y no se debe utilizar nunca la lectura/escritura sincrónica cuando se utilice la transmisión por secuencias, independientemente de si está transmitiendo por secuencias mensajes grandes o pequeños.
  • Cuando dude sobre una cuota, establézcala en un valor seguro en lugar de dejarla abierta.

Evitar la ejecución de código malintencionado

Las clases generales siguientes de amenazas pueden ejecutar código y tener los efectos imprevistos:

  • El deserializador carga un tipo malintencionado, no seguro o que afecta a la seguridad.
  • Un mensaje entrante hace que el deserializador construya una instancia de un tipo normalmente seguro de de manera que tiene consecuencias imprevistas.

Las secciones siguientes abordan detalladamente estas clases de amenazas.

DataContractSerializer

(Para obtener información de seguridad en XmlSerializer, consulte la documentación pertinente.) El modelo de seguridad para XmlSerializer es similar al de DataContractSerializery difiere principalmente en detalles. Por ejemplo, el atributo XmlIncludeAttribute se utiliza para la inclusión de tipo en lugar del atributo KnownTypeAttribute. Sin embargo, algunas amenazas únicas de XmlSerializer se discuten más adelante en este tema.

Evitar que se carguen tipos imprevistos

Cargar tipos imprevistos puede tener consecuencias significativas, tanto si el tipo es malintencionado o simplemente tiene efectos secundarios que afectan a la seguridad. Un tipo puede contener vulnerabilidad de seguridad explotable, realizar acciones que afecten a la seguridad en su constructor o constructor de clase, tener una superficie de memoria grande que facilite los ataques por denegación de servicio, o bien puede provocar excepciones no recuperables. Los tipos pueden tener constructores de clase que se ejecutan en cuanto se cargue el tipo y antes de que se cree cualquier instancia. Por estas razones, es importante controlar el conjunto de tipos que el deserializador puede cargar.

DataContractSerializer deserializa con acoplamiento separado. Nunca lee el tipo de Common Language Runtime (CLR) y los nombres de ensamblado de los datos entrantes. Esto es similar al comportamiento de XmlSerializer, pero difiere del comportamiento de NetDataContractSerializer, BinaryFormatter, y SoapFormatter. El acoplamiento separado introduce un grado de seguridad, porque el atacante remoto no puede indicar que se cargue un tipo arbitrario simplemente denominando ese tipo en el mensaje.

Siempre se permite que DataContractSerializer cargue un tipo que se espera actualmente según el contrato. Por ejemplo, si un contrato de datos tiene un miembro de datos de tipo Customer, DataContractSerializer puede cargar el tipo Customer cuando deserializa este miembro de datos.

Además, DataContractSerializer admite el polimorfismo. Un miembro de datos se puede declarar como Object, pero los datos entrantes pueden contener una instancia Customer. Esto sólo es posible si el tipo Customer se ha dado a conocer al deserializador a través de uno de estos mecanismos:

  • Atributo KnownTypeAttribute aplicado al tipo.
  • Atributo KnownTypeAttribute que especifica un método que devuelve una lista de tipos.
  • Atributo ServiceKnownTypeAttribute.
  • Sección de configuración KnownTypes .
  • Una lista de tipos conocidos pasada explícitamente a DataContractSerializer durante la construcción, si se utiliza directamente el serializador.

Cada uno de estos mecanismos aumenta el área de superficie introduciendo más tipos que el deserializador puede cargar. Controle cada uno de estos mecanismos para asegurarse de que ningún tipo malintencionado o imprevisto se agregue a la lista de tipos conocidos.

Una vez un tipo conocido está en el ámbito, se puede cargar en cualquier momento y se pueden crear las instancias del tipo, aun cuando el contrato prohíba realmente utilizarlo. Por ejemplo, suponga que el tipo "MyDangerousType" se agregue a la lista de tipos conocidos utilizando uno de los mecanismos anteriores. Esto significa que:

  • Se carga MyDangerousType y su constructor de clase se ejecuta.
  • Incluso al deserializar un contrato de datos con un miembro de datos de cadena, un mensaje malintencionado puede provocar la creación de una instancia de MyDangerousType. El código en MyDangerousType, como establecedores de propiedades, se puede ejecutar. Una vez hecho, el deserializador intenta asignar esta instancia al miembro de dato de cadena y se produce un error con una excepción.

Al escribir un método que devuelve una lista de tipos conocidos o al pasar directamente una lista al constructor DataContractSerializer, asegúrese de que el código que prepara la lista sea seguro y funcione sólo en datos que sean de confianza.

Si especifica los tipos conocidos en configuración, asegúrese de que el archivo de configuración sea seguro. Utilice siempre nombres seguros en configuración (especificando la clave pública del ensamblado firmado donde el tipo reside), pero no especifique la versión del tipo a cargar. El cargador de tipo escoge automáticamente la última versión, si es posible. Si especifica una versión determinada en configuración, corre el riesgo siguiente: un tipo puede tener una vulnerabilidad de seguridad que se puede fijar en una versión futura, pero la versión vulnerable todavía se carga porque está explícitamente especificado en configuración.

Tener demasiados tipos conocidos tiene otra consecuencia: DataContractSerializer crea una memoria caché de código de serialización/deserialización en el dominio de aplicación, con una entrada para cada tipo que debe serializar y deserializar. Esta memoria caché nunca se borra mientras se esté ejecutando el dominio de aplicación. Por consiguiente, un atacante que es consciente que una aplicación utiliza muchos tipos conocidos puede desencadenar la deserialización de todos estos tipos, haciendo que la caché utilice una cantidad desproporcionadamente grande de memoria.

Evitando que los tipos estén en un estado imprevisto

Un tipo puede tener restricciones de coherencia internas que se deban cumplir. Se debe proceder con cuidado para evitar la ruptura de estas restricciones durante la deserialización.

El siguiente ejemplo de un tipo representa el estado de una cámara estanca en una nave espacial y exige la restricción de que las puertas internas y externas no pueden abrirse al mismo tiempo.

Un atacante puede enviar un mensaje malintencionado como esté, evitando las restricciones y poniendo el objeto en un estado no válido, lo que puede tener consecuencias imprevistas e imprevisibles.

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

Esta situación se puede evitar siendo consciente de los puntos siguientes:

  • Cuando DataContractSerializer deserializa la mayoría de las clases, los constructores no se ejecutan. Por consiguiente, no confíe en cualquier administración de estados hecha en el constructor.
  • Utilice las devoluciones de llamada para asegurarse de que el objeto esté en un estado válido. La devolución de llamada marcada con el atributo OnDeserializedAttribute es especialmente útil porque se ejecuta después de que la deserialización se haya completado y tiene una oportunidad para examinar y corregir el estado total. Para obtener más información, consulte Devoluciones de llamadas en la serialización tolerante a versiones.
  • No diseñe los tipos de contrato de datos para confiar en cualquier orden determinado en el que se deba llamar a los establecedores de propiedad.
  • Tenga cuidado utilizando los tipos heredados marcado con el atributo SerializableAttribute. Muchos de ellos han sido diseñados para trabajar sólo con comunicación remota .NET Framework para el uso con datos que son de confianza. Los tipos existentes marcados con este atributo pueden no haber sido diseñados con la seguridad del estado en mente.
  • No confíe en la propiedad IsRequired del atributo DataMemberAttribute para garantizar la presencia de datos en lo relativo a la seguridad del estado. Los datos siempre podrían ser null, zeroo invalid.
  • Nunca confíe en un gráfico de objeto deserializado de un origen de datos que no es de confianza sin validarlo primero. Cada objeto individual puede estar en un estado coherente, pero el gráfico de objeto en conjunto puede no estarlo. Además, aun cuando el modo de preservación de gráfico de objeto está deshabilitado, el gráfico deserializado puede tener varias referencias al mismo objeto o tener referencias circulares. Para obtener más información, consulte Serialización y deserialización.

Utilizar NetDataContractSerializer con seguridad

NetDataContractSerializer es un motor de serialización que utiliza el acoplamiento apretado a los tipos. Es similar a BinaryFormatter y SoapFormatter. Es decir, determina para qué tipo se deben crear instancias leyendo el ensamblado .NET Framework y el nombre de tipo de los datos entrantes. Aunque forma una parte de WCF, no hay ninguna manera proporcionada de conectarse a este motor de la serialización; se debe escribir código personalizado. NetDataContractSerializer se proporciona principalmente para facilitar la migración de la comunicación remota .NET Framework a la sección relevante WCF. Para obtener más información, consulte de Serialización y deserialización.

Dado que el propio mensaje puede indicar que se puede cargar cualquier tipo, el mecanismo NetDataContractSerializer es inherentemente inseguro y debería utilizarse sólo con datos que sean de confianza. Es posible hacerlo seguro escribiendo un enlazador de tipo seguro y que limite el tipo que sólo permita que se carguen los tipos seguros (utilizar la propiedad Binder ).

Incluso cuando se utiliza con datos que son de confianza, los datos entrantes pueden especificar insuficientemente el tipo a cargar, sobre todo si la propiedad AssemblyFormat está establecida en Simple. Cualquiera con acceso al directorio de la aplicación o a la caché de ensamblados global puede sustituir un tipo malintencionado en lugar del que se supone que debe cargarse. Garantice siempre la seguridad del directorio de su aplicación y de la caché de ensamblados global estableciendo correctamente los permisos.

En general, si permite que el código de confianza parcial tenga acceso a la instancia de NetDataContractSerializer o controle de algún otro modo el selector de suplentes (ISurrogateSelector) o el enlazador de serialización (SerializationBinder), el código puede ejercer un gran control sobre el proceso de serialización o deserialización. Por ejemplo, puede insertar tipos arbitrarios, provocar la divulgación de información, manipular el gráfico de objetos resultante o los datos serializados, o desbordar la secuencia serializada resultante.

Otra preocupación de seguridad con NetDataContractSerializer es una denegación de servicio, no una amenaza de ejecución de código malintencionado. Al utilizar NetDataContractSerializer, establezca siempre la cuota MaxItemsInObjectGraph en un valor seguro. Es fácil construir un mensaje malintencionado pequeño que asigne una matriz de objetos cuyo tamaño está sólo limitado por esta cuota.

Amenazas específicas de XmlSerializer

El modelo de seguridad XmlSerializer es similar al de DataContractSerializer. Sin embargo, algunas amenazas son exclusivas de XmlSerializer.

XmlSerializer genera ensamblados de serialización en tiempo de ejecución que contienen código que realmente serializa y deserializa; estos ensamblados se crean en un directorio de archivos temporales. Si algún otro proceso o usuario tiene derechos de acceso a ese directorio, pueden sobrescribir el código de serialización/deserialización con código arbitrario. XmlSerializer ejecuta a continuación este código utilizando su contexto de seguridad, en lugar del código de serialización/deserialización. Asegúrese de que los permisos estén correctamente establecidos en el directorio de archivos temporales para evitar que esto suceda.

XmlSerializer también tiene un modo en el que utiliza los ensamblados de serialización generados previamente en lugar de generarlos en tiempo de ejecución. Este modo se activa cuando XmlSerializer puede encontrar un ensamblado de serialización conveniente. XmlSerializer comprueba si se firmó o no el ensamblado de serialización con la misma clave utilizada para firmar el ensamblado que contiene los tipos que se están serializando. Esto proporciona protección e impide que los ensamblados malintencionados se oculten como ensamblados de serialización. Sin embargo, si no se firma el ensamblado que contiene los tipos serializables, XmlSerializer no puede realizar esta comprobación y utiliza cualquier ensamblado con el nombre correcto. Esto hace que sea posible ejecutar código malintencionado. Firme siempre los ensamblados que contienen sus tipos serializables o controle bien el acceso al directorio de su aplicación y la caché de ensamblados global para evitar la introducción de ensamblados malintencionados.

XmlSerializer puede estar sujeto a un ataque por denegación de servicio. XmlSerializer no tiene una cuota MaxItemsInObjectGraph (como está disponible en DataContractSerializer). Por lo tanto, deserializará una cantidad arbitraria de objetos, limitada sólo por el tamaño del mensaje.

Otras amenazas de confianza parcial

Tenga en cuenta las preocupaciones siguientes con respecto a las amenazas de código ejecutado con confianza parcial. Estas amenazas incluyen el código malintencionado de confianza parcial así como el código malintencionado de confianza parcial en combinación con otros escenarios de ataque, por ejemplo, código de confianza parcial que construye una cadena concreta y después la deserializa.

  • Al utilizar cualquier componente de serialización, nunca valide ningún permiso antes del uso, incluso si el escenario de serialización completo esté dentro del ámbito de su validez y no esté tratando con datos u objetos que no son de confianza. Tal uso puede provocar vulnerabilidades de seguridad.
  • En los casos en los que el código de confianza parcial tiene control sobre el proceso de serialización, bien a través de puntos de extensibilidad (suplentes), tipos que se están serializando o de otra manera, el código de confianza parcial puede provocar que el serializador produzca una gran cantidad de datos en la secuencia serializada, lo que podría dar lugar a la denegación del servicio (DoS) al receptor de la secuencia. Si serializa datos previstos para un destino vulnerable a las amenazas de denegación de servicio, no serialice tipos de confianza parcial o bien permita que el código de confianza parcial controle la serialización.
  • Si permite que el código de confianza parcial tenga acceso a la instancia de DataContractSerializer o que controle de algún otro modo el Suplentes de contratos de datos, puede ejercer un gran control sobre el proceso de serialización o deserialización. Por ejemplo, puede insertar tipos arbitrarios, provocar la divulgación de información, manipular el gráfico de objetos resultante o los datos serializados, o bien desbordar la secuencia serializada resultante. Se describe una amenaza NetDataContractSerializer equivalente en la sección "Utilizar NetDataContractSerializer con seguridad".
  • Si el atributo DataContractAttribute se aplica a un tipo (o el tipo marcado como [Serializable] pero no es ISerializable), el deserializador puede crear una instancia de este tipo aun cuando todos los constructores no sean públicos o estén protegidos por peticiones.
  • Nunca confíe en el resultado de la deserialización salvo que los datos que se deserializan sean de confianza y esté seguro de que todos los tipos conocidos son de su confianza. Tenga en cuenta que cuando se realiza la ejecución en confianza parcial, los tipos conocidos no se cargan desde el archivo de configuración de la aplicación, sino desde el archivo de configuración del equipo.
  • Si pasa una instancia de DataContractSerializer con un suplente agregado a un código de confianza parcial, el código puede cambiar cualquier valor modificable de ese suplente.
  • Para un objeto deserializado, si el lector XML (o los datos que contiene) procede del código de confianza parcial, trate el objeto deserializado resultante como datos que no son de confianza.
  • El hecho de que el tipo ExtensionDataObject no tenga ningún miembro público no significa que los datos dentro de él sean seguros. Por ejemplo, si deserializa desde un origen de datos privilegiado a un objeto en el que residen algunos datos, entregue el objeto al código de confianza parcial, éste puede leer los datos del ExtensionDataObject serializando el objeto. Considere establecer IgnoreExtensionDataObject en true cuando deserialice desde un origen de datos privilegiado a un objeto que después se pasa al código de confianza parcial.

Otras preocupaciones de administración de estados

Cabe mencionar otras preocupaciones con respecto a la administración de estados de objeto:

  • Al utilizar el modelo de programación basado en secuencias con un transporte de transmisión por secuencias, el procesamiento del mensaje se produce cuando llega el mensaje. El remitente del mensaje puede anular la operación de envío en medio de la secuencia, dejando su código en un estado imprevisible si se esperaba más contenido. En general, no confíe en que la secuencia sea completa y no realice ningún trabajo en una operación basada en secuencias que no se pueda deshacer en caso de que se anule la secuencia. Esto también se aplica a la situación donde un mensaje puede estar malformado después del cuerpo de transmisión por secuencias (por ejemplo, puede faltar una etiqueta de cierre para el sobre SOAP o puede tener un segundo cuerpo del mensaje).
  • Utilizar la característica IExtensibleDataObject puede provocar que se emitan datos confidenciales. Si está aceptando datos desde un origen que no es de confianza a los contratos de datos con IExtensibleObjectData y después vuelve a emitir en un canal seguro en el que se firman los mensajes, está respondiendo potencialmente de datos sobre los que no sabe absolutamente nada. Es más, el estado total que está enviando puede no ser válido si tiene en cuenta tanto las partes conocidas como desconocidas de los datos. Evite esta situación estableciendo selectivamente la propiedad de datos de extensión en null o deshabilitando selectivamente la característica IExtensibleObjectData.

Importación de esquema

Normalmente, el proceso de importar el esquema para generar los tipos sólo pasa en tiempo de diseño, por ejemplo, al utilizar ServiceModel Metadata Utility Tool (Svcutil.exe) en un Servicio Web para generar una clase de cliente. Sin embargo, en escenarios más avanzados, puede procesar el esquema en tiempo de ejecución. Tenga en cuenta que hacerlo puede exponerle a los riesgos de la denegación de servicio. Algunos esquemas pueden tardar mucho tiempo en importarse. No utilice nunca el componente de importación de esquema XmlSerializer en tales escenarios si es posible que los esquemas procedan de un origen que no es de confianza.

Amenazas específicas de la integración de AJAX de ASP.NET

Cuando el usuario implementa WebScriptEnablingBehavior o WebHttpBehavior, WCF expone un extremo que puede aceptar mensajes XML y JSON. No obstante, sólo existe un conjunto de cuotas de lector, utilizado tanto por el lector XML como por el lector JSON. Algunos valores de cuota pueden ser apropiados para un lector pero demasiado grandes para otro.

Al implementar WebScriptEnablingBehavior, el usuario tiene la opción de exponer un proxy de JavaScript en el extremo. Deben tenerse en cuenta las siguientes cuestiones relacionadas con la seguridad:

  • Puede obtenerse información acerca del servicio (nombres de operación, nombres de parámetro, etc.) mediante el examen del proxy de JavaScript.
  • Al utilizar el extremo de JavaScript, la información confidencial y privada puede retenerse en la memoria caché del explorador web cliente.

Un nota sobre componentes

WCF es un sistema flexible y personalizable. La mayoría del contenido de este tema se centra en los escenarios de uso WCF más comunes. Sin embargo, es posible crear componentes WCF de muchas maneras diferentes. Es importante entender las implicaciones de seguridad de utilizar cada componente. En concreto:

  • Cuando deba utilizar lectores XML, utilice los lectores que la clase XmlDictionaryReader proporciona en oposición a cualquier otro lector. Los lectores seguros se crean utilizando los métodos CreateTextReader, CreateBinaryReadero CreateMtomReader. No utilice el método Create. Configure siempre los lectores con cuotas seguras. Los motores de serialización en WCF sólo son seguros cuando se utilizan con lectores XML seguros de WCF.
  • Al utilizar DataContractSerializer para deserializar los datos que potencialmente no son de confianza, establezca siempre la propiedad MaxItemsInObjectGraph.
  • Al crear un mensaje, establezca el parámetro maxSizeOfHeaders si MaxReceivedMessageSize no proporciona protección suficiente.
  • Al crear un codificador, configure siempre las cuotas pertinentes, como MaxSessionSize y MaxBufferSize.
  • Al utilizar un filtro de mensajes XPath, establezca NodeQuota para limitar la cantidad de nodos XML que el filtro visita. No utilice expresiones XPath que podrían tardar mucho tiempo en calcularse sin visitar muchos nodos.
  • En general, al utilizar cualquier componente que acepte una cuota, entienda sus implicaciones de seguridad y establézcalo en un valor seguro.

Consulte también

Referencia

DataContractSerializer
XmlDictionaryReader
XmlSerializer

Conceptos

Tipos conocidos de contratos de datos