
Hace unos dÃas me topé con un artÃculo de Seth Larson que me dejó helado. Resulta que str.lower(), esa función que usas mil veces al dÃa en Python sin pensarlo, es una vulnerabilidad de seguridad activa. Y no es un bug nuevo: viene camuflado desde que se implementó IDNA 2003 en el estándar Unicode.
El problema: Unicode no es estático
La wea es asÃ: cuando Python hace str.lower(), no está usando las reglas de 2003. Está usando la versión más reciente de Unicode que tenga instalada tu intérprete. En Python moderno puede ser Unicode 17.0.0. Y eso cambia cómo se comportan ciertos caracteres.
El ejemplo que Larson usa es brutal. El carácter Ꭰ(U+13A0, letra Cherokee) en Unicode 3.2.0 se mapea a sà mismo en lowercase. Pero en Unicode 17.0.0, str.lower() lo transforma en algo distinto. Entonces, si usas .encode("idna") para validar un dominio internacionalizado, obtienes un resultado distinto dependiendo de la versión de Python.
Esto abre la puerta a ataques de homógrafo IDNA: un atacante registra un dominio que se ve igual en una versión de Python pero distinto en otra, y tu validación de seguridad falla silenciosamente.
¿Y la solución?
Python tiene un módulo oculto llamado unicodedata.ucd_3_2_0 que justamente existe para StringPrep e IDNA. Pero el código de stringprep.py y encodings/idna.py en el estándar sigue llamando str.lower() en vez de usar esa base de datos congelada.
O sea: la herramienta correcta existe desde hace años, pero nadie la conectó donde correspondÃa.
Mi opinión
Como ingeniero de sistemas que mantiene servidores con Python desde hace rato, esto me da rabia y me da miedo a la vez. Me da rabia porque es el tipo de bug que solo se encuentra cuando alguien se pone a leer RFCs de 2003 en detalle. Me da miedo porque hay miles de validadores de email, de dominio y de URL por ahà que usan .lower() o casefold() asumiendo que Unicode es una roca inmutable.
No lo es. Unicode es un rÃo. Y si tu código de seguridad asume que el rÃo está quieto, te vas a mojar.
La lección que me llevo es simple: nunca uses funciones de string estándar para lógica de seguridad que dependa de estándares congelados en el tiempo. Si el RFC dice Unicode 3.2.0, usa Unicode 3.2.0. Punto. No confÃes en que Python, Java o cualquier lenguaje te lo mantenga por compatibilidad. La compatibilidad es un mito cuando hablamos de Unicode.
Si eres de los que revisan código en la pega, agrega este caso a tu checklist de seguridad. Revisa dónde usas lowercase en validaciones de dominio o email. PodrÃas estar dejando la puerta abierta sin saberlo.
Fuente de inspiración: When str.lower() is a security vulnerability in Python
