Seguridad extra para la web: Una guía al nonce


Nonce (number used once / número usado una sóla vez), es un camino simple para añdir una seguridad extra a nuestra aplicación web, evitando particularmente ataques de replay o de reinyección, ataques URL de tipo semánticos y permitiendo verificar además el origen de la petición.

Al final del artículo hay ejemplos para descargar en PHP y Go.

  • ¿Qué es un nonce?

El nonce es un valor que sirve como identificador único y al que se le puede seguir la pista para saber si ha sido usado. Además verificaremos que proviene de nosotros en primer lugar, que ha sido generado recientemente y que está destinado para la tarea para la que el usuario lo está utilizando.

La vida de un nonce es la siguiente:

  1. Un usuario hace una petición sobre una página que puede desencadenar acciones, por ejemplo: crear o borrar contenido.
  2. La página solicitada genera un valor nonce y la hace parte de la acción, por ejemplo: como parámetro GET o POST.
  3. El usuario solicita la acción, y el manejador (por ejemplo, un servidor en el caso de una página web) usa el nonce para validar la petición.
  4. Si la validación del nonce es satisfactoria, la acción es ejecutada y el nonce se marca como que ha sido usado.

Ahora veremos estos pasos con código de ejemplo. Usaremos PHP por ser muy común, aunque se puede aplicar a cualquier lenguaje. La situación que recrearemos será una acción de borrado de contenido por parte del usuario, quien ha solicitado una página que permite el borrado de elementos de una lista.

  • Creando un nonce

Algunas descripciones nos dicen que es un número aleatorio o pseudo-aleatorio, ya que para el usuario parece ser totalmente aleatorio, pero que realmente podría volver a ser recreado por nosotros. Por tanto, si puede ser recreado, es posible determinar lo que hemos creado, siendo ésto una capa extra de seguridad. Como mínimo, un nonce debe estar construido por los siguientes valores:

  1. Nombre de la acción.
  2. Un valor único para el elemento que inicia la acción (puede no ser posible en ciertas acciones).
  3. Un valor único para el usuario actual.
  4. El timestamp actual.
  5. La sal, cuyo valor es único para tu aplicación.

Estos valores permiten crear (y recrear) un valor nonce único para el usuario y tarea en un sólo sentido, no siendo posible falsificarlo para una fuente externa. Para crear un nonce simplemente combinamos dichos valores y aplicamos una función hash irreversible:

Nótese que estamos añadiendo el timestamp en la URL debido a dos razones clave, la primera porque posiblemente no podamos saber su valor cuando la acción se ha producido (y necesitamos recrearlo), y segundo porque nos ayudará en saber seguro si la petición ha expirado ya o no.

Si no tratamos con usuarios logueados (conectados), un session_id es un gran reemplazo para el userid. La sal puede ser cualquier cosa, lo suficientemente largo para ser único para tu aplicación y que no sea predecible.

If you’re not dealing with logged in users, a session_id is a great replacement for the userid. The salt can be anything, as long as it is unique to your application and not predictable.

#1 La función time de PHP devuelve la fecha y hora actual representada como el número de segundos desde el Unix Epoch (1 de Enero de 1970).

  • Manejando la petición

Si el usuario presenta una petición de un paso previo (por ejemplo a través de un vínculo con la etiqueta anchor <a>), la aplicación intentará recrear el nonce, si la recreación es diferente a la que ha sido presentada como parte de la URL, asumimos que dicha petición no es legítima.

Yendo más lejos, querremos comprobar la fecha de la petición y asegurarnos si no ha expirado. La fecha de expiración es arbitraria y puede ser diferente para cada aplicación o incluso para cada acción. Es importante no hacerla demasiado corta para que no suponga una barrera entre los usuarios y las acciones. Nuestro ejemplo tendrá por defecto un margen de 12 horas.

Llegados a este punto, sabemos si el usuario ha presentado un nonce válido y si no ha expirado. Pero no sabemos si el nonce ha sido ya usado. Recordar que un nonce es un valor que sólo puede ser utilizado una vez. Si vemos un nonce por segunda vez, puede indicar un ataque de reinyección, o simplemente que un usuario legítimo ha hecho click dos veces antes de que la página haya sido redirigida. Sea por la razón que sea, no queremos manejar la respuesta más de una vez.

Una buena solución para un seguimiento de los nonces usados es una base de datos. Una simple consulta nos dirá si ya ha sido usado.

En este punto podemos ejecutar la petición confiando en que el usuario es legítimo, y podemos darle permiso para ejecutar la acción (proporcionándole un nonce en primer lugar).

  • Marcando el nonce como usado

Marcar el nonce como que ha sido usado significa almacenarlo en algún lugar. La meta es asegurarse de que ningún nonce ha sido usado más de una vez. Esto sólo puede hacerse mediante una inserción en base de datos como la siguiente:

¿Durante cúanto tiempo necesitamos seguir este valor? Para siempre. Obviamente esto es un gran compromiso, en un sitio activo podríamos requerir de un gran espacio en la base de datos. Pero podemos mitigar la necesidad de mantenerlos realmente para siempre.

Un nonce está compuesto de dos valores, el nonce hash y un correspondiente timestamp. Debido a que nuestro nonce expira, podemos borrar con seguridad de nuestra base de datos aquellos cuyo timestamp es menor que la fecha actual menos la fecha de expiración. En términos simples, si es demasiado viejo que ya ha expirado de todas formas, no es necesario almacenarlo. Así que podemos periódicamente limpiar la base de datos de viejos valores.

  • Conclusión

Mientras que esto simple mecanismo añade un extra de seguridad, no hay reemplazo apropiado para la validación del usuario y manejo del acceso de usuarios a áreas restringidas sin permisos. La combinación de éstos junto con una implementación de un nonce incrementa gratamente la seguridad de la aplicación, para ambos, maliciosos ataques o un error accidental humano.

Puedes descargar los ejemplos de aquí para PHP y aquí para Go. Puedes modificarlos libremente, sin restricciones.

Ver artículo original de Tyler Egeto.