Considerações de segurança para dados
Ao lidar com dados no Windows Communication Foundation (WCF), você deve considerar várias categorias de ameaça. A lista a seguir mostra as classes de ameaça mais importantes relacionadas ao processamento de dados. O WCF fornece ferramentas para mitigar essas ameaças.
Denial-of-service
Ao receber dados não confiáveis, os dados podem fazer com que o lado recetor acesse uma quantidade desproporcional de vários recursos, como memória, threads, conexões disponíveis ou ciclos do processador, causando cálculos longos. Um ataque de negação de serviço contra um servidor pode fazer com que ele falhe e seja incapaz de processar mensagens de outros clientes legítimos.
Execução de código malicioso
A entrada de dados não confiáveis faz com que o lado recetor execute um código que não pretendia.
Divulgação de informações
O invasor remoto força a parte recetora a responder às suas solicitações de forma a divulgar mais informações do que pretende.
Código fornecido pelo usuário e segurança de acesso ao código
Vários locais na infraestrutura do Windows Communication Foundation (WCF) executam o código fornecido pelo usuário. Por exemplo, o DataContractSerializer mecanismo de serialização pode chamar acessadores de propriedade set
fornecidos pelo usuário e get
acessadores. A infraestrutura de canal WCF também pode chamar classes derivadas fornecidas pelo usuário da Message classe.
É da responsabilidade do autor do código garantir que não existem vulnerabilidades de segurança. Por exemplo, se você criar um tipo de contrato de dados com uma propriedade de membro de dados do tipo inteiro e, na implementação do set
acessador, alocar uma matriz com base no valor da propriedade, exporá a possibilidade de um ataque de negação de serviço se uma mensagem mal-intencionada contiver um valor extremamente grande para esse membro de dados. Em geral, evite alocações com base em dados recebidos ou processamento demorado em código fornecido pelo usuário (especialmente se o processamento demorado pode ser causado por uma pequena quantidade de dados recebidos). Ao executar a análise de segurança do código fornecido pelo usuário, certifique-se de considerar também todos os casos de falha (ou seja, todas as ramificações de código onde as exceções são lançadas).
O melhor exemplo de código fornecido pelo usuário é o código dentro da implementação do serviço para cada operação. A segurança da implementação do seu serviço é da sua responsabilidade. É fácil criar inadvertidamente implementações de operação inseguras que podem resultar em vulnerabilidades de negação de serviço. Por exemplo, uma operação que usa uma cadeia de caracteres e retorna a lista de clientes de um banco de dados cujo nome começa com essa cadeia de caracteres. Se você estiver trabalhando com um banco de dados grande e a cadeia de caracteres que está sendo passada for apenas uma única letra, seu código pode tentar criar uma mensagem maior do que toda a memória disponível, fazendo com que todo o serviço falhe. (Um OutOfMemoryException não é recuperável no .NET Framework e sempre resulta no encerramento do seu aplicativo.)
Você deve garantir que nenhum código mal-intencionado esteja conectado aos vários pontos de extensibilidade. Isso é especialmente relevante quando executado sob confiança parcial, lidando com tipos de assemblies parcialmente confiáveis ou criando componentes utilizáveis por código parcialmente confiável. Para obter mais informações, consulte "Ameaças de confiança parcial" em uma seção posterior.
Observe que, ao ser executada em confiança parcial, a infraestrutura de serialização do contrato de dados oferece suporte apenas a um subconjunto limitado do modelo de programação do contrato de dados - por exemplo, não há suporte para membros de dados privados ou tipos que usam o SerializableAttribute atributo. Para obter mais informações, consulte Confiança parcial.
Nota
O CAS (Code Access Security) foi preterido em todas as versões do .NET Framework e do .NET. As versões recentes do .NET não respeitam as anotações do CAS e produzem erros se as APIs relacionadas ao CAS forem usadas. Os desenvolvedores devem procurar meios alternativos de realizar tarefas de segurança.
Evitar a divulgação não intencional de informações
Ao projetar tipos serializáveis com a segurança em mente, a divulgação de informações é uma possível preocupação.
Considere os pontos seguintes:
O DataContractSerializer modelo de programação permite a exposição de dados privados e internos fora do tipo ou montagem durante a serialização. Além disso, a forma de um tipo pode ser exposta durante a exportação do esquema. Certifique-se de entender a projeção de serialização do seu tipo. Se você não quiser que nada seja exposto, desative a serialização (por exemplo, não aplicando o DataMemberAttribute atributo no caso de um contrato de dados).
Lembre-se de que o mesmo tipo pode ter várias projeções de serialização, dependendo do serializador em uso. O mesmo tipo pode expor um conjunto de dados quando usado com o DataContractSerializer e outro conjunto de dados quando usado com o XmlSerializer. O uso acidental do serializador errado pode levar à divulgação de informações.
O uso da XmlSerializer chamada de procedimento remoto (RPC)/modo codificado herdado pode expor involuntariamente a forma do gráfico de objeto no lado de envio para o lado recetor.
Prevenção de ataques de negação de serviço
Quotas
Fazer com que o lado recetor aloque uma quantidade significativa de memória é um potencial ataque de negação de serviço. Embora esta seção se concentre em problemas de consumo de memória decorrentes de mensagens grandes, outros ataques podem ocorrer. Por exemplo, as mensagens podem utilizar uma quantidade desproporcionada de tempo de processamento.
Os ataques de negação de serviço geralmente são mitigados usando cotas. Quando uma quota é excedida, é normalmente lançada uma QuotaExceededException exceção. Sem a cota, uma mensagem maliciosa pode fazer com que toda a memória disponível seja acessada, resultando em uma OutOfMemoryException exceção, ou todas as pilhas disponíveis sejam acessadas, resultando em um StackOverflowExceptionarquivo .
O cenário de quota excedida é recuperável; Se encontrado em um serviço em execução, a mensagem que está sendo processada no momento é descartada e o serviço continua em execução e processa outras mensagens. Os cenários de falta de memória e estouro de pilha, no entanto, não são recuperáveis em nenhum lugar no .NET Framework; O serviço termina se encontrar tais exceções.
As quotas no WCF não envolvem qualquer pré-atribuição. Por exemplo, se a cota MaxReceivedMessageSize (encontrada em várias classes) estiver definida como 128 KB, isso não significa que 128 KB sejam alocados automaticamente para cada mensagem. O montante real atribuído depende do tamanho real da mensagem recebida.
Muitas quotas estão disponíveis na camada de transporte. Essas são cotas impostas pelo canal de transporte específico em uso (HTTP, TCP e assim por diante). Embora este tópico discuta algumas dessas cotas, elas são descritas em detalhes em Cotas de transporte.
Vulnerabilidade de hashtable
Existe uma vulnerabilidade quando os contratos de dados contêm hashtables ou coleções. O problema ocorre se um grande número de valores são inseridos em uma hashtable onde um grande número desses valores geram o mesmo valor de hash. Isso pode ser usado como um ataque DOS. Esta vulnerabilidade pode ser atenuada definindo a cota de vinculação MaxReceivedMessageSize. Há que ter cuidado ao fixar esta quota, a fim de evitar tais ataques. Essa cota coloca um limite superior no tamanho da mensagem WCF. Além disso, evite usar hashtables ou coleções em seus contratos de dados.
Limitando o consumo de memória sem streaming
O modelo de segurança em torno de mensagens grandes depende se o streaming está em uso. No caso básico, não transmitido, as mensagens são armazenadas em buffer na memória. Nesse caso, use a cota MaxReceivedMessageSize nas ligações fornecidas pelo TransportBindingElement sistema ou nas ligações fornecidas pelo sistema para proteger contra mensagens grandes, limitando o tamanho máximo da mensagem para acesso. Observe que um serviço pode estar processando várias mensagens ao mesmo tempo, caso em que todas elas estão na memória. Use o recurso de limitação para mitigar essa ameaça.
Observe também que MaxReceivedMessageSize
não coloca um limite superior no consumo de memória por mensagem, mas o limita a um fator constante. Por exemplo, se o MaxReceivedMessageSize
for de 1 MB e uma mensagem de 1 MB for recebida e, em seguida, desserializada, será necessária memória adicional para conter o gráfico de objeto desserializado, resultando em um consumo total de memória bem superior a 1 MB. Por esse motivo, evite criar tipos serializáveis que possam resultar em um consumo significativo de memória sem muitos dados recebidos. Por exemplo, um contrato de dados "MyContract" com 50 campos de membro de dados opcionais e 100 campos privados adicionais pode ser instanciado com a construção XML "<MyContract/>". Esse XML resulta em memória sendo acessada para 150 campos. Observe que os membros de dados são opcionais por padrão. O problema é agravado quando esse tipo faz parte de uma matriz.
MaxReceivedMessageSize
Por si só, não é suficiente para impedir todos os ataques de negação de serviço. Por exemplo, o desserializador pode ser forçado a desserializar um gráfico de objeto profundamente aninhado (um objeto que contém outro objeto que contém outro e assim por diante) por uma mensagem de entrada. Ambos os métodos e DataContractSerializer a XmlSerializer chamada de forma aninhada para desserializar esses gráficos. O aninhamento profundo de chamadas de método pode resultar em um StackOverflowException. Essa ameaça é atenuada definindo a cota MaxDepth para limitar o nível de aninhamento XML, conforme discutido na seção "Usando XML com segurança" mais adiante no tópico.
Definir cotas adicionais para MaxReceivedMessageSize
é especialmente importante ao usar codificação XML binária. Usar a codificação binária é algo equivalente à compactação: um pequeno grupo de bytes na mensagem recebida pode representar muitos dados. Assim, mesmo uma mensagem que se encaixa no MaxReceivedMessageSize
limite pode ocupar muito mais memória em forma totalmente expandida. Para atenuar essas ameaças específicas de XML, todas as cotas de leitor XML devem ser definidas corretamente, conforme discutido na seção "Usando XML com segurança" mais adiante neste tópico.
Limitando o consumo de memória com streaming
Ao fazer streaming, você pode usar uma pequena MaxReceivedMessageSize
configuração para se proteger contra ataques de negação de serviço. No entanto, cenários mais complicados são possíveis com o streaming. Por exemplo, um serviço de upload de arquivos aceita arquivos maiores do que toda a memória disponível. Nesse caso, defina o MaxReceivedMessageSize
para um valor extremamente grande, esperando que quase nenhum dado seja armazenado em buffer na memória e a mensagem seja transmitida diretamente para o disco. Se uma mensagem maliciosa pode, de alguma forma, forçar o WCF a armazenar dados em buffer em vez de transmiti-los nesse caso, MaxReceivedMessageSize
não protege mais contra o acesso da mensagem a toda a memória disponível.
Para atenuar essa ameaça, existem configurações de cota específicas em vários componentes de processamento de dados WCF que limitam o buffer. O mais importante deles é a MaxBufferSize
propriedade em vários elementos de ligação de transporte e ligações padrão. Ao transmitir, essa cota deve ser definida levando em conta a quantidade máxima de memória que você está disposto a alocar por mensagem. Tal como acontece com MaxReceivedMessageSize
o , a configuração não coloca um máximo absoluto no consumo de memória, mas apenas limita-o a dentro de um fator constante. Além disso, tal como acontece com MaxReceivedMessageSize
o , esteja ciente da possibilidade de várias mensagens serem processadas simultaneamente.
MaxBufferSize Detalhes
A MaxBufferSize
propriedade limita qualquer buffer em massa que o WCF faz. Por exemplo, o WCF sempre armazena em buffer cabeçalhos SOAP e falhas SOAP, bem como quaisquer partes MIME encontradas para não estar na ordem de leitura natural em uma mensagem MTOM (Message Transmission Optimization Mechanism). Essa configuração limita a quantidade de buffering em todos esses casos.
WCF faz isso passando o MaxBufferSize
valor para os vários componentes que podem buffer. Por exemplo, algumas CreateMessage sobrecargas da Message classe tomam um maxSizeOfHeaders
parâmetro. WCF passa o MaxBufferSize
valor para este parâmetro para limitar a quantidade de buffer de cabeçalho SOAP. É importante definir esse parâmetro ao usar a Message classe diretamente. Em geral, ao usar um componente no WCF que usa parâmetros de cota, é importante entender as implicações de segurança desses parâmetros e defini-los corretamente.
O codificador de mensagens MTOM também tem uma MaxBufferSize
configuração. Ao usar associações padrão, isso é definido automaticamente para o valor de nível MaxBufferSize
de transporte. No entanto, ao usar o elemento de vinculação do codificador de mensagens MTOM para construir uma associação personalizada, é importante definir a propriedade para um valor seguro quando o MaxBufferSize
streaming é usado.
Ataques de streaming baseados em XML
MaxBufferSize
por si só não é suficiente para garantir que o WCF não possa ser forçado a armazenar em buffer quando o streaming é esperado. Por exemplo, os leitores XML do WCF sempre armazenam em buffer toda a marca de início do elemento XML ao começar a ler um novo elemento. Isso é feito para que namespaces e atributos sejam processados corretamente. Se MaxReceivedMessageSize
estiver configurado para ser grande (por exemplo, para habilitar um cenário de streaming de arquivo grande direto para o disco), uma mensagem mal-intencionada pode ser construída onde todo o corpo da mensagem é uma marca de início de elemento XML grande. Uma tentativa de lê-lo resulta em um OutOfMemoryExceptionarquivo . Este é um dos muitos possíveis ataques de negação de serviço baseados em XML que podem ser atenuados usando cotas de leitor XML, discutidas na seção "Usando XML com segurança" mais adiante neste tópico. Ao fazer streaming, é especialmente importante definir todas essas cotas.
Misturando modelos de programação de streaming e buffering
Muitos ataques possíveis surgem da mistura de modelos de programação streaming e não streaming no mesmo serviço. Suponha que haja um contrato de serviço com duas operações: uma usa uma Stream e outra usa uma matriz de algum tipo personalizado. Suponha também que está definido como um valor grande para permitir que MaxReceivedMessageSize
a primeira operação processe grandes fluxos. Infelizmente, isso significa que mensagens grandes agora também podem ser enviadas para a segunda operação, e o desserializador armazena dados em buffer na memória como uma matriz antes que a operação seja chamada. Este é um possível ataque de negação de serviço: a cota MaxBufferSize
não limita o tamanho do corpo da mensagem, que é com o que o desserializador trabalha.
Por esse motivo, evite misturar operações baseadas e não transmitidas em fluxo no mesmo contrato. Se você absolutamente precisa misturar os dois modelos de programação, use as seguintes precauções:
Desative o IExtensibleDataObject recurso definindo a IgnoreExtensionDataObjectServiceBehaviorAttribute propriedade do como
true
. Isso garante que apenas os membros que fazem parte do contrato sejam desserializados.Defina a MaxItemsInObjectGraph propriedade do DataContractSerializer como um valor seguro. Essa cota também está disponível no atributo ou por meio da ServiceBehaviorAttribute configuração. Essa cota limita o número de objetos que são desserializados em um episódio de desserialização. Normalmente, cada parâmetro de operação ou parte do corpo da mensagem em um contrato de mensagem é desserializado em um episódio. Ao desserializar matrizes, cada entrada de matriz é contada como um objeto separado.
Defina todas as cotas do leitor XML como valores seguros. Preste atenção ao MaxDepth, MaxStringContentLengthe MaxArrayLength evite strings em operações que não sejam de streaming.
Analise a lista de tipos conhecidos, tendo em mente que qualquer um deles pode ser instanciado a qualquer momento (consulte a seção "Impedindo que tipos não intencionais sejam carregados" mais adiante neste tópico).
Não use nenhum tipo que implemente a interface que armazena IXmlSerializable muitos dados em buffer. Não adicione esses tipos à lista de tipos conhecidos.
Não use as XmlElementXmlNode matrizes, Byte matrizes ou tipos implementados ISerializable em um contrato.
Não use as XmlElementXmlNode matrizes, Byte matrizes ou tipos implementados ISerializable na lista de tipos conhecidos.
As precauções anteriores aplicam-se quando a operação não transmitida utiliza o DataContractSerializer. Nunca misture modelos de programação streaming e não streaming no mesmo serviço se você estiver usando o XmlSerializer, porque ele não tem a proteção da cota MaxItemsInObjectGraph .
Ataques de fluxo lento
Uma classe de ataques de negação de serviço de streaming não envolve consumo de memória. Em vez disso, o ataque envolve um emissor ou recetor lento de dados. Enquanto aguarda que os dados sejam enviados ou recebidos, recursos como threads e conexões disponíveis são esgotados. Esta situação pode surgir como resultado de um ataque malicioso ou de um remetente/recetor legítimo numa ligação de rede lenta.
Para atenuar esses ataques, defina os tempos limite de transporte corretamente. Para obter mais informações, consulte Cotas de transporte. Em segundo lugar, nunca use operações ou Write
síncronas Read
ao trabalhar com fluxos no WCF.
Usando XML com segurança
Nota
Embora esta seção seja sobre XML, as informações também se aplicam a documentos JSON (JavaScript Object Notation). As cotas funcionam de forma semelhante, usando mapeamento entre JSON e XML.
Leitores XML seguros
O XML Infoset forma a base de todo o processamento de mensagens no WCF. Ao aceitar dados XML de uma fonte não confiável, existem várias possibilidades de ataque de negação de serviço que devem ser atenuadas. O WCF fornece leitores XML especiais e seguros. Esses leitores são criados automaticamente ao usar uma das codificações padrão no WCF (texto, binário ou MTOM).
Alguns dos recursos de segurança nesses leitores estão sempre ativos. Por exemplo, os leitores nunca processam definições de tipo de documento (DTDs), que são uma fonte potencial de ataques de negação de serviço e nunca devem aparecer em mensagens SOAP legítimas. Outros recursos de segurança incluem cotas de leitor que devem ser configuradas, descritas na seção a seguir.
Ao trabalhar diretamente com leitores XML (como ao escrever seu próprio codificador personalizado ou ao trabalhar diretamente com a Message classe), sempre use os leitores seguros do WCF quando houver a chance de trabalhar com dados não confiáveis. Crie os leitores seguros chamando uma das sobrecargas de método estático de fábrica de CreateTextReader, CreateBinaryReaderou CreateMtomReader na XmlDictionaryReader classe. Ao criar um leitor, passe valores de cota seguros. Não chame o Create
método de sobrecargas. Estes não criam um leitor WCF. Em vez disso, é criado um leitor que não está protegido pelos recursos de segurança descritos nesta seção.
Cotas de leitores
Os leitores XML seguros têm cinco cotas configuráveis. Eles são normalmente configurados usando a ReaderQuotas
propriedade nos elementos de ligação de codificação ou associações padrão, ou usando um XmlDictionaryReaderQuotas objeto passado ao criar um leitor.
MaxBytesPerRead
Essa cota limita o número de bytes lidos em uma única Read
operação ao ler a marca de início do elemento e seus atributos. (Em casos não transmitidos, o nome do elemento em si não é contabilizado na quota.) MaxBytesPerRead é importante pelas seguintes razões:
O nome do elemento e seus atributos são sempre armazenados em buffer na memória quando estão sendo lidos. Portanto, é importante definir essa cota corretamente no modo de streaming para evitar buffering excessivo quando o streaming é esperado. Consulte a seção de cota
MaxDepth
para obter informações sobre a quantidade real de buffering que ocorre.Ter muitos atributos XML pode consumir um tempo de processamento desproporcional porque os nomes dos atributos precisam ser verificados quanto à exclusividade.
MaxBytesPerRead
atenua esta ameaça.
Profundidade máxima
Essa cota limita a profundidade máxima de aninhamento de elementos XML. Por exemplo, o documento "<A><B><C/></B></A>" tem uma profundidade de aninhamento de três. MaxDepth é importante pelas seguintes razões:
MaxDepth
interage comMaxBytesPerRead
: o leitor sempre mantém dados na memória para o elemento atual e todos os seus antepassados, de modo que o consumo máximo de memória do leitor é proporcional ao produto dessas duas configurações.Ao desserializar um gráfico de objeto profundamente aninhado, o desserializador é forçado a acessar toda a pilha e lançar um StackOverflowException. Existe uma correlação direta entre o aninhamento XML e o aninhamento de objetos para o DataContractSerializer e o XmlSerializer. Use
MaxDepth
para mitigar essa ameaça.
MaxNameTableCharCount
Essa cota limita o tamanho da tabela de nomes do leitor. A tabela de nomes contém determinadas cadeias de caracteres (como namespaces e prefixos) que são encontradas ao processar um documento XML. Como essas cadeias de caracteres são armazenadas em buffer na memória, defina essa cota para evitar buffering excessivo quando o streaming é esperado.
MaxStringContentLength
Essa cota limita o tamanho máximo da cadeia de caracteres que o leitor XML retorna. Essa cota não limita o consumo de memória no próprio leitor XML, mas no componente que está usando o leitor. Por exemplo, quando o DataContractSerializer usa um leitor protegido com MaxStringContentLength, ele não desserializa cadeias de caracteres maiores do que essa cota. Ao usar a XmlDictionaryReader classe diretamente, nem todos os métodos respeitam essa cota, mas apenas os métodos que são especificamente projetados para ler cadeias de caracteres, como o ReadContentAsString método. A Value propriedade no leitor não é afetada por esta quota e, portanto, não deve ser usada quando a proteção que esta quota fornece é necessária.
MaxArrayLength
Essa cota limita o tamanho máximo de uma matriz de primitivas que o leitor XML retorna, incluindo matrizes de bytes. Essa cota não limita o consumo de memória no próprio leitor XML, mas em qualquer componente que esteja usando o leitor. Por exemplo, quando o DataContractSerializer usa um leitor protegido com MaxArrayLength, ele não desserializa matrizes de bytes maiores que essa cota. É importante definir essa cota ao tentar misturar streaming e modelos de programação em buffer em um único contrato. Tenha em mente que, ao usar a XmlDictionaryReader classe diretamente, apenas os métodos especificamente projetados para ler matrizes de tamanho arbitrário de certos tipos primitivos, como ReadInt32Array, respeitam essa cota.
Ameaças específicas para a codificação binária
A codificação XML binária suportada pelo WCF inclui um recurso de cadeias de caracteres de dicionário. Uma cadeia de caracteres grande pode ser codificada usando apenas alguns bytes. Isso permite ganhos significativos de desempenho, mas introduz novas ameaças de negação de serviço que devem ser mitigadas.
Existem dois tipos de dicionários: estático e dinâmico. O dicionário estático é uma lista interna de cadeias de caracteres longas que podem ser representadas usando um código curto na codificação binária. Esta lista de cadeias de caracteres é corrigida quando o leitor é criado e não pode ser modificada. Nenhuma das cadeias de caracteres no dicionário estático que o WCF usa por padrão é suficientemente grande para representar uma séria ameaça de negação de serviço, embora ainda possa ser usada em um ataque de expansão de dicionário. Em cenários avançados em que você fornece seu próprio dicionário estático, tenha cuidado ao introduzir grandes cadeias de dicionário.
O recurso de dicionários dinâmicos permite que as mensagens definam suas próprias cadeias de caracteres e as associem a códigos curtos. Esses mapeamentos string-to-code são mantidos na memória durante toda a sessão de comunicação, de modo que as mensagens subsequentes não precisem reenviar as cadeias de caracteres e possam utilizar códigos já definidos. Essas cadeias de caracteres podem ser de comprimento arbitrário e, portanto, representam uma ameaça mais séria do que as do dicionário estático.
A primeira ameaça que deve ser mitigada é a possibilidade de o dicionário dinâmico (a tabela de mapeamento string-to-code) se tornar muito grande. Este dicionário pode ser expandido ao longo de várias mensagens e, portanto, a cota MaxReceivedMessageSize
não oferece proteção porque se aplica apenas a cada mensagem separadamente. Portanto, existe uma propriedade separada MaxSessionSize no que limita BinaryMessageEncodingBindingElement o tamanho do dicionário.
Ao contrário da maioria das outras quotas, esta quota também se aplica ao escrever mensagens. Se for excedido ao ler uma mensagem, o QuotaExceededException
é lançado como de costume. Se for excedido ao escrever uma mensagem, todas as cadeias de caracteres que fazem com que a cota seja excedida são escritas como estão, sem usar o recurso de dicionários dinâmicos.
Ameaças de expansão de dicionário
Uma classe significativa de ataques binários específicos surge da expansão do dicionário. Uma mensagem pequena em forma binária pode se transformar em uma mensagem muito grande em formato textual totalmente expandido se fizer uso extensivo do recurso de dicionários de cadeia de caracteres. O fator de expansão para cadeias de caracteres de dicionário dinâmico é limitado pela cota MaxSessionSize , porque nenhuma cadeia de caracteres de dicionário dinâmico excede o tamanho máximo de todo o dicionário.
As propriedades , MaxStringContentLength
, e limitam MaxArrayLength
apenas o MaxNameTableCharCountconsumo de memória. Eles normalmente não são necessários para mitigar quaisquer ameaças no uso não transmitido porque o uso de memória já é limitado pelo MaxReceivedMessageSize
. No entanto, MaxReceivedMessageSize
conta bytes de pré-expansão. Quando a codificação binária está em uso, o consumo de memória pode potencialmente ir além MaxReceivedMessageSize
de , limitado apenas por um fator de MaxSessionSize. Por esse motivo, é importante sempre definir todas as cotas de leitor (especialmente MaxStringContentLength) ao usar a codificação binária.
Ao usar a codificação binária juntamente com o DataContractSerializer, a IExtensibleDataObject
interface pode ser usada indevidamente para montar um ataque de expansão de dicionário. Esta interface fornece essencialmente armazenamento ilimitado para dados arbitrários que não fazem parte do contrato. Se as cotas não puderem ser definidas como baixas o suficiente para que MaxSessionSize
a multiplicação por MaxReceivedMessageSize
não represente um problema, desative o IExtensibleDataObject
recurso ao usar a codificação binária. Defina a IgnoreExtensionDataObject
propriedade como true
no ServiceBehaviorAttribute
atributo. Como alternativa, não implemente a IExtensibleDataObject
interface. Para obter mais informações, consulte Contratos de dados compatíveis com encaminhamento.
Resumo das quotas
A tabela a seguir resume as orientações sobre cotas.
Condição | Quotas importantes a definir |
---|---|
Sem streaming ou streaming de pequenas mensagens, texto ou codificação MTOM | MaxReceivedMessageSize , MaxBytesPerRead , e MaxDepth |
Sem streaming ou streaming de mensagens pequenas, codificação binária | MaxReceivedMessageSize , MaxSessionSize , e todos ReaderQuotas |
Streaming de mensagens grandes, texto ou codificação MTOM | MaxBufferSize e todos ReaderQuotas |
Streaming de mensagens grandes, codificação binária | MaxBufferSize , MaxSessionSize , e todos ReaderQuotas |
Os tempos limite no nível de transporte devem ser sempre definidos e nunca usar leituras/gravações síncronas quando o streaming estiver em uso, independentemente de você estar transmitindo mensagens grandes ou pequenas.
Em caso de dúvida sobre uma quota, defina-a com um valor seguro em vez de a deixar em aberto.
Impedindo a execução de código malicioso
As seguintes classes gerais de ameaças podem executar código e ter efeitos não intencionais:
O desserializador carrega um tipo malicioso, inseguro ou sensível à segurança.
Uma mensagem de entrada faz com que o desserializador construa uma instância de um tipo normalmente seguro de tal forma que tenha consequências não intencionais.
As seções a seguir discutem melhor essas classes de ameaças.
DataContractSerializer
(Para obter informações de segurança sobre o XmlSerializer, consulte a documentação relevante.) O modelo de segurança para o XmlSerializer é semelhante ao do DataContractSerializer, e difere principalmente em detalhes. Por exemplo, o XmlIncludeAttribute atributo é usado para inclusão de tipo em vez do KnownTypeAttribute atributo. No entanto, algumas ameaças exclusivas do são discutidas XmlSerializer mais adiante neste tópico.
Impedindo que tipos não intencionais sejam carregados
O carregamento de tipos não intencionais pode ter consequências significativas, quer o tipo seja malicioso ou tenha apenas efeitos secundários sensíveis à segurança. Um tipo pode conter vulnerabilidade de segurança explorável, executar ações sensíveis à segurança em seu construtor ou construtor de classe, ter um grande espaço de memória que facilita ataques de negação de serviço ou pode lançar exceções não recuperáveis. Os tipos podem ter construtores de classe que são executados assim que o tipo é carregado e antes que quaisquer instâncias sejam criadas. Por esses motivos, é importante controlar o conjunto de tipos que o desserializador pode carregar.
O DataContractSerializer desserializa de forma fracamente acoplada. Ele nunca lê o tipo CLR (Common Language Runtime) e nomes de assembly dos dados recebidos. Isto é semelhante ao comportamento do XmlSerializer, mas difere do comportamento do NetDataContractSerializer, BinaryFormattere do SoapFormatter. O acoplamento flexível introduz um grau de segurança, porque o invasor remoto não pode indicar um tipo arbitrário para carregar apenas nomeando esse tipo na mensagem.
O DataContractSerializer é sempre permitido carregar um tipo que atualmente é esperado de acordo com o contrato. Por exemplo, se um contrato de dados tiver um membro de dados do tipo Customer
, o DataContractSerializer terá permissão para carregar o Customer
tipo quando ele desserializar esse membro de dados.
Além disso, o DataContractSerializer suporta polimorfismo. Um membro de dados pode ser declarado como Object, mas os dados de entrada podem conter uma Customer
instância. Isso só é possível se o Customer
tipo tiver sido tornado "conhecido" para o desserializador através de um destes mecanismos:
KnownTypeAttribute atributo aplicado a um tipo.
KnownTypeAttribute
especificando um método que retorna uma lista de tipos.Atributo
ServiceKnownTypeAttribute
.A
KnownTypes
seção de configuração.Uma lista de tipos conhecidos explicitamente passados para o durante a DataContractSerializer construção, se usando o serializador diretamente.
Cada um desses mecanismos aumenta a área de superfície introduzindo mais tipos que o desserializador pode carregar. Controle cada um desses mecanismos para garantir que nenhum tipo mal-intencionado ou não intencional seja adicionado à lista de tipos conhecidos.
Uma vez que um tipo conhecido está no escopo, ele pode ser carregado a qualquer momento, e instâncias do tipo podem ser criadas, mesmo que o contrato proíba realmente usá-lo. Por exemplo, suponha que o tipo "MyDangerousType" seja adicionado à lista de tipos conhecidos usando um dos mecanismos acima. Isto significa que:
MyDangerousType
é carregado e seu construtor de classe é executado.Mesmo ao desserializar um contrato de dados com um membro de dados de cadeia de caracteres, uma mensagem mal-intencionada ainda pode causar a criação de uma instância de
MyDangerousType
. O código noMyDangerousType
, como setters de propriedade, pode ser executado. Depois que isso é feito, o desserializador tenta atribuir essa instância ao membro de dados da cadeia de caracteres e falha com uma exceção.
Ao escrever um método que retorna uma lista de tipos conhecidos, ou ao passar uma lista diretamente para o DataContractSerializer construtor, certifique-se de que o código que prepara a lista é seguro e opera apenas em dados confiáveis.
Se especificar tipos conhecidos na configuração, verifique se o arquivo de configuração é seguro. Sempre use nomes fortes na configuração (especificando a chave pública do assembly assinado onde o tipo reside), mas não especifique a versão do tipo a ser carregado. O carregador de tipos escolhe automaticamente a versão mais recente, se possível. Se você especificar uma versão específica na configuração, correrá o seguinte risco: Um tipo pode ter uma vulnerabilidade de segurança que pode ser corrigida em uma versão futura, mas a versão vulnerável ainda é carregada porque é explicitamente especificada na configuração.
Ter muitos tipos conhecidos tem outra consequência: O DataContractSerializer cria um cache de código de serialização/desserialização no domínio do aplicativo, com uma entrada para cada tipo que deve serializar e desserializar. Esse cache nunca é limpo enquanto o domínio do aplicativo estiver em execução. Portanto, um invasor ciente de que um aplicativo usa muitos tipos conhecidos pode causar a desserialização de todos esses tipos, fazendo com que o cache consuma uma quantidade desproporcionalmente grande de memória.
Impedindo que os tipos estejam em um estado não intencional
Um tipo pode ter restrições de consistência interna que devem ser impostas. Deve-se tomar cuidado para evitar quebrar essas restrições durante a desserialização.
O exemplo de um tipo a seguir representa o estado de uma câmara de ar em uma espaçonave e impõe a restrição de que as portas internas e externas não podem ser abertas ao mesmo tempo.
[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
Um invasor pode enviar uma mensagem maliciosa como esta, contornando as restrições e colocando o objeto em um estado inválido, o que pode ter consequências não intencionais e imprevisíveis.
<SpaceStationAirlock>
<innerDoorOpen>true</innerDoorOpen>
<outerDoorOpen>true</outerDoorOpen>
</SpaceStationAirlock>
Esta situação pode ser evitada tendo em conta os seguintes pontos:
Quando a desserializa a DataContractSerializer maioria das classes, os construtores não são executados. Portanto, não conte com nenhuma gestão estadual feita na construtora.
Use retornos de chamada para garantir que o objeto esteja em um estado válido. O retorno de chamada marcado com o atributo é especialmente útil porque é executado após a conclusão da OnDeserializedAttribute desserialização e tem a chance de examinar e corrigir o estado geral. Para obter mais informações, consulte Retornos de chamada de serialização tolerantes à versão.
Não projete tipos de contrato de dados para confiar em qualquer ordem específica na qual os setters de propriedade devem ser chamados.
Tome cuidado ao usar tipos herdados marcados com o SerializableAttribute atributo. Muitos deles foram projetados para trabalhar com comunicação remota do .NET Framework para uso apenas com dados confiáveis. Os tipos existentes marcados com este atributo podem não ter sido concebidos tendo em mente a segurança do estado.
Não confie na IsRequired propriedade do atributo para garantir a DataMemberAttribute presença de dados no que diz respeito à segurança do Estado. Os dados podem sempre ser
null
,zero
ouinvalid
.Nunca confie em um gráfico de objeto desserializado de uma fonte de dados não confiável sem validá-lo primeiro. Cada objeto individual pode estar em um estado consistente, mas o gráfico de objetos como um todo pode não estar. Além disso, mesmo que o modo de preservação do gráfico de objeto esteja desativado, o grafo desserializado pode ter várias referências ao mesmo objeto ou ter referências circulares. Para obter mais informações, consulte Serialização e desserialização.
Usando o NetDataContractSerializer com segurança
O NetDataContractSerializer é um mecanismo de serialização que usa acoplamento apertado a tipos. Isto é semelhante ao BinaryFormatter e ao SoapFormatter. Ou seja, ele determina qual tipo instanciar lendo o assembly do .NET Framework e o nome do tipo dos dados de entrada. Embora seja uma parte do WCF, não há nenhuma maneira fornecida de conectar esse mecanismo de serialização; código personalizado deve ser escrito. O NetDataContractSerializer
é fornecido principalmente para facilitar a migração da comunicação remota do .NET Framework para WCF. Para obter mais informações, consulte a seção relevante em Serialização e desserialização.
Como a própria mensagem pode indicar que qualquer tipo pode ser carregado, o NetDataContractSerializer mecanismo é inerentemente inseguro e deve ser usado apenas com dados confiáveis. Para obter mais informações, consulte o guia de segurança BinaryFormatter.
Mesmo quando usados com dados confiáveis, os dados de entrada podem especificar insuficientemente o tipo a ser carregado, especialmente se a AssemblyFormat propriedade estiver definida como Simple. Qualquer pessoa com acesso ao diretório do aplicativo ou ao cache de assembly global pode substituir um tipo mal-intencionado no lugar daquele que deve ser carregado. Sempre garanta a segurança do diretório do seu aplicativo e do cache de assembly global definindo corretamente as permissões.
Em geral, se você permitir o acesso de código parcialmente confiável à sua NetDataContractSerializer
instância ou controlar de outra forma o seletor substituto (ISurrogateSelector) ou o fichário de serialização (SerializationBinder), o código poderá exercer um grande controle sobre o processo de serialização/desserialização. Por exemplo, ele pode injetar tipos arbitrários, levar à divulgação de informações, adulterar o gráfico de objeto resultante ou dados serializados ou transbordar o fluxo serializado resultante.
Outra preocupação de segurança com o NetDataContractSerializer
é uma negação de serviço, não uma ameaça de execução de código malicioso. Ao usar o NetDataContractSerializer
, sempre defina a cota MaxItemsInObjectGraph para um valor seguro. É fácil construir uma pequena mensagem maliciosa que aloca uma matriz de objetos cujo tamanho é limitado apenas por essa cota.
Ameaças específicas do XmlSerializer
O XmlSerializer modelo de segurança é semelhante ao do DataContractSerializer. Algumas ameaças, no entanto, são exclusivas do XmlSerializer.
O XmlSerializer gera assemblies de serialização em tempo de execução que contêm código que realmente serializa e desserializa, esses assemblies são criados em um diretório de arquivos temporários. Se algum outro processo ou usuário tiver direitos de acesso a esse diretório, eles poderão substituir o código de serialização/desserialização por código arbitrário. Em XmlSerializer seguida, executa esse código usando seu contexto de segurança, em vez do código de serialização/desserialização. Verifique se as permissões estão definidas corretamente no diretório de arquivos temporários para evitar que isso aconteça.
O XmlSerializer também tem um modo no qual ele usa assemblies de serialização pré-gerados em vez de gerá-los em tempo de execução. Este modo é acionado sempre que o XmlSerializer pode encontrar um assembly de serialização adequado. O XmlSerializer verifica se o assembly de serialização foi assinado ou não pela mesma chave que foi usada para assinar o assembly que contém os tipos que estão sendo serializados. Isso oferece proteção contra assemblies mal-intencionados disfarçados como assemblies de serialização. No entanto, se o assembly que contém seus tipos serializáveis não estiver assinado, o XmlSerializer não pode executar essa verificação e usa qualquer assembly com o nome correto. Isso torna possível a execução de código malicioso. Sempre assine os assemblies que contêm seus tipos serializáveis ou controle rigorosamente o acesso ao diretório do aplicativo e ao cache global de assemblies para evitar a introdução de assemblies mal-intencionados.
O XmlSerializer pode estar sujeito a um ataque de negação de serviço. O XmlSerializer não tem uma MaxItemsInObjectGraph
quota (como está disponível no DataContractSerializer). Assim, ele desserializa uma quantidade arbitrária de objetos, limitada apenas pelo tamanho da mensagem.
Ameaças de confiança parcial
Observe as seguintes preocupações relacionadas a ameaças relacionadas à execução de código com confiança parcial. Essas ameaças incluem código mal-intencionado parcialmente confiável, bem como código mal-intencionado parcialmente confiável em combinação com outros cenários de ataque (por exemplo, código parcialmente confiável que constrói uma cadeia de caracteres específica e, em seguida, desserializa-a).
Ao usar qualquer componente de serialização, nunca afirme nenhuma permissão antes de tal uso, mesmo que todo o cenário de serialização esteja dentro do escopo de sua declaração e você não esteja lidando com dados ou objetos não confiáveis. Esse uso pode levar a vulnerabilidades de segurança.
Nos casos em que o código parcialmente confiável tem controle sobre o processo de serialização, seja por meio de pontos de extensibilidade (substitutos), tipos sendo serializados ou por outros meios, o código parcialmente confiável pode fazer com que o serializador produza uma grande quantidade de dados no fluxo serializado, o que pode causar negação de serviço (DoS) para o recetor desse fluxo. Se você estiver serializando dados destinados a um destino sensível a ameaças de DoS, não serialize tipos parcialmente confiáveis ou permita a serialização de controle de código parcialmente confiável.
Se você permitir o acesso de código parcialmente confiável à sua DataContractSerializer instância ou controlar de outra forma os substitutos do contrato de dados, ele poderá exercer um grande controle sobre o processo de serialização/desserialização. Por exemplo, ele pode injetar tipos arbitrários, levar à divulgação de informações, adulterar o gráfico de objeto resultante ou dados serializados ou transbordar o fluxo serializado resultante. Uma ameaça equivalente NetDataContractSerializer é descrita na seção "Usando o NetDataContractSerializer com segurança".
Se o DataContractAttribute atributo for aplicado a um tipo (ou ao tipo marcado como SerializableAttribute mas não ISerializableé), o desserializador poderá criar uma instância desse tipo, mesmo que todos os construtores não sejam públicos ou estejam protegidos por demandas.
Nunca confie no resultado da desserialização, a menos que os dados a serem desserializados sejam confiáveis e você tenha certeza de que todos os tipos conhecidos são tipos confiáveis. Observe que os tipos conhecidos não são carregados do arquivo de configuração do aplicativo (mas são carregados do arquivo de configuração do computador) quando executados em confiança parcial.
Se você passar uma DataContractSerializer instância com um substituto adicionado a um código parcialmente confiável, o código poderá alterar quaisquer configurações modificáveis nesse substituto.
Para um objeto desserializado, se o leitor XML (ou os dados nele contidos) vierem de código parcialmente confiável, trate o objeto desserializado resultante como dados não confiáveis.
O fato de o tipo não ter membros públicos não significa que os ExtensionDataObject dados dentro dele estejam seguros. Por exemplo, se você desserializar de uma fonte de dados privilegiada em um objeto no qual alguns dados residem, em seguida, entregar esse objeto para código parcialmente confiável, o código parcialmente confiável pode ler os dados no
ExtensionDataObject
serializando o objeto. Considere a configuração IgnoreExtensionDataObject paratrue
ao desserializar de uma fonte de dados privilegiada para um objeto que é posteriormente passado para código parcialmente confiável.DataContractSerializer e DataContractJsonSerializer suportar a serialização de membros privados, protegidos, internos e públicos em total confiança. No entanto, na confiança parcial, apenas membros públicos podem ser serializados. A SecurityException é lançado se um aplicativo tentar serializar um membro não público.
Para permitir que membros internos ou protegidos sejam serializados em confiança parcial, use o InternalsVisibleToAttribute atributo assembly. Esse atributo permite que um assembly declare que seus membros internos são visíveis para algum outro assembly. Nesse caso, um assembly que deseja ter seus membros internos serializados declara que seus membros internos são visíveis para System.Runtime.Serialization.dll.
A vantagem dessa abordagem é que ela não requer um caminho de geração de código elevado.
Ao mesmo tempo, existem duas grandes desvantagens.
A primeira desvantagem é que a propriedade opt-in do InternalsVisibleToAttribute atributo é assembly-wide. Ou seja, você não pode especificar que apenas uma determinada classe pode ter seus membros internos serializados. Claro, você ainda pode optar por não serializar um membro interno específico, simplesmente não adicionando um DataMemberAttribute atributo a esse membro. Da mesma forma, um desenvolvedor também pode optar por tornar um membro interno em vez de privado ou protegido, com pequenas preocupações de visibilidade.
A segunda desvantagem é que continua a não apoiar membros privados ou protegidos.
Para ilustrar o InternalsVisibleToAttribute uso do atributo na confiança parcial, considere o seguinte programa:
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"; } }
No exemplo acima,
PermissionsHelper.InternetZone
corresponde ao PermissionSet para confiança parcial. Agora, sem o InternalsVisibleToAttribute atributo, o aplicativo falhará, lançando uma SecurityException indicação de que membros não públicos não podem ser serializados em confiança parcial.No entanto, se adicionarmos a seguinte linha ao arquivo de origem, o programa é executado com êxito.
[assembly:System.Runtime.CompilerServices.InternalsVisibleTo("System.Runtime.Serialization, PublicKey = 00000000000000000400000000000000")]
Outras preocupações da gestão do Estado
Algumas outras preocupações em relação à gestão do estado objeto merecem ser mencionadas:
Ao usar o modelo de programação baseado em fluxo com um transporte de streaming, o processamento da mensagem ocorre quando a mensagem chega. O remetente da mensagem pode abortar a operação de envio no meio do fluxo, deixando seu código em um estado imprevisível se mais conteúdo era esperado. Em geral, não confie na conclusão do fluxo e não execute nenhum trabalho em uma operação baseada em fluxo que não possa ser revertida caso o fluxo seja abortado. Isso também se aplica à situação em que uma mensagem pode ser malformada após o corpo de streaming (por exemplo, pode estar faltando uma marca final para o envelope SOAP ou pode ter um segundo corpo de mensagem).
O uso do
IExtensibleDataObject
recurso pode fazer com que dados confidenciais sejam emitidos. Se você estiver aceitando dados de uma fonte não confiável em contratos de dados comIExtensibleObjectData
e posteriormente reemitindo em um canal seguro onde as mensagens são assinadas, você está potencialmente garantindo dados sobre os quais não sabe nada. Além disso, o estado geral que está a enviar pode ser inválido se tiver em conta os dados conhecidos e desconhecidos. Evite essa situação definindo seletivamente a propriedade extension data comonull
ou desativando seletivamente oIExtensibleObjectData
recurso.
Importação de esquema
Normalmente, o processo de importação de esquema para gerar tipos acontece somente em tempo de design, por exemplo, ao usar a ServiceModel Metadata Utility Tool (Svcutil.exe) em um serviço Web para gerar uma classe de cliente. No entanto, em cenários mais avançados, você pode processar o esquema em tempo de execução. Esteja ciente de que isso pode expô-lo a riscos de negação de serviço. Alguns esquemas podem levar muito tempo para serem importados. Nunca use o XmlSerializer componente de importação de esquema nesses cenários se os esquemas forem possivelmente provenientes de uma fonte não confiável.
Ameaças específicas à integração ASP.NET AJAX
Quando o usuário implementa WebScriptEnablingBehavior ou WebHttpBehavior, o WCF expõe um ponto de extremidade que pode aceitar mensagens XML e JSON. No entanto, há apenas um conjunto de cotas de leitor, usado tanto pelo leitor XML quanto pelo leitor JSON. Algumas configurações de cota podem ser apropriadas para um leitor, mas muito grandes para o outro.
Ao implementar WebScriptEnablingBehavior
o , o usuário tem a opção de expor um proxy JavaScript no ponto de extremidade. As seguintes questões de segurança devem ser consideradas:
Informações sobre o serviço (nomes de operação, nomes de parâmetros e assim por diante) podem ser obtidas examinando o proxy JavaScript.
Ao usar o ponto de extremidade JavaScript, informações confidenciais e privadas podem ser retidas no cache do navegador da Web cliente.
Uma nota sobre os componentes
WCF é um sistema flexível e personalizável. A maioria do conteúdo deste tópico se concentra nos cenários de uso mais comuns do WCF. No entanto, é possível compor componentes que o WCF fornece de muitas maneiras diferentes. É importante entender as implicações de segurança do uso de cada componente. Em particular:
Quando você deve usar leitores XML, use os leitores que a XmlDictionaryReader classe fornece em vez de quaisquer outros leitores. Os leitores seguros são criados usando CreateTextReader, CreateBinaryReaderou CreateMtomReader métodos. Não utilize o Create método. Configure sempre os leitores com quotas seguras. Os mecanismos de serialização no WCF são seguros somente quando usados com leitores XML seguros do WCF.
Ao usar o DataContractSerializer para desserializar dados potencialmente não confiáveis, sempre defina a MaxItemsInObjectGraph propriedade.
Ao criar uma mensagem, defina o
maxSizeOfHeaders
parâmetro seMaxReceivedMessageSize
não oferecer proteção suficiente.Ao criar um codificador, sempre configure as cotas relevantes, como
MaxSessionSize
eMaxBufferSize
.Ao usar um filtro de mensagem XPath, defina o NodeQuota para limitar a quantidade de nós XML que o filtro visita. Não use expressões XPath que podem levar muito tempo para calcular sem visitar muitos nós.
Em geral, ao usar qualquer componente que aceite uma cota, entenda suas implicações de segurança e defina-a como um valor seguro.