Codificación segura en Swift 4

Desde la minimización del uso del puntero hasta la comprobación de tipos en tiempo de compilación, Swift es un gran lenguaje para el desarrollo seguro. Pero eso significa que es tentador olvidarse por completo de la seguridad. Todavía hay vulnerabilidades, y Swift también atrae a nuevos desarrolladores que aún no han aprendido sobre seguridad. 

Este tutorial es una guía de codificación segura que abordará los cambios en Swift 4, así como las nuevas opciones de herramientas disponibles en Xcode 9 que lo ayudarán a mitigar las vulnerabilidades de seguridad..

Punteros y Desbordamientos

Muchas vulnerabilidades de seguridad han girado en torno a C y su uso de punteros. Esto se debe a que los punteros le permiten acceder a ubicaciones de memoria sin formato, lo que facilita la lectura y la escritura en el área equivocada. Ha sido una forma importante para que los atacantes cambien maliciosamente un programa. 

Swift elimina principalmente los punteros, pero aún así le permite interactuar con C. Muchas API, incluida la API Core Foundation de Apple, se basan completamente en C, por lo que es muy fácil volver a introducir el uso de punteros en Swift. 

Afortunadamente, Apple ha nombrado adecuadamente los tipos de punteros: UnsafePointer, UnsafeRawPointerUnsafeBufferPointer, y UnsafeRawBufferPointer. Llegará un momento en que la API con la que está interactuando devolverá estos tipos, y la regla principal al usarlos es no almacene ni devuelva los punteros para su uso posterior. Por ejemplo:

deja myString = "¡Hola Mundo!" var unsafePointer: UnsafePointer? = nil myString.withCString myStringPointer en unsafePointer = myStringPointer // algo más tarde ... imprimir (unsafePointer? .pointee)

Debido a que accedimos al puntero fuera del cierre, no sabemos con seguridad si el puntero todavía apunta al contenido de memoria esperado. La forma segura de usar el puntero en este ejemplo sería mantenerlo, junto con la declaración de impresión, dentro del cierre. 

Los punteros a cadenas y matrices tampoco tienen verificación de límites. Esto significa que es fácil usar un puntero inseguro en una matriz pero acceder accidentalmente más allá de su límite: un desbordamiento de búfer.

var numbers = [1, 2, 3, 4, 5] numbers.withUnsafeMutableBufferPointer buffer en // ok buffer [0] = 5 print (buffer [0]) // bad buffer [5] = 0 print (buffer [5 ])

La buena noticia es que Swift 4 intenta bloquear la aplicación en lugar de continuar con lo que se llamaría comportamiento indefinido. No sabemos que amortiguador [5] ¡puntos a! Sin embargo, Swift no atrapará todos los casos. Establezca un punto de interrupción después del siguiente código y observe las variables una y do. Se establecerán en 999.

func getAddress (puntero: UnsafeMutablePointer) -> UnsafeMutablePointer return pointer var a = 111 var b = 222 var c = 333 let pointer: UnsafeMutablePointer = getAddress (puntero: & b) pointer.successor (). initialize (to: 999) pointer.predecessor (). initialize (to: 999)

Esto demuestra una desbordamiento de pila porque sin una asignación explícita, las variables generalmente se almacenan en la pila. 

En el siguiente ejemplo, hacemos una asignación con una capacidad de solo una Int8. Las asignaciones se almacenan en el montón, por lo que la siguiente línea desbordará el montón. Para este ejemplo, Xcode solo le advierte con una nota en la consola que consigue es inseguro.

let buffer = UnsafeMutablePointer.asignar (capacidad: 1) obtiene (búfer) 

Entonces, ¿cuál es la mejor manera de evitar los desbordamientos? Es extremadamente importante cuando se interconecta con C para hacer verificaciones de límites en la entrada para asegurarse de que esté dentro del rango. 

Podría estar pensando que es bastante difícil de recordar y encontrar todos los diferentes casos. Así que para ayudarte, Xcode viene con una herramienta muy útil llamada Address Sanitizer. 

Address Sanitizer se ha mejorado en Xcode 9. Es una herramienta que te ayuda a capturar el acceso a la memoria no válido, como los ejemplos que acabamos de ver. Si va a trabajar con el Inseguro* tipos, es una buena idea usar la herramienta desinfectante de direcciones. No está habilitado por defecto, así que para habilitarlo, vaya a Producto> Esquema> Editar esquema> Diagnóstico, y comprobar Desinfectante de direcciones. En Xcode 9 hay una nueva subopción., Detectar uso de pila después de retorno. Esta nueva opción detecta las vulnerabilidades de uso después del alcance y uso después del retorno de nuestro primer ejemplo.

A veces se pasa por alto el desbordamiento de enteros. Esto se debe a que los desbordamientos de enteros son agujeros de seguridad solo cuando se usan como índice o tamaño de un búfer, o si el valor inesperado del desbordamiento cambia el flujo de código de seguridad crítico. Swift 4 captura los desbordamientos de enteros más obvios en el momento de la compilación, como cuando el número es claramente mayor que el valor máximo del entero. 

Por ejemplo, lo siguiente no se compilará..

var someInteger: CInt = 2147483647 someInteger + = 1

Pero muchas de las veces el número llegará dinámicamente en tiempo de ejecución, como cuando un usuario ingresa información en un UITextField. Undefined Behavior Sanitizer es una nueva herramienta en Xcode 9 que detecta desbordamiento de enteros con signo y otros errores de falta de coincidencia de tipo. Para habilitarlo, vaya a Producto> Esquema> Editar esquema> Diagnóstico, y enciende Desinfectante de comportamiento indefinido. Entonces en Configuraciones de compilación> Desinfectante de comportamiento indefinido, conjunto Habilitar cheques extra enteros a .

Hay otra cosa que vale la pena mencionar sobre el comportamiento indefinido. A pesar de que Swift puro oculta los punteros, las referencias y las copias de los buffers todavía se usan detrás de la escena, por lo que es posible encontrar un comportamiento que no esperaba. Por ejemplo, cuando comienza a iterar sobre índices de recopilación, los índices podrían ser modificados accidentalmente por usted durante la iteración.

números var = [1, 2, 3] para números en números números de impresión (número) = [4, 5, 6] //<- accident ???  for number in numbers  print(number) 

Aquí solo causamos la números matriz para apuntar a una nueva matriz dentro del bucle. Entonces que hace número ¿apunta a? Esto normalmente se llamaría una referencia pendiente, pero en este caso Swift crea implícitamente una referencia a una copia del búfer de su matriz durante el ciclo. Eso significa que la declaración de impresión realmente imprimirá 1, 2 y 3 en lugar de 1, 4, 5 ... ¡Esto es bueno! Swift te está salvando de un comportamiento indefinido o de una caída de la aplicación, aunque es posible que tampoco hayas esperado esa salida. Sus desarrolladores pares no esperarán que su colección sea mutada durante la enumeración, así que, en general, tenga mucho cuidado durante la enumeración de que no esté modificando la colección..

Así que Swift 4 tiene una gran aplicación de seguridad en tiempo de compilación para detectar estas vulnerabilidades de seguridad. Hay muchas situaciones en las que la vulnerabilidad no existe hasta el tiempo de ejecución cuando hay interacción del usuario. Swift también incluye la verificación dinámica, que puede detectar muchos de los problemas en el tiempo de ejecución, pero es demasiado costoso hacerlo en subprocesos, por lo que no se realiza para código de multiproceso. La comprobación dinámica detectará muchas violaciones, pero no todas, por lo que es importante escribir código seguro en primer lugar! 

Con eso, pasemos a otra área muy común para los ataques de vulnerabilidades de inyección de código..

Inyección y formato de ataques de cuerdas

Los ataques de cadena de formato ocurren cuando una cadena de entrada se analiza en su aplicación como un comando que no tenía la intención. Si bien las cadenas Swift puras no son susceptibles de formatear ataques de cadena, el Objective-C NSString y Core Foundation CFString Las clases son, y están disponibles en Swift. Ambas de estas clases tienen métodos tales como stringWithFormat.

Digamos que el usuario puede ingresar texto arbitrario desde un UITextField.

deje inputString = "Cadena desde un campo de texto% @% d% p% ld% @% @" como NSString

Esto podría ser un agujero de seguridad si la cadena de formato se maneja directamente.

dejar que textFieldString = NSString.init (formato: inputString) // mal dejar textFieldString = NSString.init (formato: "% @", inputString) // bueno

Swift 4 intenta manejar los argumentos de cadena de formato faltantes devolviendo 0 o NULL, pero es especialmente preocupante si la cadena se pasará de nuevo al tiempo de ejecución de Objective-C.

NSLog (textFieldString); // mal NSLog ("% @", textFieldString); //bueno

Si bien la mayoría de las veces la forma incorrecta solo causará un bloqueo, un atacante puede diseñar cuidadosamente una cadena de formato para escribir datos en ubicaciones de memoria específicas en la pila para alterar el comportamiento de su aplicación (como cambiar una es autenticado variable). 

Otro gran culpable es NSPredicate, que puede aceptar una cadena de formato que se utiliza para especificar qué datos se recuperan de los Datos del Core. Cláusulas como ME GUSTA y CONTIENE Permitir comodines y debe evitarse, o al menos solo usarse para búsquedas. La idea es evitar la enumeración de cuentas, por ejemplo, donde el atacante ingresa "a *" como nombre de cuenta. Si cambias el ME GUSTA cláusula a ==, esto significa que la cadena tiene que coincidir literalmente con "a *". 

Otros ataques comunes ocurren al terminar la cadena de entrada antes con un carácter de comilla simple para que se puedan ingresar comandos adicionales. Por ejemplo, un inicio de sesión se puede omitir ingresando ') O 1 = 1 O (contraseña LIKE' * en el UITextField. Esa línea se traduce como "donde la contraseña es como cualquier cosa", que omite la autenticación por completo. La solución es escapar por completo de cualquier intento de inyección agregando sus propias comillas dobles en el código. De esa manera, cualquier cita adicional del usuario se verá como parte de la cadena de entrada en lugar de ser un carácter de terminación especial:

dejar consulta = NSPredicate.init (formato: "contraseña == \"% @ \ "", nombre)

Una forma más de protegerse contra estos ataques es simplemente buscar y excluir caracteres específicos que sepa que podrían ser dañinos en la cadena. Los ejemplos incluirían citas, o incluso puntos y barras. Por ejemplo, es posible hacer una ataque transversal del directorio cuando la entrada pasa directamente a la Administrador de archivos clase. En este ejemplo, el usuario ingresa "... /" para ver el directorio principal de la ruta en lugar del subdirectorio deseado.

let userControllerString = "… /" como NSString let sourcePath = NSString.init (formato: "% @ /% @", Bundle.main.resourcePath!, userControllerString) NSLog ("% @", sourcePath) // En lugar de compilación / Productos / Debug / Swift4.app / Contents / Resources, será Build / Products / Debug / Swift4.app / Contents let filemanager: FileManager = FileManager () let files = filemanager.enumerator (atPath: sourcePath as String) mientras que el archivo = archivos? .nextObject () imprimir (archivo)

Otros caracteres especiales pueden incluir un byte de terminación NULL si la cadena se utiliza como una cadena C. Los punteros a las cadenas C requieren un byte de terminación NULL. Debido a esto, es posible manipular la cadena simplemente introduciendo un byte NULO. El atacante podría querer terminar la cadena antes si hubiera una bandera como needs_auth = 1, o cuando el acceso está activado de forma predeterminada y desactivado explícitamente, como con is_subscriber = 0.

let userInputString = "username = Ralph \ 0" como NSString let commandString = NSString.init (formato: "subscribe_user:% @ & needs_authorization = 1", userInputString) NSLog ("% s", commandString.utf8String!) // imprime subscribe_user: username = Ralph en lugar de subscribe_user: username = Ralph & needs_authorization = 1

El análisis de cadenas HTML, XML y JSON también requiere atención especial. La forma más segura de trabajar con ellos es usar las bibliotecas nativas de Foundation que proporcionan objetos para cada nodo, como el NSXMLParser clase. Swift 4 introduce la serialización de tipo seguro para formatos externos como JSON. Pero si está leyendo XML o HTML utilizando un sistema personalizado, asegúrese de que no se puedan usar caracteres especiales de la entrada del usuario para instruir al intérprete.

  • < debe convertirse & lt.
  • > debe ser reemplazado con & gt.
  • Y debe convertirse &erio.
  • Dentro de los valores de los atributos, cualquiera " o ' necesita convertirse & quot y y apos, respectivamente.

Aquí hay un ejemplo de una forma rápida de eliminar o reemplazar caracteres específicos:

var myString = "string to sanitize;" myString = myString.replacingOccurrences (of: ";", with: "")

Un área final para los ataques de inyección está dentro de los manejadores de URL. Asegúrese de que la entrada del usuario no se usa directamente en los controladores de URL personalizados openURL y didReceiveRemoteNotification. Verifique que la URL sea lo que espera y que no permita que un usuario ingrese información de manera arbitraria para manipular su lógica. Por ejemplo, en lugar de permitir que el usuario elija la pantalla de la pila para navegar por índice, permita solo pantallas específicas utilizando un identificador opaco, como t = es84jg5urw

Si estas usando WKWebViews en su aplicación, podría ser bueno revisar las URL que se cargarán allí también. Usted puede anular decidePolicyFor navigationAction, que le permite elegir si desea continuar con la solicitud de URL. 

Algunos trucos conocidos de webview incluyen la carga de esquemas de URL personalizados que el desarrollador no pretendía, como ID de aplicación: para lanzar una aplicación completamente diferente o SMS: enviar un mensaje de texto. Tenga en cuenta que las vistas web integradas no muestran una barra con la dirección URL o el estado SSL (el icono de bloqueo), por lo que el usuario no puede determinar si la conexión es de confianza. 

Si la vista web es a pantalla completa, por ejemplo, la URL podría ser pirateada con una página web que se parece a su pantalla de inicio de sesión, salvo que se dirijan las credenciales a un dominio malicioso. Otros ataques en el pasado han incluido ataques de scripts entre sitios que han filtrado cookies e incluso todo el sistema de archivos.. 

La mejor prevención para todos los ataques mencionados es tomarse el tiempo para diseñar su interfaz utilizando controles de UI nativos en lugar de simplemente mostrar una versión basada en web dentro de su aplicación..

Hasta ahora, hemos estado viendo tipos de ataques relativamente sencillos. Pero terminemos con un ataque más avanzado que puede ocurrir en el tiempo de ejecución.

Piratería en tiempo de ejecución

Al igual que Swift se vuelve más vulnerable cuando interactúa con C, la interfaz con Objective-C trae vulnerabilidades separadas a la mesa.. 

Ya hemos visto los problemas con NSString y los ataques de cadena de formato. Otro punto es que Objective-C es mucho más dinámico como lenguaje, lo que permite pasar tipos y métodos sueltos. Si su clase Swift hereda de NSObject, entonces se abre a ataques de tiempo de ejecución de Objective-C. 

La vulnerabilidad más común implica el intercambio dinámico de un método de seguridad importante por otro método. Por ejemplo, un método que se devuelve si se valida un usuario podría cambiarse por otro método que casi siempre devolverá verdadero, como isRetinaDisplay. Minimizar el uso de Objective-C hará que su aplicación sea más robusta contra este tipo de ataque.

En Swift 4, los métodos en clases que heredan de una clase de Objective-C solo están expuestos al tiempo de ejecución de Objective-C si esos métodos o las clases en sí están marcados con @atributo. A menudo, la función Swift se llama en su lugar, incluso si la @objc atributo se utiliza. Esto puede suceder cuando el método tiene una @objc atributo pero nunca se llama realmente desde Objective-C. 

En otras palabras, Swift 4 introduce menos @objc Inferencia, por lo que esto limita la superficie de ataque en comparación con las versiones anteriores. Sin embargo, para admitir las características de tiempo de ejecución, los binarios basados ​​en Objective-C deben conservar una gran cantidad de información de clase que no se puede eliminar. Esto es suficiente para que los ingenieros inversos reconstruyan la interfaz de clase para averiguar qué secciones de seguridad deben parchearse, por ejemplo. 

En Swift, hay menos información expuesta en el binario, y los nombres de las funciones están alterados. Sin embargo, el desbarbado se puede deshacer mediante la herramienta Xcode swift-demangle. De hecho, las funciones Swift tienen un esquema de denominación consistente, que indica si cada una es una función Swift o no, parte de una clase, nombre y longitud del módulo, nombre y longitud de la clase, nombre y longitud del método, atributos, parámetros y tipo de retorno. 

Estos nombres son más cortos en Swift 4. Si le preocupa la ingeniería inversa, asegúrese de que la versión de lanzamiento de sus símbolos de tiras de aplicaciones vaya a Configuraciones de compilación> Implementación> Strip Swift Symbols y configurando la opción para Sí.

Además de ofuscar el código de seguridad crítico, también puede solicitar que esté en línea. Esto significa que cualquier lugar al que se llame la función en su código, el código se repetirá en ese lugar en lugar de existir solo en una ubicación del binario. 

De esta manera, si un atacante logra omitir un control de seguridad en particular, no afectará ninguna otra ocurrencia de ese control situado en otros lugares de su código. Cada revisión debe ser parcheada o enganchada, lo que hace que sea mucho más difícil realizar una grieta con éxito. Puede en línea código como este:

@inline (__ siempre) func myFunction () //…

Conclusión

Pensar en la seguridad debería ser una gran parte del desarrollo. El simple hecho de esperar que el lenguaje sea seguro puede llevar a vulnerabilidades que podrían haberse evitado. Swift es popular para el desarrollo de iOS, pero está disponible para aplicaciones de escritorio macOS, tvOS, watchOS y Linux (por lo que podría usarlo para los componentes del lado del servidor donde el potencial de explotaciones de ejecución de código es mucho mayor). La caja de arena de la aplicación se puede romper, como en el caso de los dispositivos con jailbreak que permiten que se ejecute un código sin firmar, por lo que es importante seguir pensando en la seguridad y prestar atención a los avisos de Xcode mientras se realiza la depuración.. 

Un último consejo es tratar las advertencias del compilador como errores. Puedes forzar Xcode para hacer esto yendo a Configuraciones de compilación y configuración Tratar las advertencias como errores a . No olvide modernizar la configuración de su proyecto al migrar a Xcode 9 para obtener advertencias mejoradas y, por último pero no menos importante, haga uso de las nuevas funciones disponibles al adoptar Swift 4 hoy!

Aprende rápido

Hemos creado una guía completa para ayudarlo a aprender Swift, ya sea que esté comenzando con lo básico o si desea explorar temas más avanzados..

Para obtener una introducción a otros aspectos de la codificación segura para iOS, consulte algunas de mis otras publicaciones aquí en Envato Tuts+!