Skip to main contentSkip to footer

Detección y reglas

Qué encuentra el agente en los registros que un servidor ya escribe: los catorce tipos de fuente y sus umbrales, cómo se añade una fuente que el instalador no encontró, qué sale de la máquina y qué no sale nunca, y los archivos de reglas de los que está hecho cada detector, incluidos los que escribes tú.

Qué encuentra el agente en tus registros

Cada fuente tiene su propio detector y su propio umbral, porque cinco inicios de sesión SSH fallidos en diez minutos y cincuenta peticiones web sospechosas en dos horas son la misma afirmación sobre un atacante y no el mismo número. La rotación, copytruncate y un registro que desaparece un rato están cubiertos, y un archivo ilegible produce una advertencia y no una por lectura.

Los catorce tipos de fuente

TipoRuta típicaQué cuenta como coincidencia
sshdjournald, si no /var/log/auth.log o /var/log/secureContraseñas equivocadas, usuarios inválidos, claves rechazadas para un usuario que no existe, límites de intentos superados, y los fallos previos a la autenticación que caracterizan a los escáneres: ninguna cadena de identificación, una versión de protocolo errónea, datos inservibles en el intercambio de banner. Una clave rechazada para un usuario válido no cuenta a propósito, porque un administrador con cinco claves escribe cuatro de esas por cada inicio de sesión correcto. Un inicio de sesión correcto borra los fallos de esa dirección.
web/var/log/nginx/access.log o un patrón por sitioLos POST de inicio de sesión, contados sea cual sea el código de estado, y las rutas que ningún cliente legítimo pide. Todo lo demás de un registro de acceso se ignora, porque esta es la fuente que tiene clientes de verdad detrás.
web-error/var/log/nginx/error.log, /var/log/apache2/error.logLas peticiones que el propio servidor web ha rechazado, los fallos de autenticación HTTP básica, y las líneas de ModSecurity de gravedad crítica cuando caen aquí en lugar de en un registro de auditoría. Las líneas de limitación de tasa no cuentan a propósito: un límite se dispara también con un navegador real con veinte pestañas. Los mensajes de negociación TLS y de PHP dicen algo del servidor y nada del cliente.
postfix/var/log/mail.log, /var/log/maillogTres fuentes de evento distintas a partir de un solo archivo: los fallos de autenticación SASL, los rechazos NOQUEUE que son realmente 5xx (un 450 es greylisting y no cuenta), y los bloqueos de amavis. Un correo entregado nunca es una coincidencia.
dovecotel mismo registro de correoFallos de inicio de sesión IMAP y POP3, uno por línea, diga lo que diga «N attempts». La dirección remota viene del campo rip=, nunca de lip=, que es la dirección propia del servidor. Un inicio de sesión abortado sin intento de autenticación no es una coincidencia: eso es un escaneo TLS, y no se probó ninguna contraseña.
exim/var/log/exim4/mainlog, /var/log/exim/main.logFallos de autenticación y remitentes rechazados. No verificado contra un host real: los patrones vienen del formato de registro documentado.
ftp/var/log/syslog, /var/log/messagesFallos de autenticación de pure-ftpd, proftpd y vsftpd. Los tres registran a través de syslog, y cada uno escribe la dirección del otro extremo de forma distinta, así que se usan tres patrones. pure-ftpd está medido: 494 de 494 líneas de un host de producción tenían una única forma.
namedel mismo syslogSolo una consulta entrante que bind ha rechazado. La gran trampa es el sentido inverso: connection refused resolving es el resolvedor de este host que no alcanza el servidor de nombres de otra persona, y el 90 por ciento de las líneas medidas eran eso. Un detector sin esa excepción notifica los servidores de nombres ajenos como atacantes.
panel/var/log/ispconfig/auth.logInicios de sesión fallidos en el panel de ISPConfig. El archivo tiene 0 bytes en todos los hosts medidos, y eso no es un defecto: el panel simplemente no escribe nada ahí. doctor lo señala al cabo de una semana.
modsec/var/log/apache2/modsec_audit.logUn evento por transacción cuyo veredicto lleva gravedad crítica. Es el único detector con estado, porque una transacción abarca varias líneas. Nada de la petición viaja en el evento: ni el identificador de regla, ni la parte coincidente, ni la URI.
csf/var/log/lfd.logLo que CSF ha bloqueado realmente, nunca lo que solo ha detectado. Sin un segundo umbral por encima: lfd ya había contado antes de que el agente viera la línea, así que un bloqueo es un informe.
fail2banel diario de la unidad, si no /var/log/fail2ban.logSolo acciones de bloqueo. Un Restore Ban nunca se notifica, porque eso es lo que un reinicio de fail2ban reproduce desde su propia base de datos y no un ataque nuevo. Una línea Found de un filtro tampoco se notifica: el jail aún no ha decidido.
imunify360consulta imunify360-agentIncidentes del CLI, asociados a categorías por el prefijo del nombre de regla. Experimental y no verificado contra una instalación con licencia; el decodificador omite lo que no puede leer en vez de convertir una respuesta inesperada en una fuente muerta.
scanel diario del núcleo (journalctl -k), si no /var/log/kern.log o /var/log/messagesLas líneas de la regla de escaneo de puertos que la sincronización instala con esta fuente: un SYN a un puerto en el que este host no escucha, con el prefijo rip-scan:. Diez sondeos en diez minutos desde una dirección son un escaneo. Nada en una línea del registro del núcleo lo escribe el otro extremo. Véase más abajo.

web-app no es un tipo de fuente y no puede escribirse en sources. Es un segundo detector que se ejecuta sobre las líneas que una fuente web ya lee, y tiene su propio umbral, por eso aparece con su propio nombre en la tabla de umbrales de más arriba. Un agente cuya configuración lo nombra no arranca: sources[0]: type "web-app" is not supported in this version.

Escaneos de puertos, desde el registro del núcleo

Todas las demás fuentes leen un registro que escribe un servicio. La fuente scan lee el núcleo, y es la única fuente que además cambia el cortafuegos: con ella en la configuración, la sincronización añade detrás de las listas una regla que registra un SYN a un puerto en el que este host no escucha, con el prefijo rip-scan: y como máximo diez líneas por minuto tras una primera ráfaga de 20, para que un escaneo de los 65535 puertos no inunde el diario. Con el backend ipset es una cadena propia, rip-blacklist-scan; con nftables es una regla en la cadena. Los puertos en escucha se leen de /proc en cada sincronización, así que un servicio que arrancas deja de ser objetivo de escaneo tras la siguiente pasada, y no hay nada que declarar. Solo cuenta como puerto en escucha un socket que algo de fuera podría alcanzar. Un puerto asociado únicamente a 127.0.0.1 o a ::1 sigue dentro de la regla, porque un paquete que llega ahí desde la red es un sondeo de un puerto cerrado y nada más. La lista de la última sincronización no es la única comprobación: la regla también consulta al núcleo cuando llega el paquete, y un SYN a un puerto en el que en ese momento escucha un socket es una conexión y nunca se registra. Eso cubre un servicio arrancado después de la última sincronización y un demonio que abre un puerto solo para una transferencia. El rango pasivo de un servidor FTP en marcha (pure-ftpd, proftpd, vsftpd) se lee de su configuración y queda fuera de la regla; si el servidor funciona sin rango configurado, queda fuera en su lugar el rango de puertos efímeros del núcleo. Un núcleo sin la coincidencia socket mantiene la regla sin esta comprobación.

La regla está detrás de la lista blanca y detrás de las listas. Una dirección de tu lista blanca ha salido de la cadena antes y no produce ninguna línea, y una dirección que las listas ya descartan tampoco llega a ella. En mode: log y mode: off hace lo que hace todo lo demás en esos modos.

La fuente lee el diario del núcleo, journalctl -k, y recurre a /var/log/kern.log o /var/log/messages donde no hay diario; un path que definas gana. La regla incluida scan cuenta diez sondeos en diez minutos desde una dirección, los notifica bajo la categoría 14 (Port Scan) como port probes y bloquea la dirección como cualquier otro hallazgo. Un cliente que reintenta un puerto cerrado envía como máximo seis paquetes, por eso el umbral es diez. install escribe la fuente en un host nuevo como última entrada de sources; un host instalado antes conserva su configuración, y añadirla es una línea, seguida de un reinicio del servicio watch y de una sincronización para la regla.

yaml
# config.yaml: the source, next to the ones the installer wrote
sources:
  - type: sshd
  - type: scan

# /etc/reportedip-agent/rules.d/50-scan.yaml: ban a scanner on this
# host, but leave the report to hosts that see more of it. Every field
# not named here stays as shipped.
format: 1
rules:
  - id: scan
    action: {ban: true, report: false}

# The same file with enabled: false switches the rule off; the firewall
# rule and its log lines stay. A threshold of your own goes into
# config.yaml and wins over the file:
thresholds:
  scan: {hits: 20, window_minutes: 10}

Añadir una fuente que el instalador no encontró

Una fuente que el instalador no encontró simplemente falta en la configuración, y añadirla es una entrada con una ruta o un patrón. Como un archivo con un bloque sources sustituye por completo la lista predeterminada, tu entrada va junto a las existentes y no en un segundo bloque.

yaml
sources:
  - type: sshd
  - type: web
    path: /var/log/nginx/access.log

  # A second web server on a different path
  - type: web
    path: /var/log/caddy/access.log

  # Every site of a panel host, rescanned every ten minutes.
  # At most five wildcard segments.
  - type: web
    glob: /var/www/clients/*/web*/log/access.log
    exclude:
      # This site reports to the API on its own, through Hive or a
      # honeypot. Without the exclude the host sends every address
      # twice and pays twice out of the daily quota.
      - /var/log/ispconfig/httpd/honeypot.example.com/*

  # Exim on a host the installer saw as a Postfix machine
  - type: exim
    path: /var/log/exim4/mainlog

Dos rutas que apuntan al mismo archivo no son un problema y no necesitan exclude: ISPConfig publica cada registro de acceso dos veces, en /var/www y en /var/log/ispconfig, y el agente lee un archivo así una sola vez. Antes de confiar en una entrada nueva, apunta reportedip-agent test al archivo, reinicia después el servicio watch y mira en status: la sección sources muestra las coincidencias por fuente. doctor muestra un recuento files= por fuente, y una fuente con recuento cero no se resuelve en nada.

Qué sale de la máquina

Un informe contiene la dirección, los identificadores de las categorías de amenaza y una frase generada. Ninguna línea de registro, ningún cuerpo de petición, ningún User-Agent, ninguna URL y ningún nombre de usuario se transmite nunca, así que un informe no puede filtrar tus clientes, tus rutas ni tus credenciales, ni siquiera por accidente.

Una precisión, porque una promesa absoluta sería falsa aquí. Dos fuentes ponen en esa frase generada un nombre procedente del registro: con fail2ban el nombre del jail que bloqueó, con imunify360 el nombre de la regla que se activó. Los dos se filtran a letras, dígitos, punto, guion y guion bajo y se recortan a 32 caracteres antes de salir de la máquina, porque un nombre de jail procede de una línea de registro y es por tanto una entrada cercana al atacante. Ninguna otra fuente envía nada procedente de una línea de registro.

El nombre del host se envía, y solo con un fin: que reconozcas tu propia máquina en la lista de servidores en vez de comparar identificadores aleatorios. Se filtra en el cliente a A-Za-z0-9._-, eliminando todo lo demás en lugar de sustituirlo, se recorta a 191 caracteres, y nunca sale sin el identificador de instalación al lado.

La identidad sigue siendo el identificador de instalación. El nombre del host es una etiqueta y nada más: renombrar la máquina no cambia nada de la licencia, nada sobre qué host la tiene y nada de lo que el servidor decide. Además puedes darle a un host una etiqueta propia en tu cuenta; se muestra delante del nombre del host y nunca se deriva de la máquina.

Cuatro cosas no se notifican nunca y no se bloquean nunca, y eso es una frontera en el código y no un ajuste: los rangos privados y reservados, la interfaz loopback, cada dirección de las interfaces de este servidor junto con su puerta de enlace predeterminada, y todo lo que esté en tu lista blanca. La dirección del cliente SSH tomada en la instalación está en la lista blanca automática y pertenece por tanto al último grupo.

Reglas

Una regla dice qué líneas de una fuente son aciertos, dónde está la dirección en una línea así, cómo se llama un acierto y cuántos de ellos en cuántos minutos se convierten en un bloqueo o en un informe. Cada detector del agente es una regla, y cada regla es un archivo. Se leen tres capas y se fusionan por el identificador de la regla:

CapaDónde estáQué es
1Dentro del binarioLas reglas incluidas, un archivo por tipo de fuente. Corren desde el binario. Tras la siguiente sincronización hay una copia para leer en /var/lib/reportedip-agent/rules.d/standard/; un cambio hecho ahí lo sobrescribe la sincronización siguiente. install crea ese directorio y packs/ a su lado, vacíos, para que las tres capas sean visibles antes de que la primera sincronización llene dos de ellas.
2Un paquete de reglas firmado de reportedip.comLa sincronización lo descarga, verifica su firma contra una clave integrada en el binario antes de leer un solo byte de él como regla, y lo guarda en el directorio de estado. Un host que no alcanza el servicio conserva el paquete que tiene. Un paquete cuya firma no verifica se rechaza, y el que está en disco se queda.
3/etc/reportedip-agent/rules.d/*.yamlTus archivos: anulaciones de reglas incluidas, y reglas propias.

El paquete gana al binario, tus archivos ganan al paquete. Una anulación nombra una regla por su id y solo los campos que cambian; cada campo que no nombra conserva el valor de la capa de abajo. match y examples se sustituyen enteros, action campo a campo. enabled: false apaga una regla. El nombre del archivo no cuenta: un archivo llamado 10-sshd.yaml en rules.d no tapa el archivo incluido de ese nombre, se lee como cualquier otro y se fusiona por los identificadores que contiene, así que una versión que renombre un archivo incluido no puede convertir tu anulación en un duplicado.

El formato del archivo

Un archivo contiene tantas reglas como quieras. Esta es una completa, sacada de la propia suite de pruebas del agente:

yaml
format: 1
rules:
  - id: my-sshd
    source: sshd
    match:
      regex: '(Failed password|Invalid user) .* from '
      anchors: ["Failed password", "Invalid user", "Accepted "]
      reset: 'Accepted (password|publickey) for .* from '
    addr: {after: " from ", occurrence: last}
    categories: [22, 18]
    noun: failed logins
    threshold: {hits: 5, window_minutes: 10}
    examples:
      match:
        - {line: "Sep 24 14:47:38 host sshd[1]: Failed password for root from 45.33.32.156 port 51422 ssh2", addr: 45.33.32.156}
      inject:
        - {line: "Sep 24 14:47:38 host sshd[1]: Invalid user x from 8.8.8.8 port 22 from 45.33.32.156 port 51422", addr: 45.33.32.156}
      nomatch:
        - "Sep 24 14:47:38 host sshd[1]: Connection closed by 45.33.32.156 port 1"
CampoSignificadoQué comprueba el cargador
formatLa primera línea de todo archivo de reglas. Este agente lee el formato 1.Un archivo con un número mayor se escribió para un agente más nuevo y se salta; un archivo sin esa línea, también.
idEl nombre de la regla, tal como lo muestran rules list y el diario.a-z, 0-9 y - a partir del segundo carácter, como mucho 32 bytes. Un identificador que ya existe en una capa inferior convierte la regla en una anulación.
sourceEl tipo de fuente cuyas líneas lee la regla, uno de los catorce: sshd, fail2ban, web, web-error, postfix, dovecot, exim, ftp, named, panel, modsec, csf, imunify360, scan.Obligatorio para una regla nueva.
eventLa fuente de evento bajo la que cuentan los aciertos, el nombre al que se refiere una entrada bajo thresholds en config.yaml, y el nombre que lleva el comentario de un informe. Por defecto, el identificador.El mismo juego de caracteres que un identificador. Las reglas que comparten un evento comparten su umbral, y un acierto cuenta una vez, la haya cazado la que sea. Una regla tuya no puede apropiarse del evento de una regla incluida: anula esa regla por su identificador en su lugar.
enabledtrue o false; una clave ausente vale true.enabled: false en una anulación apaga una regla incluida sin tocar su archivo.
match.regexEl patrón que decide si una línea es un acierto, y nada más. Sintaxis RE2, tal como la lee Go.Como mucho 512 bytes. Sin banderas como (?i), (?s) o (?m), sin grupos con nombre: la dirección no sale del patrón.
match.anchorsPalabras literales que lleva todo acierto. El patrón solo corre sobre una línea que lleve una de ellas; cualquier otra línea nunca llega a él.Obligatorio. Cada una de al menos 6 bytes y parte literal de regex o de reset. Cuando reset está definido, al menos un ancla tiene que aparecer en él, o el reset nunca se dispara.
match.ignorePatrones que descartan una línea antes de que regex la vea.RE2, los mismos límites que regex.
match.resetEl patrón de una línea que pone a cero el contador de una dirección, un inicio de sesión correcto.RE2, los mismos límites. La dirección se lee igual que en un acierto.
match.builtinEn lugar de un patrón, el nombre de un detector compilado. Las reglas incluidas para web, web-app, web-error, modsec, exim, panel, named, fail2ban, csf e imunify360 son de este tipo y solo llevan la política.No junto con regex, y sin anchors, ignore, reset, addr ni examples.
addrDónde está la dirección en una línea que coincide. Exactamente una forma: after con occurrence: last o first; between: [left, right]; in_brackets: N, el N-ésimo [...] contando desde 1; before_byte: ":", un byte; field: N, el N-ésimo campo separado por espacios contando desde 0. Un upto: "<" opcional corta la línea en la primera aparición de esa marca antes de aplicar la forma, así que un campo que el otro extremo escriba detrás no se puede alcanzar.Obligatorio para una regla regex. after necesita al menos 3 bytes y una occurrence; upto tiene como mucho 16 bytes.
categoriesLos identificadores de las categorías de amenaza que lleva un informe. Hay 63, numeradas del 1 al 63.Obligatorio cuando la regla notifica. Cada uno al menos 1.
nounCómo se llama un acierto en el comentario de un informe, por ejemplo failed logins.Letras, dígitos y espacios, como mucho 40.
thresholdhits dentro de window_minutes, el par que un jail de fail2ban llamaba maxretry y findtime.hits de 1 a 10000, window_minutes de 1 a 1440. Sin el bloque se aplican min_hits y window_minutes de config.yaml. Una entrada bajo thresholds en config.yaml gana a ambos.
actionban y report, cada uno true o false; ambos valen true si faltan.Ambos en false se rechaza: una regla que no hace nada se apaga con enabled: false en su lugar.
examplesLas pruebas de la regla. match: líneas y la dirección que da cada una. inject: líneas que llevan una segunda dirección, un señuelo, en un campo que escribe el otro extremo, y la dirección que la regla debe dar de todos modos. nomatch: líneas que la regla no debe aceptar.Obligatorio para una regla regex, al menos una de cada clase, y al menos una línea nomatch tiene que llevar una dirección. Corren cada vez que se carga la regla; una regla cuyos ejemplos no se cumplen no carga.

Por qué la dirección no es un grupo de captura

fail2ban pasó por esto dos veces, CVE-2013-2178 y CVE-2009-0362, y las dos veces fue el mismo error: la dirección salía del patrón, y un atacante había puesto una dirección a su elección en un campo que el patrón alcanzaba. RE2 encuentra la coincidencia más a la izquierda, así que en Invalid user x from 8.8.8.8 port 22 from 45.33.32.156 port 51422 un grupo detrás del primer from bloquearía 8.8.8.8, que es el nombre de usuario que tecleó el atacante. addr: {after: " from ", occurrence: last} toma el último, el que escribió el propio sshd. Por eso una regla nombra una posición y no un grupo, y por eso cada regla lleva una línea inject que lo demuestra.

Qué te protege de tu propia regla

Los ejemplos corren cada vez que se carga la regla, así que un patrón que ya no acepta sus propias líneas no carga. Una regla de patrón que tu capa añade o anula bloquea y no notifica durante las primeras 24 horas de su contenido (una regla con match.builtin nunca): una entrada equivocada en el núcleo caduca y aparece en ban list, un informe equivocado no se puede retirar. rules status marca una regla así como young. Un reinicio del demonio vuelve a empezar el día.

Dos guardias vigilan una regla mientras corre. Cuando tres direcciones de tu lista blanca alcanzan su umbral en diez minutos, la regla lee el campo equivocado, porque los atacantes reales nunca están en la lista blanca; deja de notificar hasta que su archivo cambie, y sigue bloqueando, que es lo que la lista blanca cubre. Cuando una regla joven empuja a más de 50 direcciones distintas por encima de su umbral en un minuto, acepta a cualquier visitante y se suspende entera, bloqueos e informes, hasta que su contenido cambie. Una regla establecida nunca se suspende por una ráfaga: una fuerza bruta distribuida con cientos de fuentes por minuto es exactamente para lo que existe una regla sshd. Una regla del paquete la vigila el guardia de ráfagas como a una joven, porque un paquete llega a todos los hosts a la vez.

Una entrada bajo thresholds en config.yaml sigue ganando al archivo, así que un host que hayas ajustado no pierde nada. Un archivo que no es YAML válido, o que nombra un formato más nuevo, se salta él solo y los demás cargan. Una regla que falla una de las comprobaciones de arriba cuesta toda tu capa: las reglas incluidas y el paquete siguen corriendo, status y doctor lo dicen, y rules check nombra el archivo, la línea y el identificador.

Los comandos rules

bash
reportedip-agent rules list                 # every rule: id, source, event, kind, state, threshold, action
reportedip-agent rules show sshd            # one rule as merged, and whether it is overridden
reportedip-agent rules check                # load rules.d the way the daemon does, and say what is wrong
reportedip-agent rules check /root/my.yaml  # try a file before it is put in place
reportedip-agent rules export sshd          # print the shipped file of a rule, as the template
reportedip-agent rules status               # state, hits since the daemon started, young and suspended rules
reportedip-agent rules disable sshd         # write enabled: false for one id; enable takes it back
reportedip-agent test --rule my-sshd /var/log/auth.log   # run one rule alone over a real log

list, show, check, export y status leen y muestran. check sin archivo carga tu directorio exactamente como lo haría el demonio; con archivos carga esos sobre el conjunto incluido, así que un archivo se puede probar antes de estar en rules.d. export muestra el archivo incluido en el que vive una regla, comentarios incluidos, como punto de partida de una anulación. enable y disable son los dos que escriben: un único archivo generado, 90-agent-toggles.yaml, accesible solo para root, reescrito en cada llamada. test --rule corre solo esa regla y ningún detector compilado al lado, toma el tipo de fuente de la regla, y no notifica ni bloquea nada, como test siempre.

Escribir una regla propia

  1. Partir de un archivo incluido. reportedip-agent rules export sshd > /root/60-sshd.yaml muestra el archivo en el que vive la regla, con comentarios y ejemplos.
  2. Recortarlo, o darle un identificador nuevo. Para una anulación conserva el id y solo los campos que cambian; el archivo de abajo está completo. Para una regla propia dale un id nuevo con source, match, addr, categories y examples, como my-sshd arriba. Un identificador nuevo es una fuente de evento nueva, y la regla incluida sigue corriendo al lado.
  3. Comprobarlo. reportedip-agent rules check /root/60-sshd.yaml carga el archivo sobre el conjunto incluido exactamente como lo haría el demonio y muestra cada problema con archivo, línea e identificador, o una línea que dice ok.
  4. Ponerlo en su sitio. install -m 644 -o root -g root /root/60-sshd.yaml /etc/reportedip-agent/rules.d/. El directorio y cada archivo que contiene tienen que pertenecer a root y no ser escribibles por nadie más, y un enlace simbólico que apunte fuera del directorio se rechaza; si no, la capa entera no se usa.
  5. Esperar un minuto. El demonio recoge el cambio por su cuenta, sin reinicio, y los contadores vuelven a empezar. reportedip-agent rules status muestra la regla, marcada como young durante su primer día. reportedip-agent test --rule my-sshd /var/log/auth.log muestra mientras tanto lo que habría cazado.
yaml
# /etc/reportedip-agent/rules.d/60-sshd.yaml: the shipped sshd rule,
# stricter on this host. Everything not named here stays as shipped.
format: 1
rules:
  - id: sshd
    threshold: {hits: 3, window_minutes: 10}

Última actualización: · Mantenido por el equipo de ReportedIP

Security Focused
Conforme al RGPD
Made in Germany
Volver a la documentación