|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
// - Limpieza/normalización de entrada // - Validación por allowlist // - Comprobación de límites (rangos y longitudes) // - Acceso seguro a listas (índices) // - Lectura y validación de configuración (Properties) // - Protección básica frente a path traversal import java.nio.file.Files import java.nio.file.Path import java.text.Normalizer import java.util.Properties // ----------------------------- // 1) Limpiar y validar un nombre de usuario // ----------------------------- /** * Limpia y valida un nombre de usuario siguiendo un enfoque de allowlist: * - trim() para espacios * - normalización Unicode (NFKC) para reducir trucos con caracteres * - límite de longitud * - permitir solo caracteres esperados * * Devuelve el username saneado o null si no pasa la validación. */ fun sanitizeUsername(raw: String): String? { val trimmed = raw.trim() // Normaliza Unicode para evitar combinaciones raras (p.ej. letras con diacríticos combinados) val normalized = Normalizer.normalize(trimmed, Normalizer.Form.NFKC) // Comprobación de límites: longitud mínima/máxima if (normalized.length !in 3..20) return null // Allowlist de caracteres permitidos val ok = normalized.matches(Regex("^[a-zA-Z0-9._-]+$")) return if (ok) normalized else null } // ----------------------------- // 2) Parseo seguro de un número con rango (ej. puerto) // ----------------------------- /** * Convierte un texto a Int de forma segura: * - evita excepciones con toIntOrNull() * - valida un rango permitido * * Devuelve el puerto válido o null. */ fun parsePort(raw: String): Int? { val port = raw.trim().toIntOrNull() ?: return null return if (port in 1024..65535) port else null } // ----------------------------- // 3) Acceso seguro a listas (índices) // ----------------------------- /** * Devuelve el elemento de una lista si el índice está dentro de rango. * Evita IndexOutOfBoundsException. */ fun safeGet(list: List<String>, index: Int): String? { return if (index in list.indices) list[index] else null } // ----------------------------- // 4) Cargar configuración y validarla (Properties) // ----------------------------- /** * Representa una configuración mínima ya validada. */ data class AppConfig(val port: Int, val mode: String) /** * Lee un fichero .properties y valida cada campo como si fuese entrada hostil: * - comprueba que existe la clave y no está vacía * - valida tipos (Int) y rangos * - valida valores permitidos (dev/prod) * * Lanza IllegalStateException si algo no cumple. */ fun loadConfig(path: Path): AppConfig { val props = Properties() // Abrimos el fichero de forma segura con use{} para cerrar el stream Files.newInputStream(path).use { props.load(it) } // Función local: obliga a que la propiedad exista y tenga contenido fun requireProp(key: String): String = props.getProperty(key)?.trim()?.takeIf { it.isNotEmpty() } ?: error("Falta o está vacía la propiedad: $key") // Validación: puerto en rango val port = requireProp("port").toIntOrNull() ?.takeIf { it in 1024..65535 } ?: error("port inválido (debe estar entre 1024 y 65535)") // Validación: modo solo puede ser dev o prod val mode = requireProp("mode").lowercase() .takeIf { it in setOf("dev", "prod") } ?: error("mode inválido (solo dev|prod)") return AppConfig(port, mode) } // ----------------------------- // 5) Resolver rutas de forma segura (path traversal) // ----------------------------- /** * Resuelve una ruta "proporcionada por el usuario" dentro de un directorio base, * evitando path traversal: * - resolve() + normalize() * - comprueba que el resultado sigue empezando por baseDir * * Devuelve la ruta segura o null si se detecta intento de escape. */ fun safeResolve(baseDir: Path, userProvided: String): Path? { val candidate = baseDir.resolve(userProvided).normalize() val baseNorm = baseDir.normalize() return if (candidate.startsWith(baseNorm)) candidate else null } // ----------------------------- // main de demostración // ----------------------------- fun main() { // 1) Sanitización de username val userInput = " juan.perez " val username = sanitizeUsername(userInput) println("Username saneado: ${username ?: "INVÁLIDO"}") // 2) Parseo seguro de puerto val portInput = "8080" val port = parsePort(portInput) println("Puerto validado: ${port ?: "INVÁLIDO"}") // 3) Acceso seguro a lista val items = listOf("A", "B", "C") val index = 10 val value = safeGet(items, index) println("Elemento en índice $index: ${value ?: "FUERA DE RANGO"}") // 4) Carga de config (requiere un fichero app.properties en el directorio actual) // Ejemplo de contenido: // port=8080 // mode=prod try { val config = loadConfig(Path.of("app.properties")) println("Config cargada: $config") } catch (e: Exception) { println("Error cargando config: ${e.message}") } // 5) Path traversal val base = Path.of("/app/data") val okPath = safeResolve(base, "reports/2025.txt") println("Ruta OK: ${okPath ?: "BLOQUEADA"}") // println("Ruta mala: ${badPath ?: "BLOQUEADA"}") } |

