Por qué aparecen URLs doblemente codificadas en sistemas reales
Una URL debería codificarse en la etapa correcta y solo para la parte relevante. Los problemas comienzan cuando los desarrolladores codifican la cadena completa manualmente y luego la pasan a una herramienta que la codifica de nuevo.
Las causas comunes incluyen:
- codificar un parámetro de consulta antes de darlo a URLSearchParams
- codificar una URL de redirección completa y luego embebida dentro de otra URL
- middleware de framework reescribiendo valores ya procesados
- código frontend enviando entrada codificada a un backend que la codifica de nuevo
- reglas de proxy o WAF transformando componentes de la solicitud inesperadamente
Un ejemplo visual sencillo
Supongamos que el valor original es:
/docs/My File.pdf
Codificación única correcta para un fragmento de ruta:
/docs/My%20File.pdf
Después de un segundo paso, se convierte en:
/docs/My%2520File.pdf
El servidor puede ahora buscar un archivo que literalmente contiene %20 en su nombre en lugar de un espacio.
Cómo luce el problema de la codificación doble de URL
La parte más difícil es que la URL todavía parece «codificada», por lo que los equipos depuran primero la capa equivocada. Los síntomas dependen del contexto.
Síntomas típicos en producción
|
Síntoma |
Lo que se ve |
Causa probable |
|
Redirección rota |
El usuario llega a página de error o ruta equivocada |
Destino de redirección codificado dos veces |
|
Consulta API inválida |
El backend recibe un valor de filtro distorsionado |
Cliente y servidor codifican ambos |
|
No coincidencia de firma |
La verificación de HMAC o URL firmada falla |
La codificación se modificó después de firmar |
|
Archivo o activo faltante |
La ruta se resuelve incorrectamente |
El segmento de ruta codificado se volvió a codificar |
|
Omisión de regla de seguridad o falso positivo |
El filtro se comporta de manera inconsistente |
Diferentes etapas de decodificación entre capas |
Por qué las URLs anidadas son arriesgadas
Una trampa clásica aparece con las URLs de retorno:
https://app.example.com/login?next=https://site.example.com/account?tab=settings
Si la URL interna debe convertirse en un valor de parámetro, debería codificarse correctamente una sola vez. Pero muchas apps primero la codifican a mano, luego un enrutador o cliente HTTP la codifica de nuevo. Eso crea flujos de callback malformados y logs difíciles de leer.
Errores de codificación doble que cometen los desarrolladores
El error más común es no entender qué API espera datos en bruto y cuál espera datos codificados. Las buenas bibliotecas generalmente quieren valores sin codificar y manejan el escape internamente.
Ejemplo en JavaScript
Incorrecto:
const next = encodeURIComponent("https://site.example.com/account?tab=settings");
const url = "/login?next=" + encodeURIComponent(next);
Esto produce un valor transformado dos veces.
Mejor:
const next = "https://site.example.com/account?tab=settings";
const url = "/login?next=" + encodeURIComponent(next);
Ejemplo con constructores de consultas
Patrón incorrecto:
- codificar manualmente name=John Doe
- pasar el resultado a un constructor de consultas
- el constructor de consultas codifica % de nuevo
Patrón correcto:
- pasar John Doe en bruto
- dejar que una capa de confianza construya la cadena de consulta final
Cómo decodificar URLs con doble codificación de forma segura
La decodificación es fácil solo cuando se sabe cuántas veces se transformó el valor. La decodificación repetida a ciegas es peligrosa, especialmente en código sensible a la seguridad, porque puede alterar literales intencionados o ayudar a las cargas útiles a eludir la validación.
Enfoque práctico de decodificación
- Inspeccionar el valor de solicitud en bruto desde logs o un proxy de depuración.
- Decodificar una vez y comparar el resultado.
- Si quedan secuencias de porcentaje donde debería haber caracteres simples, inspeccionar si una segunda decodificación está justificada.
- Normalizar solo en un límite controlado.
- Validar después de la normalización, no antes.
Ejemplo:
Original: hello%2520world
Decodificar una vez: hello%20world
Decodificar dos veces: hello world
Eso muestra un valor codificado dos veces. Pero no convertir esto en una regla ciega de «decodificar dos veces» para cada solicitud.
Lista de verificación de depuración más segura
- capturar la URL en bruto exacta
- separar ruta, consulta y fragmento
- probar un paso de decodificación a la vez
- verificar qué capa realizó cada transformación
- confirmar si el valor debía mantenerse codificado
Prevenir la codificación doble de URL por diseño
La mejor solución es la disciplina arquitectónica, no los parches.
Reglas que funcionan en la práctica
- codificar en el límite donde se ensambla la URL final
- mantener los valores internos en bruto tanto tiempo como sea posible
- nunca codificar manualmente antes de pasar datos a un constructor de URL de confianza
- no decodificar y volver a codificar sin una razón específica
- documentar si los auxiliares esperan entrada en bruto o codificada
- firmar las URLs solo después de que la forma canónica final esté lista
Buena convención de equipo
Elegir un lugar responsable de la transformación de URL:
- enrutador frontend
- auxiliar de URL del backend
- biblioteca de cliente de API
- capa de normalización del gateway
Una vez que la responsabilidad está clara, el escape duplicado disminuye drásticamente.
Casos límite de codificación doble en parámetros anidados
Algunos casos parecen errores pero en realidad son intencionales. Por ejemplo, si una URL está embebida dentro de otra como dato, la codificación debe preservar separadores como ?, = y & dentro del valor anidado. La clave es que la URL anidada debería codificarse una vez para su papel como valor de parámetro, no repetidamente en cada salto.
Ejemplo: parámetro de redirección
/auth?return_to=https%3A%2F%2Fapp.example.com%2Fdashboard%3Fpage%3D2
Esto es normal. Se convierte en un problema solo si el mismo valor luego se transforma en:
/auth?return_to=https%253A%252F%252Fapp.example.com%252Fdashboard%253Fpage%253D2
Esa segunda versión generalmente indica procesamiento adicional.
Manejo de valores ya transformados en APIs y middleware
Las pilas de framework a menudo mezclan el manejo automático y manual. Un proxy inverso puede normalizar una vez, el framework puede decodificar en parámetros de ruta, y el middleware personalizado puede aplicar otro paso. El resultado es inconsistencia entre logs, entrada del handler y llamadas a servicios posteriores.
Dónde auditar primero
- reglas de reescritura del proxy inverso
- configuración de normalización de CDN o WAF
- extracción de parámetros de ruta
- auxiliares de redirección
- constructores de solicitudes de SDK de terceros
- filtros de seguridad personalizados
Una auditoría breve en estas capas generalmente revela por qué un valor cambió más de lo esperado.
Conclusión
La codificación doble rara vez es un misterio de bajo nivel. Generalmente es un fallo de coordinación entre capas que todas intentan ayudar con la misma URL. La solución es tratar la codificación como un paso de responsabilidad única, mantener los valores internamente en bruto y normalizar cuidadosamente en el borde.
Cuando una URL parece incorrecta, no limitarse a parchearla con decodificación adicional. Rastrear la ruta completa del valor: dónde comenzó, quién lo codificó, quién lo volvió a codificar y si el componente final era una ruta, un valor de consulta o una URL anidada. Ese enfoque resuelve el error sin crear un sistema más frágil.