NAXSI (Nginx Anti XSS & SQL Injection) es un firewall de aplicaciones web (WAF) que funciona como módulo de Nginx. En lugar de depender de una base de firmas, puntúa cada petición según caracteres y palabras que no deberían aparecer en una URL o un formulario (<, ", union, ../...) y la bloquea cuando la puntuación supera un umbral. Por eso bloquea mucho por defecto, y el trabajo del administrador es añadir excepciones (whitelists) para el tráfico legítimo de su aplicación.

En este tutorial compilarás NAXSI como módulo dinámico para el Nginx de Ubuntu 24.04, cargarás las reglas base, protegerás un sitio, usarás el modo aprendizaje para detectar falsos positivos, escribirás whitelists y una regla propia, y comprobarás que los ataques se bloquean con un 403.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Nginx instalado desde los repositorios de Ubuntu y un sitio funcionando (en los ejemplos se usa el sitio por defecto en /var/www/html).
  • Unos 500 MB libres en disco para las fuentes y la compilación.

Paso 1: Instalar las dependencias de compilación

Ubuntu no empaqueta NAXSI, así que hay que compilarlo contra el código fuente de la misma versión de Nginx que tienes instalada. Instala Nginx (si aún no lo tienes) y las herramientas de compilación:

sudo apt update
sudo apt install nginx build-essential libpcre2-dev zlib1g-dev wget

Comprueba la versión exacta de Nginx, porque el módulo solo se cargará si se compila contra ella:

nginx -v
nginx version: nginx/1.24.0 (Ubuntu)

El Nginx de Ubuntu está compilado con --with-compat, lo que permite cargar módulos dinámicos compilados aparte siempre que la versión coincida.

Paso 2: Descargar NAXSI y las fuentes de Nginx

Trabaja en un directorio temporal dentro de tu home:

mkdir -p ~/naxsi-build && cd ~/naxsi-build

Descarga la última versión de NAXSI con sus dependencias incluidas (libinjection). Consulta la versión actual en la página de releases; en el momento de escribir esta guía es la 1.7:

NAXSI_VERSION=1.7
wget "https://github.com/wargio/naxsi/releases/download/${NAXSI_VERSION}/naxsi-${NAXSI_VERSION}-src-with-deps.tar.gz"
mkdir -p naxsi
tar -C naxsi -xzf "naxsi-${NAXSI_VERSION}-src-with-deps.tar.gz"

Descarga ahora el código fuente de Nginx de nginx.org con la misma versión que devolvió nginx -v. El siguiente comando la extrae automáticamente:

NGINX_VERSION=$(nginx -v 2>&1 | grep -oP 'nginx/\K[0-9.]+')
echo "$NGINX_VERSION"
wget "https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz"
tar -xzf "nginx-${NGINX_VERSION}.tar.gz"

Verifica que tienes los dos árboles de código:

ls ~/naxsi-build/naxsi ~/naxsi-build/nginx-*/

Deberías ver naxsi_src y naxsi_rules en el primero, y configure y src en el segundo.

Paso 3: Compilar el módulo dinámico

Configura Nginx con --with-compat y añade NAXSI como módulo dinámico. Solo se compilan los módulos, no un Nginx nuevo:

cd ~/naxsi-build/nginx-*/
./configure --with-compat --add-dynamic-module=../naxsi/naxsi_src
make modules

Durante configure puede aparecer No package 'libinjection' found seguido de Using submodule libinjection. Es normal: usa la copia de libinjection incluida en el tarball.

Comprueba que el módulo se ha generado:

ls -l objs/ngx_http_naxsi_module.so
-rwxrwxr-x 1 your_user your_user 1392584 sep 25 10:12 objs/ngx_http_naxsi_module.so

Paso 4: Instalar el módulo y las reglas

Copia el módulo al directorio de módulos de Nginx en Ubuntu:

sudo install -m 0644 objs/ngx_http_naxsi_module.so /usr/lib/nginx/modules/

Ubuntu carga los módulos a través de ficheros en /etc/nginx/modules-enabled/, que nginx.conf incluye al principio. Crea el fichero de carga siguiendo esa convención:

echo 'load_module modules/ngx_http_naxsi_module.so;' | sudo tee /etc/nginx/modules-available/mod-http-naxsi.conf
sudo ln -s /etc/nginx/modules-available/mod-http-naxsi.conf /etc/nginx/modules-enabled/50-mod-http-naxsi.conf

Copia las reglas de NAXSI a /etc/nginx/naxsi/:

sudo mkdir -p /etc/nginx/naxsi
sudo cp -r ~/naxsi-build/naxsi/naxsi_rules/* /etc/nginx/naxsi/
ls /etc/nginx/naxsi
blocking  naxsi_core.rules  whitelists

naxsi_core.rules contiene las reglas genéricas (SQLi, XSS, RFI, traversal, subidas). blocking/ tiene reglas adicionales por categoría y whitelists/ excepciones ya preparadas para aplicaciones conocidas como WordPress o Drupal.

Paso 5: Cargar las reglas base

Las reglas globales se declaran con MainRule y deben estar en el contexto http. En Ubuntu, nginx.conf incluye /etc/nginx/conf.d/*.conf dentro de http, así que crea ahí un fichero:

sudo nano /etc/nginx/conf.d/naxsi.conf
# Reglas globales de NAXSI (contexto http)
include /etc/nginx/naxsi/naxsi_core.rules;

Crea también un fragmento reutilizable con las directivas que se aplican a cada location protegida. Así no repites las mismas líneas en cada sitio:

sudo nano /etc/nginx/naxsi/location.rules
SecRulesEnabled;
LearningMode;
LibInjectionSql;
LibInjectionXss;

DeniedUrl "/RequestDenied";

CheckRule "$SQL >= 8" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$UWA >= 8" BLOCK;
CheckRule "$EVADE >= 8" BLOCK;
CheckRule "$UPLOAD >= 5" BLOCK;
CheckRule "$TRAVERSAL >= 5" BLOCK;
CheckRule "$LIBINJECTION_SQL >= 8" BLOCK;
CheckRule "$LIBINJECTION_XSS >= 8" BLOCK;

Qué hace cada directiva:

  • SecRulesEnabled activa NAXSI en la location. Sin ella el módulo no hace nada.
  • LearningMode convierte las acciones BLOCK en LOG: registra lo que habría bloqueado sin cortar tráfico. Empezarás así y lo quitarás en el paso 8.
  • LibInjectionSql y LibInjectionXss activan la detección de libinjection, que suma puntos a $LIBINJECTION_SQL y $LIBINJECTION_XSS.
  • DeniedUrl es la redirección interna a la que se envía una petición bloqueada.
  • Los CheckRule son obligatorios con las reglas base: definen el umbral de cada puntuación.

Paso 6: Proteger un sitio

Edita el bloque server de tu sitio. En este ejemplo, el sitio por defecto:

sudo nano /etc/nginx/sites-available/default

Deja el bloque server como este (ajusta server_name y root a los tuyos):

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;
    index index.html index.htm;
    server_name your_domain;

    location / {
        include /etc/nginx/naxsi/location.rules;
        try_files $uri $uri/ =404;
    }

    location /RequestDenied {
        internal;
        return 403;
    }
}

La location /RequestDenied es internal para que nadie pueda pedirla directamente y deducir que hay un WAF delante. Si tu sitio es un proxy inverso, pon el include dentro de la location que contiene el proxy_pass.

Valida la configuración y recarga Nginx:

sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Lanza una petición con una comilla doble en un parámetro, algo que la regla 1001 puntúa como SQLi y XSS:

curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost/?q="test'
200

Devuelve 200 porque estás en modo aprendizaje, pero la petición queda registrada en el log de errores de Nginx:

sudo grep NAXSI_FMT /var/log/nginx/error.log | tail -n 1
2026/09/25 10:30:12 [error] 12345#12345: *7 NAXSI_FMT: ip=127.0.0.1&server=localhost&uri=%2F&config=learning&rid=6c1f...&cscore0=$SQL&score0=8&cscore1=$XSS&score1=8&zone0=ARGS&id0=1001&var_name0=q, client: 127.0.0.1, server: your_domain, request: "GET /?q=%22test HTTP/1.1", host: "localhost"

Los campos importantes son config=learning (no se bloqueó), id0 (regla que saltó), zone0 (dónde: ARGS, BODY, URL, HEADERS) y var_name0 (el parámetro).

Paso 7: Detectar falsos positivos y escribir whitelists

Deja el modo aprendizaje activo mientras usas la aplicación de forma normal: navega, envía formularios, inicia sesión, usa el buscador. Si puedes, déjalo así unos días con tráfico real. Después agrupa los eventos por zona, regla y parámetro para ver qué se repite:

sudo grep -oE 'zone0=[^&]*&id0=[0-9]+&var_name0=[^&,]*' /var/log/nginx/error.log | sort | uniq -c | sort -rn | head -n 20
     42 zone0=ARGS&id0=1001&var_name0=q
     17 zone0=BODY&id0=1302&var_name0=comment
      3 zone0=URL&id0=1000&var_name0=

Cada línea frecuente de tráfico legítimo es candidata a una whitelist. Revisa también las URIs completas en el log antes de decidir: una whitelist mal acotada abre un hueco real.

Las whitelists usan la directiva BasicRule wl:<ids> con una zona de coincidencia (mz:) lo más estrecha posible. Crea un fichero para las del sitio:

sudo nano /etc/nginx/naxsi/whitelist-your_domain.rules
# El buscador acepta comillas en el parámetro q, solo en /search
BasicRule wl:1001 "mz:$URL:/search|$ARGS_VAR:q";

# El campo comment del formulario puede contener < y >
BasicRule wl:1302,1303 "mz:$URL:/contact|$BODY_VAR:comment";

Algunos ejemplos de zonas de coincidencia:

ZonaSignificado
$ARGS_VAR:qEl parámetro q de la query string
$BODY_VAR:commentEl campo comment de un POST
$URL:/searchSolo en esa URL exacta
$HEADERS_VAR:CookieLa cabecera Cookie

Combinar $URL con otra zona mediante | aplica ambas condiciones a la vez, que es lo recomendable. Incluye el fichero dentro de la location protegida, justo después del include de location.rules:

    location / {
        include /etc/nginx/naxsi/location.rules;
        include /etc/nginx/naxsi/whitelist-your_domain.rules;
        try_files $uri $uri/ =404;
    }

Si usas WordPress, Drupal u otra aplicación conocida, empieza por la whitelist correspondiente de /etc/nginx/naxsi/whitelists/ en lugar de escribirla de cero.

Valida y recarga:

sudo nginx -t && sudo systemctl reload nginx

Paso 8: Añadir una regla propia

Las reglas propias se escriben con MainRule (globales) o BasicRule (por location). Usa identificadores altos para no chocar con las reglas incluidas. Esta regla bloquea peticiones cuyo User-Agent contiene sqlmap:

sudo nano /etc/nginx/naxsi/custom.rules
MainRule id:90001 "s:$UWA:8" "str:sqlmap" "mz:$HEADERS_VAR:User-Agent" "msg:sqlmap en el user-agent";
  • id es el identificador de la regla (usa valores de 5 o más cifras para las tuyas).
  • s:$UWA:8 suma 8 puntos a la puntuación $UWA (Unwanted Access), que ya tiene su CheckRule.
  • str: busca una cadena literal; rx: aceptaría una expresión regular.
  • mz: indica dónde buscar.

Inclúyela en el contexto http, junto a las reglas base:

sudo nano /etc/nginx/conf.d/naxsi.conf
# Reglas globales de NAXSI (contexto http)
include /etc/nginx/naxsi/naxsi_core.rules;
include /etc/nginx/naxsi/custom.rules;

Paso 9: Pasar a modo bloqueo

Cuando el log ya no muestre eventos de tráfico legítimo, desactiva el modo aprendizaje comentando la línea en location.rules:

sudo nano /etc/nginx/naxsi/location.rules
SecRulesEnabled;
# LearningMode;
LibInjectionSql;
LibInjectionXss;

Valida y recarga:

sudo nginx -t && sudo systemctl reload nginx

Comprueba que un intento de XSS, uno de SQLi y la regla propia se bloquean:

curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost/?q=<script>alert(1)</script>'
curl -s -o /dev/null -w '%{http_code}\n' "http://localhost/?id=1'+union+select+password+from+users--"
curl -s -o /dev/null -w '%{http_code}\n' -A 'sqlmap/1.8' http://localhost/
403
403
403

Y que el tráfico normal sigue pasando:

curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost/?q=cubepath'
200

En el log, los eventos bloqueados aparecen ahora con config=block:

sudo grep -c 'config=block' /var/log/nginx/error.log

Sigue revisando el log durante los primeros días en modo bloqueo. Cualquier 403 legítimo que reporten tus usuarios se resuelve igual que en el paso 7: identificar regla, zona y parámetro, y añadir una whitelist acotada.

Mantener el módulo tras actualizar Nginx

El módulo está compilado para una versión concreta de Nginx. Las actualizaciones de seguridad de Ubuntu mantienen la versión 1.24.0, pero si en algún momento cambia la versión upstream (por ejemplo, al actualizar a otra versión de Ubuntu o usar el repositorio de nginx.org), Nginx se negará a arrancar hasta que recompiles. Después de actualizar, comprueba siempre:

nginx -v
sudo nginx -t

Si la versión ha cambiado, repite los pasos 2 a 4 con la nueva versión antes de reiniciar Nginx.

Solución de problemas

unknown directive "SecRulesEnabled": el módulo no está cargado. Comprueba que existe /etc/nginx/modules-enabled/50-mod-http-naxsi.conf y que apunta a un fichero con la línea load_module.

module "/usr/lib/nginx/modules/ngx_http_naxsi_module.so" version 1024000 instead of 1026000: el módulo se compiló para otra versión de Nginx. Recompílalo con las fuentes de la versión que muestra nginx -v.

module ... is not binary compatible: falta --with-compat en el ./configure del paso 3. Vuelve a ejecutar configure y make modules.

Peticiones POST con JSON bloqueadas por la regla 15 u 11: NAXSI valida el cuerpo según el Content-Type. Asegúrate de que el cliente envía Content-Type: application/json con un JSON válido; si una ruta concreta usa un formato que NAXSI no entiende, crea una whitelist acotada a esa $URL.

Líneas de log cortadas: Nginx limita la longitud de cada línea del log de errores, así que una petición con muchas coincidencias puede aparecer truncada. Filtra por el campo rid para agrupar los eventos de la misma petición.

Conclusión

Tienes NAXSI compilado como módulo dinámico para el Nginx de Ubuntu 24.04, con las reglas base y libinjection activos, whitelists acotadas para tu aplicación y una regla propia, todo en modo bloqueo. Como siguientes pasos puedes:

  • Añadir HTTPS al sitio con Let's Encrypt y Certbot, manteniendo las mismas directivas en el bloque server de 443.
  • Enviar /var/log/nginx/error.log a tu sistema de logs centralizado y alertar sobre picos de config=block.
  • Complementar el WAF con limitación de peticiones (limit_req) de Nginx y Fail2ban para bloquear por IP a los clientes que acumulan 403.