Cuando str.lower() se convierte en tu peor pesadilla: la vulnerabilidad que Python llevaba años escondiendo

Cuando str.lower() se convierte en tu peor pesadilla: la vulnerabilidad que Python llevaba años escondiendo

Cuando str.lower() se convierte en tu peor pesadilla: la vulnerabilidad que Python llevaba años escondiendo

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

Comentarios

Aún no hay comentarios. ¿Por qué no comienzas el debate?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *