Guia básico de segurança na desserialização em Swift
Sam Sanoop
18 de julho de 2023
0 minutos de leituraDesserialização é o processo de converter dados de um formato serializado, como JSON ou binário, de volta à forma original. O Swift oferece vários protocolos que permitem converter objetos e valores de e para listas de propriedades, JSON e outras representações binárias planas.
A desserialização também pode introduzir vulnerabilidades de segurança inesperadas no código de uma pessoa usuária, que podem ser exploradas por atacantes. Este artigo detalha vulnerabilidades de desserialização em Swift que podem ocorrer ao usar as APIs populares, NScoding e NSSecureCoding, além de mostrar como preveni-las corretamente.
NSCoding
NSCoding é um protocolo fornecido pela Apple que pode ser usado para serializar e desserializar dados. O protocolo padrão para serializar e desserializar dados com NSCoding é vulnerável a ataques de substituição de objetos, que podem permitir que um atacante execute código remotamente.
Considere a classe a seguir, que implementa NSCoding com dois métodos: initWithCoder: e encodeWithCoder:. Classes que adotam NSCoding podem ser serializadas e desserializadas em dados que podem ser arquivados em disco ou distribuídos pela rede.
A classe acima adota o protocolo NSCoding e tem duas propriedades: name e role. Ela também implementa o inicializador required init(coder:) para decodificar as propriedades name e role de um arquivo e o método encode(with:) para codificá-las em um arquivo.
Em seguida, é possível criar um objeto employee e arquivá-lo em um arquivo usando NSKeyedArchiver.
Depois, é possível desarquivar esse objeto employee do arquivo usando NSKeyedUnarchiver e imprimir os valores das propriedades name e role.
Isso pode ser explorado da seguinte maneira. Suponha que existam outras classes na base de código, incluindo a classe a seguir. Ela tem uma única propriedade chamada "command", uma string que representa um caminho de arquivo. A classe também tem dois métodos, "encode(with:)" e "init?(coder:)", necessários para adotar o protocolo NSCoding.
A propriedade command é obtida de um objeto codificado e direcionada para a função sink1, que a executa como parte de um comando.
Um atacante pode explorar essa classe para executar código e realizar um ataque de desserialização que pode levar à injeção de comandos.
NSSecureCoding
NSSecureCoding é uma alternativa mais segura ao NSCoding, com recursos aprimorados para proteger contra ataques de desserialização. Ao contrário do NSCoding, NSSecureCoding impõe requisitos de segurança mais rigorosos aos objetos codificados e decodificados, incluindo a necessidade de uma hierarquia de classes definida e de medidas de segurança específicas implementadas nas próprias classes. Essas medidas ajudam a impedir a manipulação de dados serializados que poderia resultar na criação de objetos capazes de executar código malicioso, reforçando a segurança geral do sistema. No entanto, ainda podem ocorrer ataques de desserialização, dependendo de como o protocolo é usado.
Decodificação sem verificar o tipo da classe
No exemplo abaixo, o código foi alterado para adotar NSSecureCoding. A propriedade supportSecureCoding foi definida como true e, assim como antes, o método decodeObject é usado para desserializar objetos codificados.
No entanto, como informa a documentação para desenvolvedores da Apple sobre NSSecureCoding, essa técnica pode não ser segura porque, quando o tipo da classe é verificado, o objeto já foi construído e, se fizer parte de uma classe de coleção, pode já ter sido inserido em um grafo de objetos. Definir supportsSecureCoding como true marca um objeto como seguro para decodificação. Porém, NSSecureCoding não verifica o tipo desse objeto nem se ele corresponde à classe employee esperada.
Para corrigir esse problema, especifique a chave of com decodeObject(of:Employee.self, forKey: "name"), que decodifica apenas objetos da classe especificada (neste caso, employee). Isso garante que a propriedade name seja decodificada apenas como uma string, e não como um objeto criado de forma maliciosa. Outra opção é usar o método decodeObjectOfClass, que decodifica o objeto associado à chave, restringindo-o à classe especificada. Além disso, não use os métodos decodeObjectForKey e decodeTopLevelObjectForKey: eles também são afetados por esse problema e foram considerados obsoletos pela Apple.
supportsSecureCoding definido como False
Como informa a documentação para desenvolvedores da Apple, ao escrever uma classe compatível com codificação segura, você deve garantir que o getter dessa propriedade da classe retorne true. Definir supportsSecureCoding = false ainda permite adotar NSSecureCoding e dá a quem desenvolve uma falsa sensação de segurança, deixando ataques de desserialização possíveis.
Desserialização segura em Swift
Em resumo, é preciso levar em conta a segurança ao desserializar dados de usuários usando NSCoding e NSSecureCoding. Embora a documentação possa dar a entender que NSSecureCoding impede a desserialização insegura por padrão, isso não é verdade: ainda há casos em que ela é possível. Sempre que usar funções de decodificação com NSSecureCoding, verifique o tipo do objeto que está sendo desserializado.
Referências
O código de exemplo usado neste artigo está disponível aqui: https://github.com/snoopysecurity/swift-deserialization-security-primer



