Reparar el límite del enlace de restablecimiento

CiberseguridadBeginner
Practicar Ahora

Introducción

El portal de miembros de Harbor permite recuperar una cuenta cuando se olvida la contraseña. El correo de restablecimiento lleva un token temporal: poseerlo debería autorizar un solo cambio de contraseña para una sola cuenta. Soporte ha descubierto que los enlaces se reutilizan y pueden cambiar la contraseña de otra cuenta.

Primero recuperarás una cuenta ficticia normalmente, observarás el comportamiento inseguro y repararás un pequeño controlador de Python. No necesitas experiencia en Python: la lección explica funciones y condiciones antes de editar. Debes saber introducir un comando y cambiar de pestaña.

Usa Portal Web para la aplicación, Mailbox Web para el correo simulado y App Server para el código preparado. Todo funciona en esta instancia de formación independiente. No se envía correo externo. Los tokens, las comparaciones de caducidad, las transacciones, los cambios de contraseña y el inicio de sesión son reales; esta aplicación no es un sistema de identidad de producción.

Recuperar una cuenta normalmente

En este paso, seguirás un correo de restablecimiento e iniciarás sesión con la nueva contraseña para establecer el funcionamiento que debe conservar la reparación.

Una cuenta identifica a una persona; una contraseña demuestra que puede iniciar sesión. Si se olvida, el enlace autoriza temporalmente su cambio. El token del enlace no es la contraseña. Este buzón local permite actuar como propietario de las cuentas ficticias maya y noah, ambas con StartPass42! inicialmente. Usa solo estas cuentas de formación.

En Portal Web, busca Request a reset link. Introduce maya en Recovery account y pulsa Send reset link. Aparece Link requested. Open Local Mailbox. Actualiza Mailbox Web: el mensaje más reciente está arriba y muestra destinatario, Open reset link y una cadena de token.

Pulsa Open reset link en ese mensaje. El formulario se abre en la misma pestaña con Token y Reset account rellenados. No los cambies; introduce FirstPass42! en New password y pulsa Reset password. Espera Reset accepted y HTTP 200.

HTTP es la conversación de peticiones y respuestas entre navegador y servidor. Aquí 200 significa aceptado, 403 indica una petición denegada y 500 un error de aplicación. Un error de conexión es distinto de una denegación de seguridad.

Abre Portal Web, actualiza si hace falta y usa Sign in: Login account maya, Password FirstPass42!, botón Sign in. Espera Login accepted. Un restablecimiento funcional cambia la contraseña real, no solo un texto de la página.

Si pierdes el enlace, solicita otro con el mismo formulario y usa el mensaje más reciente. Dura 30 minutos. Una errata o un enlace caducado no requieren reiniciar el entorno.

Observar el límite roto

En este paso, harás cambios de demostración desechables para distinguir propietario, vigencia y permiso de un solo uso del token.

El navegador envía campos con nombre y valor. Como el usuario puede cambiarlos, el servidor no puede confiar en Reset account solo porque el correo lo rellenó. El servidor guarda un registro separado:

Campo Significado
account Cuenta autorizada a usar el token
expires_at Último momento permitido, en segundos desde un origen de reloj fijo
used 0 antes del uso; 1 después

Un token nuevo solo sirve para su propietario antes de caducar. Un cambio correcto lo deja usado y nunca debe volver a ser utilizable. Uno sin usar también puede caducar con el tiempo. Ninguno de esos estados autoriza otro cambio.

Solicita un enlace nuevo para maya en Portal Web. Actualiza Mailbox Web, abre el mensaje más reciente y cambia su contraseña a DemoPass42!. Vuelve a abrir el enlace del mismo mensaje y cámbiala a ReplayDemo42!. Ambos muestran Reset accepted. El segundo debería denegarse: es una repetición, reutilizar un permiso que ya debía agotarse.

Abre otra vez ese enlace. Cambia Reset account a noah y New password a CrossDemo42!; pulsa Reset password. Se acepta. En Portal Web, inicia sesión como noah con CrossDemo42!. Un enlace de maya ha cambiado a noah: el servidor no vinculó token y cuenta.

Finalmente, vuelve al formulario de Portal Web. Introduce Token expired-demo, Reset account maya y New password ExpiredDemo42!. Pulsa Reset password. Este token preparado caducó una hora antes de la preparación y también se acepta: el servidor ignoró el reloj. No necesitas esperar un temporizador.

Son cuentas desechables; las pruebas siguientes usan copias limpias. Conserva el estado observado, sin editar directamente la base de datos.

Vincular y consumir el token

En este paso, repararás la pequeña función del servidor y la probarás sin modificar las cuentas de demostración activas.

Cambia a App Server, que empieza en /home/labex/project/reset. cat muestra un archivo de texto; su argumento es el nombre. Lee el controlador:

cat handler.py

La primera línea importa dos funciones auxiliares. def define una función; los nombres entre paréntesis son parámetros, las entradas de una llamada. eligible(row, account, now) recibe el registro del token, la cuenta enviada y la hora del servidor. row["account"] lee un campo. row is None significa que no se encontró token. El bloque sangrado de if solo se ejecuta si se cumple la condición; return False deniega y return True concede permiso. Cuatro espacios iniciales definen aquí un bloque de Python.

La función actual devuelve True para cualquier token existente, lo que explica los tres fallos. Además, reset llama a unsafe_reset, que cambia la contraseña sin consumir el token.

La reparación exige todas las condiciones. == compara valores, > significa mayor que y and exige ambos lados verdaderos. or aceptaría cualquiera y una propiedad válida ocultaría otra inválida. Una caducidad futura es mayor que now; la igualdad ya significa caducado. used == 0 solo permite un token sin usar.

La función preparada consume_and_reset inicia una transacción SQLite, lee el token actual, llama a tu eligible y confirma juntos el cambio de contraseña y used = 1. Esto es atomicidad: ambos cambios tienen éxito juntos. Bloquea antes de leer para impedir que dos solicitudes concurrentes vean el token sin usar. No implementas bloqueo ni hash de contraseñas.

Abre el archivo con nano, un editor de texto del terminal:

nano handler.py

Sustituye el contenido por este código. Usa flechas y Retroceso/Supr para borrar lo anterior y pega la sustitución completa. Conserva espacios y signos; los paréntesis permiten dividir la condición en varias líneas.

from framework import unsafe_reset, consume_and_reset


def eligible(row, account, now):
    if row is None:
        return False
    return (row["account"] == account
            and row["expires_at"] > now
            and row["used"] == 0)


def reset(db, token, account, password):
    return consume_and_reset(db, token, account, password, eligible)

Guarda con Ctrl+O, pulsa Enter para confirmar handler.py y sal con Ctrl+X. Cada solicitud Web lee el archivo guardado; no hace falta reiniciar. Si aparece HTTP 500, abre otra vez y compara sangría, corchetes y comillas.

python3 ejecuta un programa Python. El comprobador recibe la ruta del controlador y arranca una aplicación HTTP separada con datos SQLite temporales. Prueba el código real sin repararlo ni cambiar las cuentas activas:

python3 /opt/reset-lab/check.py handler.py

Espera once líneas PASS, incluidas valid reset accepted, replay denied y competing use has exactly one winner. FAIL nombra la propiedad incorrecta. Denegar todo falla las pruebas de recuperación y acceso legítimos. Cambiar campos del navegador o la base activa no repara el código probado con datos nuevos. Reabre nano handler.py, corrige, guarda y repite el comprobador cuando sea necesario.

Demostrar que la recuperación sigue funcionando

En este paso, usarás el servicio reparado y relacionarás sus resultados con las pruebas de seguridad aisladas.

En Portal Web, solicita un enlace nuevo para maya. Actualiza Mailbox Web, abre el último y cambia a ResetSafe42!. Espera Reset accepted y HTTP 200. Reabre el enlace del mismo mensaje e intenta ReplayAgain42! para maya. Espera Reset denied y HTTP 403.

Solicita otro enlace nuevo de maya. Ábrelo, cambia Reset account a noah e intenta BlockedCross42!. Debe denegarse. Eso no debe consumir el enlace sin usar del propietario: reabre el mismo mensaje, conserva maya y establece ResetSafe42!. Debe funcionar.

En el formulario, prueba Token expired-demo, Reset account maya y New password BlockedExpired42!: espera denegación. En Portal Web, inicia sesión como maya con ResetSafe42!. Debe mostrar Login accepted: los intentos fallidos no cambiaron la contraseña. También puedes entrar como noah con CrossDemo42!, su contraseña de demostración; el controlador conserva otras cuentas.

En App Server, repite las pruebas de regresión desechables:

python3 /opt/reset-lab/check.py handler.py

Las once deben pasar, incluidas dos solicitudes simultáneas con un único cambio aceptado. Repetirlas no altera registros activos. Se combinan recuperación permitida y abuso denegado; desactivar la página o denegar siempre no es una reparación correcta.

La aplicación usa entrega simulada e identidad simplificada. En producción también hacen falta correo seguro, límites de frecuencia, gestión de sesiones y política de contraseñas, fuera de este ejercicio. La guía de OWASP amplía ese contexto.

Resumen

Seguiste un restablecimiento real, demostraste repetición, cruce de cuentas y caducidad, y reparaste un pequeño controlador Python. Propietario almacenado, caducidad futura, estado sin usar y consumo atómico actúan juntos. Las pruebas HTTP desechables demuestran recuperación válida, abuso denegado, cuentas ajenas intactas y un único ganador concurrente. El desafío del portal de personal aplica esas reglas a otra condición incompleta.