Un nonce en WordPress es un token de seguridad que se adjunta a formularios, enlaces y peticiones AJAX para que WordPress pueda verificar que la petición fue lanzada de forma intencionada por una persona usuaria concreta con la sesión iniciada. El nombre viene de la criptografía y significa number used once, pero aquí resulta algo engañoso: un nonce de WordPress no es un número, no se usa una sola vez y es técnicamente un hash ligado a la persona usuaria, a su sesión y a una acción concreta. Sigue siendo válido durante un máximo de 24 horas. La amenaza principal contra la que defienden los nonces es el cross-site request forgery (CSRF), un ataque en el que una página maliciosa envía en silencio una petición aprovechando tu sesión iniciada, por ejemplo para borrar una entrada o cambiar un ajuste. Los nonces funcionan, y precisamente por eso el error de seguridad más habitual está en el otro lado: un nonce demuestra intención, no permiso. El código de plugin que comprueba un nonce pero se olvida de la comprobación de capacidades con current_user_can() es una de las fuentes más frecuentes de vulnerabilidades publicadas de WordPress.
¿Qué es un nonce? El nombre despista un poco
En criptografía, un nonce es un valor que se usa exactamente una vez y nunca más, y eso es lo que lo hace útil contra los ataques de repetición. WordPress tomó prestado el nombre, pero no la definición estricta. Un nonce de WordPress es un hash corto generado a partir de cuatro ingredientes: la acción a la que pertenece, el ID y el token de sesión de la persona usuaria actual, la ventana temporal vigente y las claves secretas de tu wp-config.php. Esa construcción tiene tres consecuencias prácticas:
- Un nonce es personal. Un token generado para una persona usuaria falla la validación para cualquier otra, así que no se puede compartir ni robar desde una página pública.
- Un nonce es específico de una acción. Un token creado para la acción delete-post_42 no sirve para delete-post_43 ni para ninguna otra operación. Por eso poner un nombre de acción genérico fijo debilita todo el mecanismo.
- Un nonce es reutilizable durante su vida útil. El mismo token valida tantas veces como quieras durante un máximo de 24 horas. WordPress lo documenta de forma explícita, así que un nonce por sí solo no protege frente a la repetición de una petición que el atacante ya haya capturado.
La documentación oficial para desarrolladores de WordPress es refrescantemente honesta con todo esto y describe los nonces como una capa de defensa, no como un sistema de seguridad completo.
¿Qué ataques evitan los nonces de WordPress? El CSRF explicado
El ataque para el que existen los nonces es el cross-site request forgery. El escenario: tienes la sesión iniciada en tu administración de WordPress en una pestaña del navegador. En otra pestaña abres una página completamente ajena que controla un atacante. Esa página contiene un formulario oculto o una etiqueta de imagen que apunta a tu sitio, por ejemplo una petición para desactivar un plugin o crear una persona usuaria. Como tu navegador envía automáticamente tus cookies de WordPress en cada petición a tu dominio, la petición llega perfectamente autenticada y WordPress la ejecutaría encantado. No has hecho clic en nada, no has visto nada y tu sitio acaba de ganar un administrador nuevo.
Los nonces rompen este ataque en el último paso. La página maliciosa puede falsificar la petición, pero no puede conocer el nonce actual para tu persona usuaria y esa acción, porque el token solo aparece dentro de páginas que WordPress renderiza para ti. La petición falsificada llega sin un nonce válido, la verificación falla y WordPress se detiene con la famosa pantalla en blanco que pregunta: ¿Seguro que quieres hacer esto?
¿Cuánto dura un nonce de WordPress? El sistema de ticks
WordPress divide el tiempo en ticks de doce horas. Cuando se verifica un nonce, WordPress acepta tokens del tick actual y del anterior, y de ahí sale la vida útil máxima de 24 horas. Las funciones de verificación incluso te dicen en qué mitad estás: devuelven 1 si el nonce se generó en las últimas doce horas y 2 si tiene entre doce y 24 horas, y false si no es válido o ha caducado.
La duración se puede cambiar con el filtro nonce_life, que algunas instalaciones centradas en seguridad usan para acortar la ventana:
// Reducir la vida del nonce a 4 horas
add_filter( 'nonce_life', function () {
return 4 * HOUR_IN_SECONDS;
} );Las vidas más cortas encogen la ventana de repetición, pero también hacen que las páginas cacheadas con nonces incrustados caduquen antes, lo que supone un problema real en sitios con caché de página agresiva. Si una página cacheada sirve un nonce con más de 24 horas, cada visitante recibe un token que nunca podrá validar. Esta es la razón clásica por la que las funciones AJAX de sitios WordPress muy cacheados fallan de forma misteriosa para algunas visitas.
Cómo crear un nonce en WordPress
WordPress trae tres funciones auxiliares para generar nonces, una para cada sitio donde suele vivir un token:
// 1. En un formulario: añade un campo oculto más un campo de referer
wp_nonce_field( 'save_settings_action', 'my_plugin_nonce' );
// 2. En una URL: añade ?_wpnonce=... a un enlace
$url = wp_nonce_url( admin_url( 'admin.php?page=my-plugin&delete=42' ), 'delete-item_42' );
// 3. Token en crudo, p. ej. para pasarlo a JavaScript
$nonce = wp_create_nonce( 'my_ajax_action' );El primer argumento siempre es la acción, y aquí es donde el cuidado da beneficios. Una cadena de acción como delete-item_42, que incluye el ID del objeto, produce un token que solo funciona para exactamente ese elemento. Una acción genérica y perezosa como my_nonce compartida por todo un plugin significa que un único token filtrado o de contexto predecible abre todas las operaciones que ofrece el plugin.
Cómo verificar un nonce: wp_verify_nonce, check_admin_referer y check_ajax_referer
La verificación tiene una función de bajo nivel y dos envoltorios cómodos con los que te encontrarás mucho más a menudo en el código real:
// Bajo nivel: devuelve 1, 2 o false, nunca corta la ejecución
if ( ! wp_verify_nonce( $_POST['my_plugin_nonce'], 'save_settings_action' ) ) {
return; // parar, petición no válida
}
// Pantallas de administración: verifica y muestra una página de error si falla
check_admin_referer( 'save_settings_action', 'my_plugin_nonce' );
// Manejadores AJAX: misma idea para las peticiones a admin-ajax.php
check_ajax_referer( 'my_ajax_action', 'security' );Los envoltorios detienen la ejecución cuando falla la comprobación, que suele ser lo que quieres al principio de un manejador. La función de bajo nivel wp_verify_nonce() te devuelve en cambio el valor, de modo que puedes responder con un error JSON adecuado en un contexto de API. Uses la variante que uses, la verificación debe ir antes de ejecutar la acción, no después, lo que suena obvio y sigue siendo un fallo recurrente en los registros de cambios de los plugins.
Los nonces en la REST API y en las peticiones AJAX
La REST API de WordPress usa el mismo mecanismo con un nombre de acción fijo. Para cualquier petición que dependa de la autenticación por cookies, WordPress espera un nonce creado para la acción wp_rest, entregado en la cabecera de petición X-WP-Nonce. Los scripts registrados de la forma estándar lo reciben automáticamente: wp_localize_script() o el paquete más moderno wp.apiFetch pasan el token, y cada petición REST con sesión iniciada lo lleva sin que quien desarrolla tenga que pensarlo.
// Pasar un nonce REST a tu JavaScript
wp_localize_script( 'my-app', 'myAppData', array(
'restUrl' => esc_url_raw( rest_url( 'my-plugin/v1/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
) );
// En JavaScript: enviarlo como cabecera
fetch( myAppData.restUrl + 'items', {
method: 'POST',
headers: { 'X-WP-Nonce': myAppData.nonce, 'Content-Type': 'application/json' },
body: JSON.stringify( { title: 'New item' } ),
} );Sin la cabecera, la REST API trata la petición como no autenticada, aunque el navegador haya enviado cookies de sesión válidas. Es algo deliberado: se trata exactamente de la protección CSRF descrita más arriba, aplicada a la API.
Por qué un nonce no es una comprobación de permisos
Esto es lo más importante que hay que entender sobre los nonces, y es la parte que falla con regularidad en el ecosistema de plugins. Una comprobación de nonce correcta demuestra dos cosas: que la petición se originó en una página generada por WordPress y que pertenece a la persona usuaria a la que se emitió. No demuestra que esa persona tenga permiso para hacer lo que la petición pide. Una persona suscriptora puede tener un nonce perfectamente válido, porque WordPress emite nonces alegremente a cualquier persona con la sesión iniciada, y algunos tokens funcionan incluso para visitas que no han iniciado sesión.
Por eso el código correcto de un manejador siempre se hace dos preguntas separadas:
// Intención: ¿es una petición genuina de esta persona usuaria?
check_admin_referer( 'save_settings_action', 'my_plugin_nonce' );
// Permiso: ¿tiene permitido hacer esto?
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Permisos insuficientes.' );
}Las bases de datos de vulnerabilidades están llenas de lo que pasa cuando se salta la segunda pregunta. El control de acceso roto y el CSRF figuran de forma constante entre las categorías más habituales en los CVE de plugins de WordPress, y buena parte de esas entradas se reducen a un manejador que comprobó un nonce, o nada en absoluto, en lugar de una capacidad. Si lees un aviso de plugin que dice que cualquier persona autenticada podía cambiar los ajustes, o que faltaba autorización, este patrón suele estar detrás.
Errores habituales con nonces en temas y plugins
- Comprobar el nonce en lugar de la capacidad. El clásico, descrito más arriba. Hacen falta las dos comprobaciones, responden a preguntas distintas.
- Un único nonce genérico para todo. Una sola cadena de acción para todo un plugin convierte un token estrecho en una llave maestra.
- Nonces en páginas cacheadas. Las cachés de página completa sirven tokens más allá de su vida de 24 horas y rompen en silencio los formularios y el AJAX para algunas visitas. La caché por fragmentos o pedir el nonce aparte lo resuelve.
- Verificar después de actuar. La comprobación debe ser lo primero que haga el manejador.
- Fiarse de un nonce como prueba de identidad para visitas sin sesión. Los nonces para visitas sin sesión son más débiles por construcción, ya que el componente de persona usuaria del hash está vacío.
- Filtrar nonces en las URL. Los tokens añadidos a los enlaces acaban en los registros del servidor, en el historial del navegador y en la cabecera Referer. Para las acciones destructivas, las peticiones POST con wp_nonce_field() son mejor hogar.
Cómo te ayuda InspectWP a detectar las consecuencias
Desde fuera no puedes ver si un plugin verifica correctamente sus nonces, pero sí puedes ver las consecuencias una vez se hacen públicas. InspectWP detecta los plugins y temas de un sitio WordPress analizado junto con sus versiones y los coteja con datos de vulnerabilidades conocidas, y los fallos de CSRF y de autorización ausente exactamente del tipo descrito en este artículo suponen buena parte de esas entradas. Si un informe marca un plugin instalado con una vulnerabilidad publicada, la solución casi siempre es la misma y refrescantemente simple: actualizar. Con informes automáticos programados, ese ciclo de detectar, entender y actualizar funciona de forma continua en lugar de cuando alguien se acuerda.