1. Fundamentos de expresiones regulares en seguridad

En las tareas de bastionado y auditoría, la inspección manual de ficheros de configuración y registros de eventos resulta inviable por su volumen. Las expresiones regulares constituyen un lenguaje formal para describir patrones de texto y permiten localizar en segundos directivas mal configuradas, credenciales expuestas o evidencias de compromiso.

Su dominio es una competencia transversal del módulo: las mismas expresiones que se escriben con grep se reutilizan después en sed, awk, en los filtros de Fail2ban, en las reglas de detección de un SIEM o en las comprobaciones automatizadas de OpenSCAP.

1.1. Variantes de expresiones regulares

En los sistemas GNU/Linux conviven tres dialectos que conviene distinguir con precisión, ya que un mismo patrón puede tener significados distintos según el motor que lo interprete:

  1. Expresiones regulares básicas o BRE (Basic Regular Expressions): Es el dialecto por defecto de grep y sed. Los caracteres +, ?, {, }, (, ) y | son literales y solo adquieren significado especial al escaparlos. El estándar POSIX define \{n,m\} y \(\). Las formas \+, \? y \| son extensiones de GNU que no funcionan en otras implementaciones.

  2. Expresiones regulares extendidas o ERE (Extended Regular Expressions): Los operadores +, ?, {n,m}, () y | funcionan sin escape, lo que produce patrones más legibles. Se activan con grep -E y sed -E, y son el dialecto nativo de awk.

  3. Expresiones compatibles con Perl o PCRE (Perl Compatible Regular Expressions): Añaden abreviaturas como \d, \w y \s, cuantificadores perezosos, aserciones de anticipación y retrospección, y la secuencia \K, que descarta del resultado todo lo que la precede. En GNU grep se activan con -P. No están disponibles en sed ni en awk.

Una expresión regular no es un comodín de la shell

En la shell, *.log significa «cualquier nombre que termine en .log». En una expresión regular, * significa «cero o más repeticiones del elemento anterior» y . representa cualquier carácter. Por ello, los patrones deben escribirse siempre entre comillas simples, para que la shell no los expanda ni interprete $, \ o ! antes de entregárselos a grep.

1.2. Metacaracteres fundamentales

Metacarácter Descripción Ejemplo Explicación técnica

^

Anclaje de inicio de línea

^PermitRootLogin

Solo coincide si la cadena aparece al principio de la línea, lo que descarta las versiones comentadas con #.

$

Anclaje de fin de línea

/bin/bash$

Solo coincide si la línea termina con la cadena, útil para clasificar intérpretes de órdenes en /etc/passwd.

.

Cualquier carácter

192.168

Coincide con cualquier carácter individual. Para buscar un punto literal, como en una dirección IP, debe escaparse: 192\.168.

*

Cero o más repeticiones

[[:blank:]]*

Repite el elemento anterior cualquier número de veces, incluida ninguna.

+

Una o más repeticiones

[0-9]+

En ERE y PCRE exige al menos una aparición del elemento anterior.

?

Cero o una repetición

https?

En ERE y PCRE convierte en opcional el elemento anterior.

{n,m}

Repetición acotada

[0-9]{1,3}

En ERE exige entre n y m repeticiones. En BRE se escribe \{1,3\}.

[ ]

Clase de caracteres

[0-9a-f]

Coincide con un único carácter de la lista o del intervalo.

[^ ]

Clase negada

[^:]*

Coincide con cualquier carácter que no esté en la lista. Es la forma idónea de recorrer un campo delimitado por :.

( )

Agrupación

(ab)+

En ERE agrupa subpatrones para aplicarles un cuantificador o una alternancia.

La alternancia se expresa en ERE y PCRE con la barra vertical, que actúa como disyunción entre subpatrones: (error|fail) coincide con cualquiera de las dos palabras. En BRE, GNU admite la forma escapada error\|fail.

1.3. Clases POSIX, límites de palabra y configuración regional

Los intervalos como [a-z] dependen de la configuración regional activa, o locale, y, en algunas de ellas, pueden incluir letras acentuadas o mayúsculas. Para que una auditoría produzca el mismo resultado en cualquier servidor se recomiendan las clases POSIX:

Clase Equivale a Uso típico en bastionado

[[:digit:]]

Dígitos 0-9

UID, GID, puertos, contadores

[[:alpha:]]

Letras

Nombres de directivas

[[:alnum:]]

Letras y dígitos

Identificadores y nombres de cuenta

[[:blank:]]

Espacio y tabulador

Separación entre directiva y valor

[[:space:]]

Espacio, tabulador, saltos de línea y retorno de carro

Detección de líneas en blanco con \r

[[:xdigit:]]

Dígitos hexadecimales

Huellas SHA-256, direcciones MAC

Para buscar palabras completas, GNU proporciona las anclas \< y \>, así como \b, o bien la opción -w de grep. De este modo, buscar su no coincide con sudo ni con sshd_config.

Anteponer LC_ALL=C a grep, sort o uniq fuerza la comparación byte a byte, lo que hace los resultados reproducibles entre sistemas y acelera notablemente el análisis de ficheros de registro grandes.

2. Sintaxis y opciones esenciales de grep

El nombre grep procede del comando g/re/p del editor ed: aplicar globalmente una expresión regular e imprimir las líneas que coinciden. La herramienta examina la entrada línea a línea y escribe en la salida estándar las que satisfacen el patrón.

grep [opciones] 'patrón' [fichero...]
grep [opciones] -e 'patrón1' -e 'patrón2' [fichero...]
grep [opciones] -f fichero_de_patrones [fichero...]

Si no se indica ningún fichero, grep lee de la entrada estándar, lo que permite usarlo como filtro dentro de una tubería.

2.1. Opciones clave

Categoría Opción Efecto Uso en bastionado

Motor

-E

Usa ERE

Patrones con alternancias o cuantificadores

Motor

-F

Cadena literal, sin interpretar metacaracteres

Búsqueda de listas de indicadores de compromiso, como direcciones o hashes, con -f

Motor

-P

Usa PCRE

Extracción precisa con \K o aserciones

Coincidencia

-i

Ignora mayúsculas y minúsculas

Directivas de sshd_config, que no distinguen mayúsculas

Coincidencia

-w

Solo palabras completas

Evitar que root coincida con chroot

Coincidencia

-x

La línea completa debe coincidir

Validar el contenido exacto de una línea

Coincidencia

-v

Invierte la selección

Descartar comentarios y ruido

Salida

-n

Muestra el número de línea

Localizar la directiva para corregirla

Salida

-c

Cuenta las líneas coincidentes

Métricas rápidas de eventos

Salida

-o

Muestra solo el fragmento coincidente

Extraer direcciones IP, usuarios o rutas

Salida

-l / -L

Lista los ficheros con o sin coincidencias

Inventario de ficheros que contienen o que carecen de un control

Salida

-h / -H

Oculta o fuerza el nombre del fichero

Formatear la salida para tuberías

Salida

-q

Silencioso, solo devuelve el código de salida

Condiciones en scripts de auditoría

Salida

-s

Suprime errores de ficheros inexistentes o ilegibles

Rutas con comodines que pueden no existir

Recursión

-r / -R

Búsqueda recursiva; -R sigue además todos los enlaces simbólicos

Barrido de /etc o de directorios de usuario

Recursión

--include / --exclude

Filtra por nombre de fichero

Limitar el barrido a *.conf o excluir *.bak

Recursión

-I

Ignora ficheros binarios

Evitar falsos positivos en ejecutables

Contexto

-A n / -B n / -C n

Muestra n líneas posteriores, anteriores o ambas

Reconstruir la secuencia de un incidente

Las órdenes egrep y fgrep están obsoletas. Desde GNU grep 3.8 emiten un aviso en cada ejecución, por lo que en scripts nuevos deben sustituirse por grep -E y grep -F. La variable de entorno GREP_OPTIONS tampoco tiene ya ningún efecto.

2.2. Código de salida y uso en scripts

grep comunica su resultado mediante el código de salida, que es la base de cualquier comprobación automatizada:

Código Significado

0

Se ha encontrado al menos una coincidencia

1

No se ha encontrado ninguna coincidencia

2

Se ha producido un error, como un fichero inexistente o sin permisos de lectura

if sudo grep -qE '^[^:]+::' /etc/shadow; then
    echo "NO CONFORME: existen cuentas sin contraseña"
else
    echo "CONFORME"
fi

En un script de auditoría, el código 2 no debe interpretarse como «conforme». Si el auditor no tiene permisos para leer /etc/shadow, grep -q devuelve 2 y una comprobación mal escrita concluiría erróneamente que no hay cuentas sin contraseña. Ejecute las auditorías con los privilegios necesarios y trate el código 2 como un resultado indeterminado.

3. Inspección y auditoría de configuraciones

3.1. Extracción de la configuración activa

Los ficheros de configuración contienen numerosas líneas comentadas y en blanco. Para revisar únicamente las directivas operativas:

grep -vE '^[[:space:]]*(#|;|$)' /etc/ssh/sshd_config
  • ^[[:space:]]* admite espacios, tabuladores o un retorno de carro al principio de la línea.

  • (#|;|$) coincide con el inicio de un comentario o con el final de una línea vacía. El carácter ; cubre ficheros de estilo INI, como php.ini.

  • -v invierte la selección y conserva solo las líneas que no son comentarios ni líneas vacías.

3.2. Auditoría de directivas de OpenSSH

Una primera aproximación consiste en buscar las directivas en el fichero principal y en los ficheros incluidos:

sudo grep -HinsE '^[[:blank:]]*(PermitRootLogin|PasswordAuthentication|PermitEmptyPasswords)[[:blank:]]' \
    /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf

Sin embargo, este método no es concluyente. En OpenSSH, para la mayoría de directivas, prevalece el primer valor leído, y en Debian y Ubuntu la línea Include /etc/ssh/sshd_config.d/*.conf aparece al principio de sshd_config. Un fichero como 50-cloud-init.conf, que crean muchas imágenes de nube con PasswordAuthentication yes, anula cualquier valor posterior del fichero principal aunque grep muestre no en él.

La verificación fiable, y la que utiliza el propio CIS Benchmark, consulta la configuración efectiva que calcula el servicio:

sudo sshd -T | grep -iE '^(permitrootlogin|passwordauthentication|permitemptypasswords) '

Fichero de configuración frente a configuración efectiva

grep sobre un fichero demuestra lo que está escrito, no lo que el servicio aplica. Siempre que el software ofrezca un modo de volcado de su configuración efectiva, como sshd -T, nginx -T, sysctl -a o postconf -n, la auditoría debe basarse en él. Si existen bloques Match, la evaluación debe repetirse para el contexto concreto con sshd -T -C user=usuario,host=equipo,addr=ip.

El CIS Benchmark exige PermitRootLogin no y PermitEmptyPasswords no. Desactivar PasswordAuthentication para admitir solo claves públicas es una medida de bastionado habitual, más estricta que el control equivalente del CIS, que debe aplicarse únicamente después de haber desplegado y probado las claves de los administradores.

3.3. Detección de claves privadas expuestas

El almacenamiento descuidado de claves privadas en directorios temporales, copias de seguridad o repositorios es una vía frecuente de acceso inicial y movimiento lateral, recogida en MITRE ATT&CK como Unsecured Credentials: Private Keys (T1552.004). La siguiente orden inventaría los ficheros que contienen una clave privada en formato PEM u OpenSSH:

sudo grep -rlIE -- '-----BEGIN ([A-Z]+ )*PRIVATE KEY( BLOCK)?-----' /home /root /tmp /var/tmp /srv /opt 2>/dev/null
  • -l devuelve solo la ruta de cada fichero, sin volcar material sensible en la terminal ni en el historial de la sesión.

  • -I descarta ficheros binarios.

  • -- indica el final de las opciones, imprescindible porque el patrón empieza por un guion.

  • El patrón cubre las cabeceras RSA, EC, DSA, OPENSSH, ENCRYPTED y PGP.

Cada resultado debe revisarse: comprobar sus permisos con stat, confirmar si la clave está cifrada con frase de paso y, si estuvo expuesta, revocarla y sustituirla.

Con los conceptos explicados en esta sección, ya puede realizar el Ejercicio 3. Auditoría de configuraciones y credenciales.

4. Análisis de registros y detección de anomalías

4.1. Fuentes de registro de autenticación

La ubicación de los eventos de autenticación depende de la distribución:

  • Ubuntu y Debian con rsyslog: /var/log/auth.log, con rotaciones en auth.log.1 y auth.log.2.gz, entre otras.

  • RHEL, Rocky Linux y Fedora: /var/log/secure.

  • Debian 12 y posteriores en instalación mínima: no instalan rsyslog, de modo que los eventos solo están en el diario de systemd-journald y se consultan con journalctl.

# Equivalente a buscar en auth.log usando el diario de systemd
sudo journalctl -u ssh --since today --no-pager | grep 'Failed password'

# journalctl también puede filtrar por expresión regular con -g
sudo journalctl -u ssh --since '-1h' -g 'Failed password|Invalid user'

A partir de OpenSSH 9.8 la autenticación se gestiona en un proceso independiente, de modo que las líneas de registro aparecen con el identificador sshd-session en lugar de sshd. Los filtros basados en el texto del mensaje, como Failed password, o en la unidad de systemd, como -u ssh, siguen funcionando; los que filtran por el nombre del proceso deben actualizarse.

4.2. Anatomía de un evento de autenticación

Oct 18 08:01:20 srv01 sshd[1203]: Failed password for invalid user admin from 198.51.100.23 port 40022 ssh2      (1) (2) (3) (4) (5) (6)
1 Marca temporal. En Ubuntu 24.04 y otras distribuciones recientes se registra en formato ISO 8601, como 2026-10-18T08:01:20.512345+02:00.
2 Nombre del equipo que genera el evento.
3 Proceso emisor y su PID.
4 Tipo de evento: autenticación fallida por contraseña.
5 Usuario objetivo. La cadena invalid user indica que la cuenta no existe, un indicio de enumeración de usuarios.
6 Dirección IP de origen de la conexión.

4.3. Extracción de direcciones IP

La opción -o permite extraer solo la parte coincidente de cada línea:

sudo grep 'Failed password' /var/log/auth.log | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}'

Este patrón es suficiente para extraer direcciones de un registro fiable, pero también aceptaría valores imposibles como 999.999.999.999 y cualquier otra secuencia numérica similar que aparezca en el mensaje. Con PCRE se puede anclar la extracción al contexto exacto y obtener tanto direcciones IPv4 como IPv6:

sudo grep -oP 'Failed password for .* from \K[0-9a-fA-F:.]+' /var/log/auth.log

\K hace que la parte anterior del patrón actúe solo como contexto y no forme parte del resultado.

4.4. Registros rotados y comprimidos

Los registros antiguos se comprimen con gzip al rotarse. zgrep acepta las mismas opciones que grep y procesa indistintamente ficheros comprimidos y sin comprimir:

sudo zgrep -h 'Failed password' /var/log/auth.log*

4.5. Detección de patrones de ataque web

Los registros de acceso de Apache y Nginx almacenan la petición tal y como la envió el cliente, por lo que es necesario contemplar también las variantes codificadas en URL:

  • Recorrido de directorios o path traversal:

    sudo grep -iE '(\.\./|\.\.\\|%2e%2e(%2f|%5c|/))' /var/log/nginx/access.log
  • Sondeo de ficheros sensibles y paneles de administración:

    sudo grep -iE '"(GET|POST|HEAD) [^"]*(phpmyadmin|wp-login\.php|/\.env|/\.git/)' /var/log/nginx/access.log
  • Herramientas automatizadas identificadas por su agente de usuario:

    sudo grep -iE '(sqlmap|nikto|nmap|zgrab|masscan|gobuster)' /var/log/nginx/access.log

Estas búsquedas son indicadores, no pruebas de compromiso. Un código de respuesta 404 o 400 suele indicar que el intento falló, mientras que un 200 ante una petición sospechosa exige investigación inmediata. Además, un atacante puede falsear su agente de usuario con facilidad.

Con los conceptos explicados en esta sección, ya puede realizar el Ejercicio 4. Análisis forense de registros.

5. Recetario de grep para el bastionado continuo

  1. Cuentas con UID 0 distintas de root:

    grep -E '^[^:]+:[^:]*:0:' /etc/passwd | grep -v '^root:'

    El patrón ancla el 0 al tercer campo. Una búsqueda ingenua de :0: en toda la línea produce falsos positivos con cuentas cuyo GID es 0.

  2. Directivas de caducidad de contraseñas y algoritmo de hash en /etc/login.defs:

    grep -E '^[[:blank:]]*(PASS_MAX_DAYS|PASS_MIN_DAYS|PASS_WARN_AGE|ENCRYPT_METHOD|UMASK)[[:blank:]]' /etc/login.defs
  3. Cuentas sin contraseña:

    sudo grep -E '^[^:]+::' /etc/shadow
  4. Módulos PAM que admiten contraseñas vacías:

    grep -rnwE '^[^#]*nullok' /etc/pam.d/
  5. Reglas de sudo sin solicitud de contraseña:

    sudo grep -rnE '^[^#]*NOPASSWD' /etc/sudoers /etc/sudoers.d/
  6. Obtención de un intérprete de órdenes interactivo a través de sudo:

    sudo grep -E 'COMMAND=(/usr)?/bin/((ba|da|z|k)?sh|su)( |$)' /var/log/auth.log

    Un patrón genérico como COMMAND=.*sh produciría falsos positivos con ssh, ssh-keygen o cualquier script .sh.

  7. Búsqueda de indicadores de compromiso (IOC) a partir de una lista:

    sudo grep -hFwf ioc_ips.txt /var/log/auth.log /var/log/nginx/access.log

    Con -F y -f, grep busca simultáneamente todas las cadenas literales del fichero ioc_ips.txt, una por línea, de forma mucho más eficiente que con una alternancia. La opción -w evita que 10.0.0.1 coincida con 10.0.0.15.

Con el recetario anterior, ya puede realizar el Ejercicio 5. Script de comprobación de controles.

6. Ejercicios prácticos

Los ejercicios deben realizarse en orden sobre una máquina virtual Debian o Ubuntu de pruebas. El último ejercicio elimina el laboratorio.

Nº Ejercicio Competencia que desarrolla

1

Preparación del laboratorio y primeras búsquedas

Usar las opciones básicas de grep sobre ficheros reales

2

Precisión de los patrones y falsos positivos

Anclar patrones a campos concretos y evitar coincidencias espurias

3

Auditoría de configuraciones y credenciales

Distinguir entre configuración escrita y configuración efectiva

4

Análisis forense de registros

Extraer indicadores de ataque de registros de autenticación y web

5

Script de comprobación de controles

Automatizar verificaciones mediante códigos de salida

6

Limpieza del entorno

Revertir el laboratorio

6.1. Ejercicio 1. Preparación del laboratorio y primeras búsquedas

Realice los siguientes pasos:

  1. Cree la estructura del laboratorio y una copia de /etc/passwd a la que se añaden dos cuentas: una puerta trasera con UID 0 y una cuenta legítima cuyo GID es 0.

    mkdir -p ~/lab_filtrado/grep/{logs,ssh/sshd_config.d,tmp}
    cd ~/lab_filtrado/grep
    cp /etc/passwd passwd
    echo 'soporte:x:0:0:Soporte tecnico:/root:/bin/bash' >> passwd
    echo 'backup2:x:1501:0:Copias:/var/backups:/usr/sbin/nologin' >> passwd
  2. Cree el registro de autenticación de muestra logs/auth.log:

    cat > logs/auth.log << 'EOF'
    Oct 18 08:00:01 srv01 CRON[1101]: pam_unix(cron:session): session opened for user root(uid=0) by (uid=0)
    Oct 18 08:01:12 srv01 sshd[1201]: Failed password for root from 203.0.113.45 port 51234 ssh2
    Oct 18 08:01:15 srv01 sshd[1201]: Failed password for root from 203.0.113.45 port 51234 ssh2
    Oct 18 08:01:19 srv01 sshd[1201]: Failed password for root from 203.0.113.45 port 51234 ssh2
    Oct 18 08:01:20 srv01 sshd[1203]: Failed password for invalid user admin from 198.51.100.23 port 40022 ssh2
    Oct 18 08:01:24 srv01 sshd[1203]: Failed password for invalid user admin from 198.51.100.23 port 40022 ssh2
    Oct 18 08:01:30 srv01 sshd[1207]: Invalid user oracle from 198.51.100.23 port 40030
    Oct 18 08:01:31 srv01 sshd[1207]: Failed password for invalid user oracle from 198.51.100.23 port 40030 ssh2
    Oct 18 08:01:40 srv01 sshd[1209]: Failed password for operador from 192.0.2.10 port 50098 ssh2
    Oct 18 08:02:01 srv01 sshd[1210]: Accepted password for operador from 192.0.2.10 port 50100 ssh2
    Oct 18 08:03:44 srv01 sudo: operador : TTY=pts/0 ; PWD=/home/operador ; USER=root ; COMMAND=/usr/bin/apt update
    Oct 18 08:04:10 srv01 sudo: operador : TTY=pts/0 ; PWD=/home/operador ; USER=root ; COMMAND=/bin/bash
    Oct 18 08:04:30 srv01 sudo: operador : TTY=pts/0 ; PWD=/home/operador ; USER=root ; COMMAND=/usr/bin/ssh-keygen -A
    Oct 18 08:05:02 srv01 sshd[1250]: Failed password for root from 203.0.113.45 port 51302 ssh2
    Oct 18 08:05:09 srv01 sshd[1250]: Failed password for root from 203.0.113.45 port 51302 ssh2
    Oct 18 08:07:12 srv01 sshd[1290]: Accepted password for root from 203.0.113.45 port 51400 ssh2
    EOF
  3. Muestre las líneas de logs/auth.log que contienen Failed password, con su número de línea, y después solo su recuento.

  4. Muestre las cuentas de passwd cuyo intérprete es /bin/bash. Repita la búsqueda mostrando únicamente las líneas que no terminan en nologin ni en false.

  5. Muestre las dos líneas anteriores y la línea posterior al evento Accepted password for root. Razone qué sugiere esa secuencia.


6.2. Ejercicio 2. Precisión de los patrones y falsos positivos

Realice los siguientes pasos:

  1. Ejecute la búsqueda ingenua grep ':0:' passwd y compárela con un patrón anclado al tercer campo. Explique por qué aparece backup2 en el primer caso.

  2. Busque en logs/auth.log los comandos ejecutados con sudo que contienen sh con el patrón COMMAND=.*sh y, a continuación, con el patrón preciso del recetario. Identifique el falso positivo.

  3. Compruebe la diferencia entre grep root logs/auth.log, grep -w root logs/auth.log y grep -c de ambos. ¿Por qué coinciden en este caso? Construya una línea de prueba con chroot que demuestre la diferencia.


6.3. Ejercicio 3. Auditoría de configuraciones y credenciales

Realice los siguientes pasos:

  1. Cree una configuración de SSH de muestra que reproduce el comportamiento de una imagen de nube:

    cat > ssh/sshd_config << 'EOF'
    Include /etc/ssh/sshd_config.d/*.conf
    #PermitRootLogin prohibit-password
    PermitRootLogin no
        PasswordAuthentication no
    #PubkeyAuthentication yes
    
    X11Forwarding yes
    PermitEmptyPasswords no
    EOF
    echo 'PasswordAuthentication yes' > ssh/sshd_config.d/50-cloud-init.conf
  2. Muestre solo las directivas activas de ssh/sshd_config. Compruebe que la línea PasswordAuthentication, que está sangrada con espacios, aparece en el resultado aunque no comience en la primera columna.

  3. Busque PasswordAuthentication en todos los ficheros de ssh/ de forma recursiva e indicando el fichero de cada coincidencia. Teniendo en cuenta la regla del primer valor leído y la posición de la directiva Include, determine qué valor aplicaría el servicio.

  4. Compruebe en su propio sistema la configuración efectiva de sshd para permitrootlogin, passwordauthentication y permitemptypasswords. Si el servidor SSH no está instalado, instálelo con sudo apt install openssh-server.

  5. Genere una clave de prueba en tmp/ y localícela con la búsqueda recursiva de claves privadas, limitada al directorio del laboratorio.

    ssh-keygen -q -t ed25519 -N '' -C 'clave de laboratorio' -f tmp/id_prueba

6.4. Ejercicio 4. Análisis forense de registros

Realice los siguientes pasos:

  1. Cree el registro de acceso web de muestra logs/access.log:

    cat > logs/access.log << 'EOF'
    192.0.2.10 - - [18/Oct/2026:08:10:01 +0200] "GET /index.html HTTP/1.1" 200 5120 "-" "Mozilla/5.0"
    203.0.113.77 - - [18/Oct/2026:08:10:05 +0200] "GET /../../etc/passwd HTTP/1.1" 400 157 "-" "curl/8.5.0"
    203.0.113.77 - - [18/Oct/2026:08:10:06 +0200] "GET /static/%2e%2e/%2e%2e/etc/shadow HTTP/1.1" 404 153 "-" "curl/8.5.0"
    198.51.100.99 - - [18/Oct/2026:08:11:00 +0200] "GET /.env HTTP/1.1" 404 153 "-" "Mozilla/5.0 zgrab/0.x"
    198.51.100.99 - - [18/Oct/2026:08:11:01 +0200] "GET /.git/config HTTP/1.1" 404 153 "-" "Mozilla/5.0 zgrab/0.x"
    198.51.100.99 - - [18/Oct/2026:08:11:02 +0200] "GET /wp-login.php HTTP/1.1" 404 153 "-" "Mozilla/5.0 zgrab/0.x"
    192.0.2.55 - - [18/Oct/2026:08:12:00 +0200] "GET /buscar?q=1%27%20OR%20%271%27=%271 HTTP/1.1" 200 812 "-" "sqlmap/1.8"
    EOF
  2. Extraiga de logs/auth.log las direcciones IP de origen de los intentos fallidos con un patrón ERE y, después, con PCRE y \K.

  3. Extraiga los nombres de usuario inexistentes que se han intentado utilizar.

  4. Comprima una copia del registro con gzip -k logs/auth.log y busque en ambos ficheros a la vez los accesos aceptados, ocultando el nombre del fichero.

  5. Detecte en logs/access.log los intentos de recorrido de directorios, incluida la variante codificada, y las peticiones de herramientas automatizadas. Indique cuál de las peticiones requiere investigación prioritaria y por qué.


6.5. Ejercicio 5. Script de comprobación de controles

Desarrolle el script ~/lab_filtrado/grep/auditoria_grep.sh, que recibe como argumento el directorio del laboratorio y muestra CONFORME o NO CONFORME para cada uno de estos controles:

  • No existe ninguna cuenta distinta de root con UID 0 en passwd.

  • ssh/sshd_config contiene PermitRootLogin no como directiva activa.

  • Ningún fichero de ssh/sshd_config.d/ activa PasswordAuthentication yes.

  • No existe ninguna clave privada bajo tmp/.

El script debe usar exclusivamente grep -q y códigos de salida en las condiciones, y debe tratar el código 2 como resultado indeterminado.


6.6. Ejercicio 6. Limpieza del entorno

Realice los siguientes pasos:

  1. Elimine el directorio del laboratorio, que incluye la clave privada de prueba, y compruebe que no queda ningún resto.

  2. Si instaló openssh-server solo para el ejercicio 3, desinstálelo con sudo apt purge openssh-server.

7. Recursos y referencias

8. Licencia

Licencia CC BY-NC-ND 4.0

Esta página forma parte del curso Bastionado de Redes y Sistemas © 2026 de José Juan Sánchez Hernández y su contenido se distribuye bajo una licencia Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International.

Esta licencia exige que quienes reutilicen el material otorguen el debido crédito al autor. Permite copiar y redistribuir el material en cualquier medio o formato, únicamente en su forma original, y solo para fines no comerciales.