Seguridad de aplicaciones Kotlin: top 10 vulnerabilidades en la práctica
Kotlin ocupa con confianza una de las posiciones como uno de los lenguajes más populares para el desarrollo de aplicaciones Android y servidores. Sin embargo, como cualquier otro lenguaje, Kotlin no está exento de vulnerabilidades. En este artículo analizaremos las 10 principales vulnerabilidades de seguridad más críticas que enfrentan los desarrolladores y mostraremos cómo evitarlas en la práctica.
1. Inyección SQL (SQL Injection)
A pesar de que Kotlin se usa a menudo con bibliotecas ORM, el trabajo directo con consultas SQL sigue siendo una práctica común.
Código vulnerable:
fun getUser(id: String): User? { val query = "SELECT * FROM users WHERE id = '$id'" val result = connection.createStatement().executeQuery(query) // ...}Versión segura (usando PreparedStatement):
fun getUserSafe(id: String): User? { val query = "SELECT * FROM users WHERE id = ?" val stmt = connection.prepareStatement(query) stmt.setString(1, id) val result = stmt.executeQuery() // ...}Conclusión: Nunca concatenes la entrada del usuario en consultas SQL. Usa consultas parametrizadas o un ORM (Exposed, Hibernate).
2. Cross-Site Scripting (XSS) en aplicaciones Web
Al desarrollar aplicaciones de servidor con Ktor o Spring Boot, a menudo se olvida escapar la salida.
Código vulnerable (Ktor):
get("/user/{name}") { val name = call.parameters["name"] ?: "Invitado" call.respondText("<h1>¡Hola, $name!</h1>", ContentType.Text.Html)}Versión segura:
import org.apache.commons.text.StringEscapeUtils
get("/user/{name}") { val name = call.parameters["name"] ?: "Invitado" val safeName = StringEscapeUtils.escapeHtml4(name) call.respondText("<h1>¡Hola, $safeName!</h1>", ContentType.Text.Html)}Consejo: Siempre escapa la entrada del usuario antes de mostrarla en HTML. Usa motores de plantillas con escape automático (por ejemplo, Kotlinx.html).
3. Almacenamiento inseguro de contraseñas
Almacenar contraseñas en texto plano o usando hashes débiles es uno de los errores más comunes.
Enfoque incorrecto:
fun savePassword(password: String) { val hash = password.md5() // ¡Nunca hagas esto! database.saveHash(hash)}Enfoque correcto (bcrypt):
import at.favre.lib.crypto.bcrypt.BCrypt
fun savePassword(password: String) { val hash = BCrypt.withDefaults().hashToString(12, password.toCharArray()) database.saveHash(hash)}
fun checkPassword(password: String, storedHash: String): Boolean { return BCrypt.verifyer().verify(password.toCharArray(), storedHash).verified}Importante: Usa bcrypt, scrypt o Argon2. Nunca uses MD5 o SHA-1 para almacenar contraseñas.
4. Fuga de datos confidenciales a través de registros
Los desarrolladores a menudo registran datos sensibles sin verificar qué se envía a la salida.
Código problemático:
logger.info("Usuario $userId autenticado con token: $token")Versión segura:
logger.info("Usuario $userId autenticado exitosamente")// O usa enmascaramiento:fun maskToken(token: String): String = token.take(4) + "****" + token.takeLast(4)Regla: Nunca registres contraseñas, tokens, números de tarjetas de crédito o datos personales. Usa bibliotecas de enmascaramiento (por ejemplo, Logstash).
5. Deserialización insegura
Kotlin usa activamente la serialización JSON. Una configuración incorrecta puede provocar ataques RCE.
Código vulnerable (Jackson):
val mapper = ObjectMapper()val obj = mapper.readValue(userInput, Any::class.j