# Implementar un Tarpit en Nginx: Atrapa a los Escáneres Maliciosos

> Una entrada de jmrp.io, publicada como documento propio. Índice: https://jmrp.io/llms-full.txt

Canonical: https://jmrp.io/es/blog/005-implementing-tarpit-nginx/
Language: es
Alternate: https://jmrp.io/blog/005-implementing-tarpit-nginx/index.md
License: https://creativecommons.org/licenses/by/4.0/
Type: TechArticle
Published: 2026-01-16
Updated: 2026-08-22
Instructions re-tested: 2026-08-22 · nginx 1.31.3 (--with-http_perl_module)
Author: José Manuel Requena Plens
Summary: Implementa un tarpit en Nginx para ralentizar bots maliciosos, escáneres de vulnerabilidades y ataques de fuerza bruta. Integración con CrowdSec incluida.
Tags: Nginx, Security, Networking
Topics: Tarpit (Q1656447), Nginx (Q306144), Honeypot (Q911932), Vulnerability scanner (Q283770), CrowdSec (Q119526222)
Build-Date: 2026-09-06

Preguntas que responde:

**¿Qué es un tarpit en seguridad de red?**

Un tarpit (o "sticky honeypot") es un mecanismo de seguridad que ralentiza a los atacantes respondiendo deliberadamente a sus request a una velocidad extremadamente lenta. Acepta la conexión pero gotea los datos tan lentamente que las herramientas del atacante se quedan atascadas, desperdiciando su tiempo y recursos.

**¿Por qué usar un tarpit en lugar de bloquear con un 403 o 404?**

A diferencia del bloqueo, que permite al atacante reintentar al instante, un tarpit malgasta sus conexiones limitadas, recopila inteligencia sobre sus IPs y rutas objetivo, e incluso puede provocar fallos en herramientas de escaneo mal programadas. El compromiso es que mantiene conexiones del servidor abiertas, por lo que conviene ser selectivo con qué rutas lo activan.

**¿Cómo implemento un tarpit HTTP en Nginx?**

Usa tres piezas: una carga útil aleatoria grande (generada con el módulo Perl), la directiva limit_rate para limitar la entrega a unos 10 bytes/segundo y un formato de log dedicado. Las rutas sensibles devuelven 418, y un manejador error_page 418 = @tarpit redirige esas request al snippet de respuesta lenta.

**¿Por qué forzar el status code 418 en el log si el tarpit devuelve 200?**

El cliente debe recibir un 200 OK para seguir conectado y atascado, pero codificar 418 directamente en el log_format ofrece a parsers como CrowdSec o Fail2Ban una señal clara y de alta fidelidad para identificar y banear las request tarpiteadas sin parseo complejo.

**¿Cómo evito atraparme a mí mismo o a IPs de confianza?**

Define un map de Nginx con IPs de confianza (localhost, tu VPN, tu IP estática) y establece su tarpit rate a 0. El snippet del tarpit devuelve un 403 estándar para las IPs de confianza y las excluye del log, de modo que el tráfico de administración nunca queda atrapado accidentalmente.

**¿Cómo puedo banear automáticamente las IPs atrapadas por el tarpit?**

Integra CrowdSec con un parser y un escenario de tipo leaky (por ejemplo, banear tras 3 impactos en el tarpit en 5 minutos) que lea el log del tarpit. Alternativas más sencillas incluyen Fail2Ban, y puedes reportar las IPs capturadas a AbuseIPDB para contribuir a la defensa colectiva.


Pasos (Implementar un tarpit HTTP en Nginx):
1. Preparar el bloque http
2. Crear el snippet del tarpit
3. Definir las rutas sensibles
4. Configurar el server block
5. Probar la configuración
6. Integrar CrowdSec para banear automáticamente

---

Si alguna vez has revisado los logs de tu servidor, seguramente habrás visto innumerables intentos de acceso a archivos como `/.env`, `/wp-login.php`, `/.git/config` o `/admin`. Se trata de escáneres automatizados que sondean tu servidor en busca de vulnerabilidades. Aunque bloquearlos con una response 403 o 404 funciona, hay un enfoque mucho más *satisfactorio*: **atraparlos en un tarpit**.

## TL;DR — Atrapa escáneres en un tarpit lento de Nginx

- **Un tarpit gotea los datos lentamente** (unos 10 bytes/segundo) para que los escáneres maliciosos se queden atascados en lugar de reintentar al instante como hacen tras un 403/404.
- **Tres piezas en Nginx**: una carga útil aleatoria grande con el módulo Perl, `limit_rate` para limitar la entrega y un `log_format` dedicado que fuerza el status **418** para un parseo limpio.
- **Las rutas sensibles devuelven 418** (`/.env`, `/.git`, backups, exploits de CMS, dotfiles) y `error_page 418 = @tarpit` las redirige al snippet de respuesta lenta.
- **Un map de IPs de confianza** fija la rate a 0 y devuelve un 403 normal, de modo que el tráfico de administración nunca queda atrapado accidentalmente.
- **CrowdSec banea automáticamente** a los reincidentes mediante un parser y un escenario leaky (p. ej. 3 impactos en 5 minutos); Fail2Ban y el reporte a AbuseIPDB son alternativas.

---

## Lo que descubrí al medir de verdad mi propio tarpit

El tarpit de arriba no es un ejercicio de laboratorio: lleva tiempo corriendo en este sitio en producción, registrando `418` en cada captura mientras sirve `200` para mantener enganchado al cliente, tal y como se describe más arriba. Hace poco descubrí que llevaba un tiempo sin cumplir la segunda mitad de ese trabajo, y nada en los logs lo delataba: cada captura seguía mostrando el reparto esperado de `200`/`418`, así que sobre el papel parecía funcionar perfectamente.

### El fallo: la compresión anulaba el estrangulamiento

Mi tarpit de producción no sirve la cadena aleatoria de Perl de la guía — sirve una página animada de señuelo, `nedry.html` (255.864 bytes), seguida de unos 10 MB de relleno con bytes a cero, para un payload combinado de 10.255.882 bytes. `limit_rate` solo estrangula los bytes que realmente viajan por la red, y Nginx lo aplica *después* de que se ejecute la compresión. Brotli reducía esa respuesta de ~10 MB a 189.208 bytes (los 189.368 que se ven en el log en crudo incluyen las cabeceras HTTP), y mi `limit_rate_after` estaba fijado en 300 KB — cómodamente por encima del tamaño comprimido, pero muy lejos del payload real. El estrangulamiento sencillamente nunca se activaba: cada escáner descargaba los 10,25 MB completos a máxima velocidad, en una fracción de segundo, y seguía su camino. El umbral de 300 KB en sí estaba bien elegido — `nedry.html` por sí solo pesa ~250 KB, calculado para que la animación visible siga cargando rápido para quien no esté siendo atrapado — el relleno es lo que lo rompía en silencio.

### El arreglo, y lo que cambió

El arreglo fue una línea, `gzip off; brotli off;`, en el snippet del tarpit, validada con `nginx -t` y recargada.

**La misma petición atrapada, antes y después del arreglo**

| Métrica | Antes | Después |
| --- | --- | --- |
| Tiempo hasta completarse | 0,23 s | 20 s (timeout del cliente) |
| Bytes recibidos | 10.255.882 | 255.872 (la animación, nada más) |

La imagen a nivel de log coincide. Comparando las 14 horas antes del arreglo con una ventana de 9 horas después, la **mediana de bytes servidos por captura pasó de 189.368 a 306.948** — por primera vez, por encima del umbral de `limit_rate_after`, que es exactamente el aspecto que tiene "el estrangulamiento por fin se activa". También esperaba que las capturas por hora cayeran con fuerza a medida que los escáneres se quedaran atascados en vez de disparar decenas de peticiones rápidas, y así fue (266/hora antes, 14/hora después) — pero no presento esa cifra como algo probado por sí sola: la ventana "después" se adentra en la noche, cuando el tráfico de escaneo es naturalmente menor, así que parte de la caída podría deberse simplemente al ciclo diario de escaneo y no al arreglo. Confirmarlo de forma rigurosa exige comparar las mismas horas en días distintos, no dos tramos consecutivos. Las cifras de bytes no tienen ese problema — son consecuencia directa y mecánica de desactivar la compresión, algo que la variación de tráfico no podría producir.

### Cómo verificar que tu propio tarpit funciona de verdad

La lección transferible aquí: un tarpit que sirve contenido compresible puede estar completamente inoperativo mientras sus logs parecen perfectamente sanos, porque el status code y el recuento de capturas no cambian. Si tienes uno corriendo, comprueba estas tres cosas en vez de fiarte solo del log.

```bash
# 1. ¿Cuánto tarda realmente un cliente atrapado y cuánto llega a recibir?
curl -s -o /dev/null -m 20 \
  -w 'status=%{http_code} t=%{time_total}s bytes=%{size_download} v=%{speed_download} B/s\n' \
  https://tu-sitio/.env
```

```bash
# 2. ¿Se puede comprimir el payload? Esta es la trampa silenciosa.
gzip -c /ruta/al/payload.html | wc -c
brotli -c -q 5 /ruta/al/payload.html | wc -c
# Si cualquiera de los dos resultados es menor que limit_rate_after,
# el tarpit no ralentiza a nadie.
```

```bash
# 3. ¿Qué se está sirviendo realmente, según el access log?
awk '{for(i=1;i<=NF;i++) if($i=="418"){print $(i+1);break}}' tarpit_access.log \
  | sort -n | uniq -c | sort -rn | head
# Un único valor repetido en casi todas las capturas apunta a un techo
# artificial, no a un abandono real del cliente -- el abandono genuino
# produce una dispersión de tamaños, no un número dominante.
```

### Dos formas de rellenar el payload, y su compromiso

Hay dos formas honestas de construir el relleno de varios megabytes, y ninguna es gratis — presento las dos en vez de fingir que la que uso hoy es sencillamente "la correcta":

1. **Relleno de bytes a cero con la compresión desactivada** (lo que corre hoy). Simple de generar y fácil de razonar, pero frágil: depende de que nada en la cadena vuelva a comprimir la respuesta. Cualquier proxy intermedio que aplique su propio gzip o Brotli colapsa de nuevo los 10 MB a ~189 KB y anula el tarpit en silencio — sin error, sin alerta, solo un estrangulamiento que deja de estrangular sin avisar.
2. **Bytes aleatorios en lugar de ceros.** Incompresibles por construcción, así que el tarpit funciona haya o no compresión activa en cualquier punto de la cadena, y sobrevive a proxies intermedios que el relleno de ceros no soporta. El coste: el fichero nunca comprime, ni siquiera en los casos en que interesaría (por ejemplo, al servirlo por un enlace lento), y generar y regenerar unos megabytes de aleatoriedad genuina cuesta más trabajo que un flujo de ceros.

Hoy uso la opción 1 porque era lo que podía desplegar de inmediato tras encontrar el fallo — pero es la más frágil de las dos, y ahora sé exactamente qué aspecto tiene "frágil" cuando falla en silencio.

---

## ¿Qué es un tarpit?

Un **[tarpit](https://en.wikipedia.org/wiki/Tarpit_%28networking%29)** (también conocido como "tar pit" o "[sticky honeypot](/es/blog/006-implementing-mikrotik-honeypot/)") es un mecanismo de seguridad de red diseñado para **ralentizar a los atacantes** respondiendo deliberadamente a sus request a una velocidad extremadamente lenta. El nombre proviene del fenómeno natural de los pozos de alquitrán — formaciones geológicas donde los animales quedan atrapados en alquitrán viscoso y no pueden escapar.

En ciberseguridad, un tarpit hace lo mismo digitalmente: acepta conexiones entrantes de actores maliciosos pero **gotea datos tan lentamente** que las herramientas del atacante se quedan atascadas, desperdiciando su tiempo y recursos mientras esperan una response que nunca llega a completarse. Para más contexto, consulta [la excelente visión general de Hedgehog Security](https://www.hedgehogsecurity.co.uk/blog/what-are-tarpits).

**Información — La Filosofía**

A diferencia del bloqueo tradicional (que rechaza las request inmediatamente), un tarpit *finge* cooperar. Esto desperdicia los recursos del atacante, le impide pasar rápidamente al siguiente objetivo e incluso puede provocar fallos en herramientas de escaneo mal programadas.

---

## Historia y orígenes

El concepto de tarpit en ciberseguridad surgió a finales de los años 90 y principios de los 2000, durante un período en el que los gusanos de red y las herramientas de escaneo automatizado comenzaron a proliferar. El tarpit más famoso fue **[LaBrea](https://labrea.sourceforge.io/)**, creado por [Tom Liston](https://www.giac.org/paper/gsec/1895/labrea-approach-securing-networks/103112) alrededor de 2001.

LaBrea funcionaba a nivel de red, respondiendo a paquetes TCP SYN dirigidos a direcciones IP sin usar y creando "máquinas virtuales pegajosas" que atrapaban a los escáneres de gusanos. Cuando los gusanos [Code Red](https://en.wikipedia.org/wiki/Code_Red_%28computer_worm%29) y [Nimda](https://en.wikipedia.org/wiki/Nimda) se estaban propagando rápidamente, LaBrea demostró ser notablemente eficaz para frenar su expansión.

### Evolución de los tarpits

El concepto de tarpit ha evolucionado más allá de las implementaciones a nivel de red:

**[OpenBSD spamd](https://man.openbsd.org/spamd)** (2003) - El tarpit de correo electrónico que introdujo el greylisting. Cuando un remitente en lista negra se conecta, spamd ralentiza deliberadamente la conversación SMTP, enviando un byte cada vez. Los servidores de correo legítimos reintentan; los spammers desisten. Este enfoque inspiró muchas implementaciones modernas de tarpit.

**[Endlessh](https://github.com/skeeto/endlessh)** (2019) - Creado por [Chris Wellons](https://nullprogram.com/blog/2019/03/22/), este tarpit SSH explota el RFC 4253: antes del intercambio de versión SSH, los servidores pueden enviar "otras líneas de datos". Endlessh envía datos aleatorios continuamente a intervalos de ~10 segundos, atrapando a los escáneres SSH indefinidamente. ¡Algunas conexiones han durado **semanas**!

**HTTP Tarpits (esta guía)** - Tarpits a nivel de aplicación que utilizan funcionalidades del servidor web como `limit_rate` para gotear datos lentamente a request HTTP maliciosas. Perfectos para atrapar escáneres de vulnerabilidades que buscan archivos sensibles.

**Servidor Normal vs Response del Tarpit**

```mermaid
sequenceDiagram
    participant Atacante
    participant Tarpit
    participant Servidor Normal

    Note over Atacante,Servidor Normal: Response del Servidor Normal
    Atacante->>Servidor Normal: GET /.env
    Servidor Normal->>Atacante: 404 Not Found (instantáneo)
    
    Note over Atacante,Tarpit: Response del Tarpit
    Atacante->>Tarpit: GET /.env
    Tarpit-->>Atacante: 200 OK (inicio)
    Note right of Tarpit: Enviando 10 bytes/segundo...
    Tarpit-->>Atacante: Datos aleatorios... (muy lento)
    Note right of Atacante: Conexión atascada durante horas
```

---

## ¿Por qué usar un tarpit en lugar de bloquear?

**Tarpit vs Otras Estrategias de Defensa**

| Enfoque | Ventajas | Desventajas |
| --- | --- | --- |
| **Bloqueo (403/404)** | Inmediato, pocos recursos | El atacante puede reintentar al instante |
| **Rate Limiting** | Controla el volumen | Puede afectar a usuarios legítimos |
| **Tarpit** | Desperdicia recursos del atacante, proporciona inteligencia [bien] | Mantiene conexiones del servidor abiertas [cuidado] |

Los tarpits ofrecen varias ventajas frente al bloqueo simple:

1. **Agotamiento de recursos**: Los escáneres automatizados tienen conexiones limitadas. Mantenerlos atascados reduce su capacidad de escaneo.
2. **Recopilación de inteligencia**: Las conexiones registradas en el tarpit revelan patrones de IP de los atacantes y las rutas objetivo.
3. **Impacto psicológico**: Los atacantes que descubren que han caído en un tarpit pueden evitar tu servidor en el futuro.
4. **Fallo de herramientas mal escritas**: Algunas herramientas de escaneo no gestionan bien las respuestas lentas y pueden fallar.

**Advertencia — Compromisos**

Los tarpits consumen recursos del servidor (conexiones, memoria). En un servidor con mucho tráfico, conviene ser selectivo respecto a qué rutas activan el tarpit. No apliques tarpit a páginas de error legítimas.

---

## Implementación en Nginx

Vamos a implementar una solución tarpit completa y lista para producción en [Nginx](https://nginx.org/). La estrategia consta de tres componentes:

1. **Generar contenido lento**: Crear una carga útil aleatoria grande
2. **Limitar la entrega**: Usar `limit_rate` para controlar el bandwidth
3. **Registrar para análisis**: Usar un formato de log especializado que fuerza el status code a 418 (aunque devolvamos 200) para que parsers como CrowdSec puedan detectarlo fácilmente.

### Paso 1: preparar Nginx (bloque http)

Necesitamos definir nuestros formatos de log y variables map en el `nginx.conf` principal. Esta configuración permite **incluir IPs de confianza en una lista blanca** (como tu IP de casa o VPN) ¡para que no te atrapes a ti mismo!

**Fichero: `/etc/nginx/nginx.conf` — Configuración de Nginx para maps y logging**

```nginx
http {
    # ... existing config ...

    # 1. Map to identify trusted IPs (Localhost, VPN, etc.)
    map $remote_addr $is_trusted_ip {
        127.0.0.1 1;
        ::1       1;
        # Add your static IP here if you have one
        # 1.2.3.4 1;
        default   0;
    }

    # 2. Dynamic Tarpit Rate
    # Trusted IPs get 0 (unlimited), others get 10 bytes/s
    map $is_trusted_ip $tarpit_rate {
        1 0;
        0 10;
    }

    # 2b. Only log tarpitted requests (avoid trusted IPs)
    map $is_trusted_ip $tarpit_loggable {
        1 0;
        0 1;
    }

    # 3. Special Log Format for Tarpit
    # We force the status code to 418 in the log, even though we return 200 to the client.
    # This allows CrowdSec to easily identify tarpit hits without complex parsing.
    log_format tarpit_418_fixed escape=none '$remote_addr - $remote_user [$time_local] "$request" 418 $body_bytes_sent "$http_referer" "$http_user_agent" "$host"';

    # 4. Generate random content (requires nginx-mod-http-perl)
    perl_set $slow_content 'sub {
        my $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        my $result = "";
        for (1..100000) {
            $result .= substr($chars, int(rand(length($chars))), 1);
        }
        return $result;
    }';
}
```

**Consejo — ¿Por qué forzar el status code 418 en el log?**

Para mantener al atacante conectado, debemos enviar un status `200 OK`. Sin embargo, para nuestro parser de logs (CrowdSec/Fail2Ban), queremos marcar explícitamente estas request como "tarpiteadas". Al codificar `418` directamente en el `log_format`, obtenemos lo mejor de los dos mundos: el atacante ve una conexión exitosa, pero nuestros logs reciben una señal clara de error/baneo.

### Paso 2: crear el snippet del tarpit

Crea un snippet reutilizable que gestione el throttle y el logging.

**Fichero: `/etc/nginx/snippets/tarpit.conf` — Configuración del snippet del tarpit en Nginx**

```nginx
# Log to dedicated tarpit log only for tarpitted requests
access_log /var/log/nginx/tarpit_access.log tarpit_418_fixed if=$tarpit_loggable;

# Bypass for Trusted IPs - Return standard 403 instead of Tarpit
# This saves you from waiting 2 hours if you accidentally hit a protected path!
if ($is_trusted_ip) {
    return 403;
}

# Dynamic bandwidth throttling (0 for trusted, 10 for others)
limit_rate $tarpit_rate;

# Start throttling after the first byte
limit_rate_after 1;

# Send the slow response
add_header Content-Type text/plain;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
add_header Pragma "no-cache";
return 200 $slow_content;
```

### Paso 3: definir las rutas sensibles (las "trampas")

En lugar de añadir lógica de logging a cada location (lo cual es un lío), simplemente **devolvemos 418** para cualquier ruta sensible. Gestionaremos este código de error globalmente en el server block.

**Fichero: `/etc/nginx/snippets/sensitive_files_tarpit.conf` — Configuración del tarpit para archivos sensibles**

```nginx
# =============================================================================
# Sensitive Files Tarpit Protection
# =============================================================================

# Environment and configuration files
location ~ /\.env { return 418; }
location ~ /\.git { return 418; }
location ~ /\.svn { return 418; }
location ~ /\.hg { return 418; }

# Server configuration files
location ~ /\.(htaccess|htpasswd) { return 418; }

# Backup and sensitive file extensions
location ~* \.(bak|backup|old|orig|save|swp|tmp|sql|db|sqlite|sqlite3)$ { return 418; }

# PHP/CMS exploit paths
location ~* (phpinfo|adminer|phpmyadmin|wp-login|xmlrpc|wp-admin|wp-config)\.php$ { return 418; }

# Common scanner/exploit paths
location ~* ^/(config|admin|administrator|login|cgi-bin|scripts|shell|cmd|console) { return 418; }

# Catch-all for dotfiles (excluding .well-known)
location ~ /\.(?!well-known) {
    return 418;
}
```

### Paso 4: configurar el server block

Ahora unimos todo usando `error_page 418`. Este es el "pegamento mágico" que hace que la arquitectura sea limpia.

**Fichero: `/etc/nginx/sites-available/example.com.conf` — Server block completo con configuración de tarpit**

```nginx
server {
    listen 443 ssl;
    server_name example.com;

    # ... SSL and other configuration ...

    # =========================================================
    # TARPIT HANDLER
    # =========================================================
    
    # 1. Catch 418 errors
    error_page 418 = @tarpit;
    
    # 2. Redirect them to the tarpit snippet
    location @tarpit {
        include /etc/nginx/snippets/tarpit.conf;
    }

    # =========================================================
    # SITE CONFIGURATION
    # =========================================================
    
    # Include the traps
    include /etc/nginx/snippets/sensitive_files_tarpit.conf;
    
    location / {
        try_files $uri $uri/ =404;
    }
}
```

### Paso 5: probar la configuración

Valida y recarga Nginx:

```bash
nginx -t && systemctl reload nginx
```

**Salida esperada**

```text
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```

Prueba el tarpit con un timeout para no quedarte esperando eternamente:

```bash
curl -v --max-time 5 'https://example.com/.env'
```

**Salida — Response del tarpit (truncada)**

```text
*   Trying [::1]:443...
* Connected to example.com (::1) port 443
* TLS 1.3 connection using TLS_AES_256_GCM_SHA384
> GET /.env HTTP/2
< HTTP/2 200
< content-type: text/plain
< cache-control: no-store, no-cache, must-revalidate, max-age=0
* Operation timed out after 5000 milliseconds with 50 bytes received
curl: (28) Operation timed out
```

La conexión se estableció, se recibieron 50 bytes (5 segundos × 10 bytes/segundo), y luego nuestro timeout entró en acción. ¡En la realidad, el escáner de un atacante esperaría mucho más!

---

## Integración con CrowdSec

Mientras el tarpit malgasta el tiempo del atacante, podemos ir más allá **baneando automáticamente** a los reincidentes. [CrowdSec](https://www.crowdsec.net/) puede analizar el log del tarpit y crear reglas de firewall.

### Crear un parser de CrowdSec

CrowdSec puede leer el archivo `/var/log/nginx/access.tarpit` para identificar IPs maliciosas:

**Fichero: `/etc/crowdsec/parsers/s02-enrich/nginx_tarpit.yaml` — Parser de CrowdSec para logs del tarpit**

```yaml
name: crowdsec/nginx-tarpit
description: "Parse Nginx tarpit access logs"
filter: "evt.Parsed.program == 'nginx-tarpit'"
onsuccess: next_stage
statics:
  - meta: service
    value: http
  - meta: log_type
    value: tarpit
  - meta: source_ip
    expression: "evt.Parsed.remote_addr"
  - meta: http_path
    expression: "evt.Parsed.request"
nodes:
  - grok:
      pattern: '%{IPORHOST:remote_addr} - %{DATA:user} \[%{HTTPDATE:time}\] "%{WORD:method} %{DATA:request} HTTP/%{NUMBER:http_version}" %{NUMBER:status} %{NUMBER:bytes}'
      apply_on: message
```

**Consejo — Configuración de Adquisición**

Asegúrate de que la configuración de adquisición de CrowdSec (`/etc/crowdsec/acquis.yaml`) establezca la etiqueta del programa:

```yaml
filenames:
  - /var/log/nginx/access.tarpit
labels:
  program: nginx-tarpit
```

### Crear un escenario

Define cuándo banear una IP (por ejemplo, tras 3 impactos en el tarpit en 5 minutos):

**Fichero: `/etc/crowdsec/scenarios/nginx_tarpit_scan.yaml` — Escenario de CrowdSec para detección de tarpit**

```yaml
type: leaky
name: crowdsec/nginx-tarpit-scan
description: "Detect and ban IPs triggering the Nginx tarpit"
filter: "evt.Meta.service == 'http' && evt.Meta.log_type == 'tarpit'"
leakspeed: 5m
capacity: 3
groupby: "evt.Meta.source_ip"
blackhole: 1h
labels:
  service: http
  type: scan
  remediation: true
```

**Consejo — Alternativa más Sencilla**

Si no quieres crear parsers personalizados, puedes usar [Fail2Ban](https://github.com/fail2ban/fail2ban) en su lugar. Crea un filtro que coincida con las líneas de `/var/log/nginx/access.tarpit` y configura un jail para banear tras N coincidencias.

---

## Reportar IPs maliciosas: contribuir a la defensa colectiva

Uno de los aspectos más potentes de ejecutar un tarpit es la **inteligencia que recopilas**. Cada conexión atrapada revela la IP del atacante, las rutas objetivo y los patrones de tiempo. Pero estos datos se vuelven exponencialmente más valiosos cuando se **comparten con la comunidad de seguridad**.

### ¿Por qué reportar IPs maliciosas?

**Información — El Poder de la Defensa Colectiva**

Cuando reportas una IP maliciosa, no solo te proteges a ti — estás protegiendo a **todos**. Las plataformas de inteligencia de amenazas agregan informes de miles de colaboradores, creando listas de bloqueo en tiempo real que pueden bloquear preventivamente a los atacantes antes de que lleguen a tu servidor.

Entre los beneficios de reportar se incluyen:

1. **Detección temprana**: Otras organizaciones pueden bloquear amenazas que has identificado antes de ser atacadas
2. **Reconocimiento de patrones**: Los datos agregados revelan ataques coordinados, botnets y campañas de malware
3. **Reducción de la superficie de ataque**: Las IPs reportadas son a menudo bloqueadas por ISPs y herramientas de seguridad a nivel global
4. **Reciprocidad de la comunidad**: Los colaboradores reciben acceso a listas de bloqueo más amplias y completas

### Integración con AbuseIPDB

[AbuseIPDB](https://www.abuseipdb.com/) es una plataforma de inteligencia de amenazas impulsada por la comunidad con más de 100.000 colaboradores. Puedes tanto consultar la reputación de una IP como reportar IPs maliciosas a través de su API.

#### Consultar la reputación de una IP

Antes de bloquear, verifica el nivel de amenaza:

```bash
curl -G "https://api.abuseipdb.com/api/v2/check" \
  --data-urlencode "ipAddress=185.234.xx.xx" \
  -d maxAgeInDays=90 \
  -H "Key: YOUR_API_KEY" \
  -H "Accept: application/json"
```

#### Reportar IPs maliciosas desde los logs del tarpit

Crea un script para reportar automáticamente las capturas del tarpit:

**Fichero: `/usr/local/bin/report_tarpit_ips.sh` — Script para reportar IPs del tarpit a AbuseIPDB**

```bash
#!/bin/bash
# Report IPs from tarpit log to AbuseIPDB
# Usage: Run via cron every hour

set -euo pipefail

ABUSEIPDB_KEY="${ABUSEIPDB_KEY:-}"
LOGFILE="/var/log/nginx/access.tarpit"
REPORTED="/var/log/nginx/reported_ips.txt"
REPORT_LOG="/var/log/nginx/abuseipdb_reports.log"

# Validate API key
if [[ -z "$ABUSEIPDB_KEY" ]]; then
  echo "[$(date -Iseconds)] ERROR: ABUSEIPDB_KEY not set" >> "$REPORT_LOG"
  exit 1
fi

# Ensure files exist
touch "$REPORTED" "$REPORT_LOG"

# Get unique IPs from last hour
awk -v d1="$(date --date='-1 hour' '+%d/%b/%Y:%H')" \
  '$4 ~ d1 {print $1}' "$LOGFILE" | sort -u | while read -r ip; do
  
  # Skip if already reported today
  grep -q "$ip" "$REPORTED" 2>/dev/null && continue
  
  # Report to AbuseIPDB with error handling
  response=$(curl -s -w "\n%{http_code}" "https://api.abuseipdb.com/api/v2/report" \
    -H "Key: $ABUSEIPDB_KEY" \
    -H "Accept: application/json" \
    --data-urlencode "ip=$ip" \
    --data-urlencode "categories=21,15" \
    --data-urlencode "comment=Vulnerability scanner trapped in HTTP tarpit." \
    2>&1) || true
  
  http_code=$(echo "$response" | tail -n1)
  body=$(echo "$response" | sed '$d')
  
  if [[ "$http_code" == "200" ]]; then
    echo "$ip $(date +%Y-%m-%d)" >> "$REPORTED"
    echo "[$(date -Iseconds)] OK: Reported $ip" >> "$REPORT_LOG"
  else
    echo "[$(date -Iseconds)] FAIL: $ip (HTTP $http_code) $body" >> "$REPORT_LOG"
  fi
done
```

**Advertencia — Límites de la API**

Las cuentas gratuitas de AbuseIPDB tienen límites diarios. Para servidores con alto volumen, considera sus planes de pago o agrupa tus reportes. Elimina siempre la información de identificación personal (PII) de los comentarios de los reportes.

### Listas de bloqueo de la comunidad CrowdSec

A diferencia de AbuseIPDB (que es una consulta pasiva), [CrowdSec](https://www.crowdsec.net/) funciona como un **"firewall masivamente multijugador"**. Cuando tu Security Engine detecta una amenaza, comparte la señal con la red, y tú recibes actualizaciones de listas de bloqueo que contienen amenazas detectadas por otros usuarios.

**Niveles de Listas de Bloqueo de CrowdSec**

| Nivel | Tamaño | Requisito |
| --- | --- | --- |
| **Lite** | 3.000 IPs | Cuenta gratuita, sin contribución |
| **Community** | ~15.000 IPs [bien] | Contribución regular de señales |
| **Premium** | Ilimitado [bien] | Suscripción de pago |

La lista de bloqueo Community se actualiza en tiempo real y está adaptada a tu stack — si ejecutas WordPress, recibirás automáticamente IPs de amenazas específicas de WordPress.

---

## Técnicas avanzadas

### Combinar con rate limiting

Para mayor protección, combina el tarpit con rate limiting para evitar sobrecargar tu servidor:

**Combinación de tarpit con rate limiting**

```nginx
# Define rate limit zone
limit_req_zone $binary_remote_addr zone=tarpit_zone:10m rate=1r/s;

location @tarpit {
    limit_req zone=tarpit_zone burst=5 nodelay;
    limit_rate 10;
    # ... rest of tarpit config
}
```

### Tarpit para User-Agents específicos

Apunta a escáneres maliciosos conocidos por User-Agent. Los escáneres más comunes incluyen [sqlmap](https://sqlmap.org/) (inyección SQL), [Nikto](https://github.com/sullo/nikto) (escáner de vulnerabilidades web), [Nmap](https://nmap.org/) (escáner de red), [masscan](https://github.com/robertdavidgraham/masscan) (escáner de puertos) y [ZGrab](https://github.com/zmap/zgrab2) (banner grabber):

**Tarpit para bots de escaneo basado en User-Agent**

```nginx
map $http_user_agent $is_scanner {
    default                     0;
    "~*sqlmap"                  1;
    "~*nikto"                   1;
    "~*nmap"                    1;
    "~*masscan"                 1;
    "~*zgrab"                   1;
}

server {
    if ($is_scanner) {
        return 418;
    }
    # ... rest of server
}
```

---

## Monitorización de tu tarpit

### Analizar los logs del tarpit

Como forzamos el status code a **418** en nuestro formato de log `tarpit_418_fixed`, filtrar los ataques es increíblemente sencillo, incluso si están mezclados con otros logs:

```bash
grep " 418 " /var/log/nginx/tarpit_access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
```

**Salida — Rutas más escaneadas**

```text
  847 /.env
  312 /wp-login.php
  201 /.git/config
  156 /admin/
   98 /phpmyadmin/
   87 /.htaccess
   65 /config.php
   43 /backup.sql
   38 /shell.php
   29 /xmlrpc.php
```

### IPs más infractoras

Identifica los escáneres más persistentes:

```bash
awk '{print $1}' /var/log/nginx/tarpit_access.log | sort | uniq -c | sort -rn | head -5
```

**Salida — IPs más infractoras**

```text
  423 185.234.xx.xx
  287 45.148.xx.xx
  156 194.169.xx.xx
   98 23.94.xx.xx
   67 89.248.xx.xx
```

---

## Resultados en el mundo real

Implementar la **arquitectura lista para producción** con logging forzado a 418 ha mejorado significativamente la visibilidad:

- **Señal clara:** El status code 418 actúa como una señal de alta fidelidad para "intención maliciosa confirmada".
- **Cero falsos positivos:** Gracias al map de IPs de confianza, el tráfico de administración nunca se atrapa accidentalmente en el tarpit.
- **Protección de recursos:** El bypass de rate limit garantiza que el tráfico válido fluya sin problemas mientras los atacantes se quedan atascados en el carril lento.
- **Integración con CrowdSec:** La lógica del parser se volvió trivial — simplemente hacer coincidir `status == 418` es suficiente para banear una IP, independientemente de la ruta solicitada.

**Idea clave — Lo que este tarpit ha atrapado de verdad**

Los cuatro puntos de arriba son cualitativos, así que aquí va la cifra. Este
sitio ejecuta el tarpit que describe esta guía, y el contador está en la
[página del homelab](/es/homelab/) — lo inyecta el servidor de borde al
responder, no JavaScript, así que lo que lees es lo que el servidor tenía en ese
instante. Marcaba **1.318 hits de tarpit y 90 baneos de Nginx** cuando se
escribió esta nota (22-08-2026); cuando la leas, la cifra en vivo será otra. Esa
es justo la idea: la medición es la página, no esta frase.

**Correcto — El Factor Satisfacción**

Hay cierta satisfacción en saber que mientras duermes, los atacantes están descargando caracteres aleatorios a 10 bytes por segundo. ¡Dulces sueños!

---

## Conclusión

Un tarpit es una adición creativa y eficaz a tu arsenal de seguridad. Aunque no debería sustituir un endurecimiento adecuado y controles de acceso, proporciona:

1. **Desperdicio de recursos** para escáneres automatizados
2. **Recopilación de inteligencia** sobre patrones de ataque
3. **Baneo automático** cuando se combina con CrowdSec o Fail2Ban
4. **Un elemento disuasorio psicológico** para los atacantes

La implementación en Nginx es directa usando solo unas pocas directivas (`limit_rate`, `error_page` y bloques location). Combinado con logging y una herramienta de seguridad como CrowdSec, se crea una defensa por capas que no solo bloquea a los atacantes, sino que les hace *pagar* por sus intentos de intrusión.

