Guía básica de seguridad de deserialización en Swift
Sam Sanoop
18 de julio de 2023
0 minutos de lecturaLa deserialización es el proceso de convertir datos de un formato serializado, como JSON o binario, a su forma original. Swift ofrece varios protocolos que permiten convertir objetos y valores hacia y desde listas de propiedades, JSON y otras representaciones binarias planas.
La deserialización también puede introducir vulnerabilidades de seguridad inadvertidas en la base de código de un usuario, que los atacantes podrían aprovechar. En este artículo se detallan las vulnerabilidades de deserialización en Swift que pueden surgir al usar las populares API NScoding y NSSecureCoding, y cómo prevenirlas correctamente.
NSCoding
NSCoding es un protocolo que Apple ofrece para serializar y deserializar datos. El protocolo predeterminado para serializar y deserializar datos con NSCoding es vulnerable a ataques de sustitución de objetos, que podrían permitir que un atacante ejecute código de forma remota.
Considera la siguiente clase, que implementa NSCoding con dos métodos: initWithCoder: y encodeWithCoder:. Las clases que se ajustan a NSCoding se pueden serializar y deserializar en datos que pueden archivarse en un disco o distribuirse a través de una red.
La clase anterior se ajusta al protocolo NSCoding y tiene dos propiedades: name y role. También implementa el inicializador required init(coder:) para decodificar las propiedades name y role de un archivo, y el método encode(with:) para codificar esas propiedades en un archivo.
Luego, se puede crear un objeto employee y archivarlo en un archivo mediante NSKeyedArchiver.
Después, se puede desarchivar el objeto employee desde el archivo mediante NSKeyedUnarchiver para imprimir los valores de las propiedades name y role.
Esto se podría aprovechar de la siguiente manera. Supongamos que existen otras clases en la base de código, incluida la siguiente. Esta clase tiene una sola propiedad llamada "command", una cadena que representa una ruta de archivo. La clase también tiene dos métodos, "encode(with:)" e "init?(coder:)", que son necesarios para ajustarse al protocolo NSCoding.
La propiedad command se obtiene de un objeto codificado y llega a la función sink1, que ejecuta la propiedad como parte de un comando.
Un atacante puede aprovechar esta clase para ejecutar código y llevar a cabo un ataque de deserialización que puede provocar una inyección de comandos.
NSSecureCoding
NSSecureCoding es una alternativa segura a NSCoding que ofrece funciones de seguridad mejoradas para proteger contra ataques de deserialización. A diferencia de NSCoding, NSSecureCoding impone requisitos de seguridad más estrictos a los objetos codificados y decodificados, incluida la necesidad de definir una jerarquía de clases e implementar medidas de seguridad específicas en las propias clases. Estas medidas adicionales ayudan a evitar la manipulación de datos serializados que podría dar lugar a la creación de objetos capaces de ejecutar código malicioso, lo que mejora la seguridad general del sistema. Sin embargo, los ataques de deserialización aún pueden ocurrir según cómo se use.
Decodificar sin verificar el tipo de clase
En el siguiente ejemplo, el código se modificó para ajustarse a NSSecureCoding. La propiedad supportSecureCoding se configuró como true y, como antes, se usa el método decodeObject para deserializar objetos codificados.
Sin embargo, como se indica en la documentación para desarrolladores de Apple sobre NSSecureCoding, esta técnica es potencialmente insegura porque, cuando se verifica el tipo de clase, el objeto ya se construyó y, si forma parte de una clase de colección, posiblemente ya se insertó en un grafo de objetos. Configurar supportsSecureCoding como true marca un objeto como seguro para decodificar. Sin embargo, NSSecureCoding no verifica el tipo de este objeto ni si corresponde a la clase employee pertinente.
Para corregir este problema, se puede especificar la clave of con decodeObject(of:Employee.self, forKey: "name"), que solo decodifica objetos de la clase especificada (en este caso, employee). Esto garantiza que la propiedad name solo se pueda decodificar como una cadena y no como un objeto creado con fines maliciosos. Como alternativa, se puede usar el método decodeObjectOfClass, que decodifica un objeto para la clave y lo restringe a la clase especificada. Además, no se deben usar los métodos decodeObjectForKey ni decodeTopLevelObjectForKey, que también se ven afectados por este problema y Apple considera obsoletos.
supportsSecureCoding configurado como False
Como se indica en la documentación para desarrolladores de Apple, al escribir una clase que admite codificación segura, debes asegurarte de que el getter de esta propiedad de clase devuelva true. Configurar supportsSecureCoding = false sigue ajustándose a NSSecureCoding y da a los desarrolladores una falsa sensación de seguridad, a la vez que permite ataques de deserialización.
Deserialización segura en Swift
En resumen, debes tener en cuenta las consideraciones de seguridad al deserializar datos de usuario con NSCoding y NSSecureCoding. Aunque la documentación podría dar a entender que NSSecureCoding evita la deserialización de forma predeterminada, no es así, y hay casos en los que la deserialización insegura sigue siendo posible. Siempre que uses funciones de decodificación con NSSecureCoding, asegúrate de verificar el tipo del objeto que se está deserializando.
Referencias
El código de ejemplo usado en este artículo está disponible aquí: https://github.com/snoopysecurity/swift-deserialization-security-primer



