Swift en primera línea: por qué la seguridad no es una opción, sino una necesidad
\nEscribes en Swift, te enorgulleces del rendimiento y la belleza del código. Pero ¿alguna vez te has preguntado qué tan vulnerable es tu aplicación a ataques? Las inyecciones SQL y XSS (Cross-Site Scripting) no son solo historias de terror de los libros de texto. Son amenazas reales que pueden costarte datos de usuarios, reputación y dinero.
\nEn este artículo, analizaremos cómo proteger una aplicación Swift (ya sea un cliente iOS o una parte del servidor con Vapor) contra los dos tipos de ataques más peligrosos. Sin rodeos: solo ejemplos prácticos, soluciones listas y código real. ¡Allá vamos!
\n\nInyecciones SQL en Swift: cómo los hackers «engañan» a la base de datos
\nImagina: un usuario escribe en un campo de texto '; DROP TABLE Users; --. Si insertas directamente esta cadena en una consulta SQL, la base de datos ejecutará el comando de eliminación. Un clásico del género.
En Swift, esta vulnerabilidad se encuentra más a menudo en frameworks del lado del servidor (Vapor, Kitura) o al trabajar con Core Data y SQLite a través de envoltorios inseguros.
\n\nEjemplo peligroso: cómo NO hacerlo
\nimport SQLite\n\nlet username = userInput.text // "admin' OR '1'='1"\nlet query = "SELECT * FROM users WHERE name = '\\(username)'"\ntry db.execute(query) // ¡AMENAZA! Consulta comprometida\n\nEste código es una bandera roja. Un atacante puede obtener acceso a cualquier dato o destruir la tabla.
\n\nEnfoque correcto: consultas parametrizadas
\nRegla de oro: nunca insertes datos de usuario directamente en una cadena de consulta. Usa parametrización.
\nimport SQLite\n\nlet db = try Connection("path/to/db.sqlite3")\nlet users = Table("users")\nlet name = Expression<String>("name")\n\nlet username = userInput.text // "admin' OR '1'='1"\nlet query = users.filter(name == username) // SEGURO\nlet result = try db.prepare(query)\n\nLa librería SQLite.swift escapa los caracteres especiales automáticamente. Para Vapor con Fluent, usa el modelo y filtros:
\nimport Fluent\nimport Vapor\n\nstruct UserController: RouteCollection {\n func login(req: Request) async throws -> String {\n let input = try req.content.decode(LoginRequest.self)\n // Fluent parametriza automáticamente la consulta\n let user = try await User.query(on: req.db)\n .filter(\\.$name == input.username)\n .first()\n return user?.name ?? "Not found"\n }\n}\n\nImportante: incluso si usas SQL raw en Vapor, aplica SQLBind para vincular parámetros.
ORM y Core Data: la ilusión de seguridad
\nMuchos desarrolladores creen que Core Data o Fluent protegen automáticamente contra inyecciones. Esto es cierto, siempre que no uses NSExpression con formato de cadenas o SQL raw. Pero recuerda: cualquier NSPredicate construido mediante interpolación de cadenas es un riesgo.
// PELIGROSO\nlet predicate = NSPredicate(format: "name == %@", userInput) // ¿Seguro?\n// ¡NO! Si userInput contiene '%', puede ocurrir una inyección mediante sustitución.\n\nSiempre usa %@ con variables, no %K con claves, y nunca insertes cadenas mediante \\().
Ataques XSS en Swift: cuando el atacante inyecta JavaScript
\nXSS es un ataque en el que un script malicioso se ejecuta en el navegador del usuario. Para aplicaciones iOS, XSS es relevante al usar WKWebView para mostrar contenido HTML (por ejemplo, en un feed de noticias o chats).
Escenario real: chat con formato HTML
\nUn usuario escribe: ¡Hola! <script>alert('XSS')</script>. Si muestras este mensaje en un WebView sin procesarlo, el script se ejecutará.
// PELIGROSO\nlet html = "<div>\\(message.text)</div>"\nwebView.loadHTMLString(html, baseURL: nil)\n