Introducción
Kotlin es un lenguaje de programación moderno que se utiliza activamente para el desarrollo de aplicaciones Android, soluciones de servidor (Ktor, Spring Boot) y proyectos multiplataforma. A pesar de su tipado estricto y seguridad contra nulos, los desarrolladores suelen cometer errores que conducen a vulnerabilidades graves. En este artículo analizaremos el top 10 de vulnerabilidades en aplicaciones Kotlin, basadas en los estándares OWASP, y mostraremos cómo evitarlas.
El artículo será útil tanto para desarrolladores Kotlin principiantes como experimentados que deseen mejorar el nivel de seguridad de sus proyectos.
1. Inyección SQL (SQL Injection)
Problema: Inserción de código SQL malicioso a través de la entrada del usuario.
Ejemplo de código vulnerable:
// ¡Vulnerable! No use concatenación de cadenasfun getUserById(id: String): User? { val query = "SELECT * FROM users WHERE id = '$id'" val result = connection.createStatement().executeQuery(query) return result.toUser()}Solución: Use consultas parametrizadas (PreparedStatement).
// Segurofun getUserById(id: String): User? { val query = "SELECT * FROM users WHERE id = ?" val preparedStatement = connection.prepareStatement(query) preparedStatement.setString(1, id) val result = preparedStatement.executeQuery() return result.toUser()}2. Cross-Site Scripting (XSS)
Problema: Inserción de código JavaScript malicioso en una página web.
Ejemplo de código vulnerable (Ktor):
// ¡Vulnerable! No escape la salidaget("/search") { val query = call.parameters["q"] ?: "" call.respondHtml { h1 { +"Resultados de búsqueda para: $query" } // XSS! }}Solución: Use escape de HTML (por ejemplo, kotlinx.html con escape).
// Seguroimport kotlinx.html.escapeHtml
get("/search") { val query = call.parameters["q"] ?: "" val safeQuery = query.escapeHtml() call.respondHtml { h1 { +"Resultados de búsqueda para: $safeQuery" } }}3. Almacenamiento inseguro de contraseñas
Problema: Almacenamiento de contraseñas en texto plano o usando hashes débiles (MD5, SHA-1).
Ejemplo de código vulnerable:
// ¡Vulnerable! MD5 no es adecuado para contraseñasfun saveUser(password: String) { val hash = MessageDigest.getInstance("MD5").digest(password.toByteArray()) // guardamos hash en la BD}Solución: Use bcrypt, scrypt o Argon2.
// Seguro (usando la librería jbcrypt)import org.mindrot.jbcrypt.BCrypt
fun hashPassword(password: String): String { return BCrypt.hashpw(password, BCrypt.gensalt(12))}
fun checkPassword(password: String, hashed: String): Boolean { return BCrypt.checkpw(password, hashed)}4. Fuga de datos confidenciales a través de logs
Problema: Registro en logs de contraseñas, tokens, números de tarjetas.
Ejemplo de código vulnerable:
// ¡Vulnerable! Registramos datos sensiblesfun login(username: String, password: String) { log.info("Intento de inicio de sesión: username=$username, password=$password")}Solución: Use filtros o enmascaramiento.
// Segurofun login(username: String, password: String) { log.info("Intento de inicio de sesión: username=$username") // password no se registra}5. Deserialización insegura
Problema: Ejecución de código arbitrario al deserializar datos no verificados.
Ejemplo de código vulnerable:
// ¡Vulnerable! Deserialización sin verificaciónfun process