1. El editor de flujo sed

La aplicación de una línea base de seguridad, como el CIS Benchmark, sobre decenas o cientos de servidores no puede depender de editores interactivos. La utilidad sed (Stream Editor) transforma texto de forma programática y desatendida, lo que la convierte en una pieza habitual de:

  1. Corrección automática de configuraciones: Corrección de parámetros inseguros en scripts de aprovisionamiento, imágenes de máquinas virtuales o contenedores.

  2. Normalización de configuraciones: Homogeneización de directivas en una flota de servidores y eliminación de valores por defecto inseguros.

  3. Tratamiento de datos en tuberías: Reestructuración de la salida de herramientas de auditoría y seudonimización de registros antes de enviarlos a un SIEM o compartirlos con terceros.

En entornos con gestión de la configuración, como Ansible, los módulos lineinfile, replace o template sustituyen a sed con ventajas de idempotencia e informes de cambios. Aun así, entender sed es imprescindible para leer scripts de remediación existentes, como los que generan OpenSCAP o los propios anexos del CIS Benchmark, y para intervenir en sistemas donde no hay otra herramienta disponible.

1.1. Arquitectura interna y ciclo de ejecución

sed no carga el fichero completo en memoria. Procesa la entrada línea a línea mediante dos áreas de trabajo:

  • Espacio de patrones o pattern space: Contiene la línea actual. Los comandos del programa se aplican sobre él.

  • Espacio de retención o hold space: Área auxiliar que conserva datos entre ciclos y permite operaciones que afectan a varias líneas, mediante los comandos h, H, g, G y x.

En cada ciclo, sed lee una línea sin su salto de línea final, ejecuta todos los comandos cuya dirección coincide y, salvo que se haya indicado -n, imprime el espacio de patrones antes de pasar a la línea siguiente.

Ciclo de ejecución de sed: espacio de patrones y espacio de retención
Figura 1. Ciclo de ejecución de sed: espacio de patrones y espacio de retención

2. Sintaxis, opciones y sustitución

sed [opciones] 'programa' [fichero...]
sed [opciones] -e 'comando1' -e 'comando2' [fichero...]
sed [opciones] -f programa.sed [fichero...]
Opción Efecto Observaciones de seguridad

-n

Suprime la impresión automática

Se combina con p para mostrar solo lo seleccionado

-E

Usa expresiones regulares extendidas

Mismo dialecto que grep -E

-i[SUFIJO]

Edita el fichero en su lugar, con copia de seguridad opcional

Ver la sección 4 antes de usarla en producción

-s

Trata cada fichero como un flujo independiente

Sin ella, $ y los números de línea se refieren al flujo concatenado

-z

Separa los registros por el carácter nulo

Procesa la salida de find -print0

--debug

Muestra el programa interpretado y el estado de cada ciclo

Disponible desde GNU sed 4.6

2.1. El comando de sustitución s

s/patrón/reemplazo/modificadores
Modificador Efecto

g

Sustituye todas las coincidencias de la línea, no solo la primera

N

Sustituye solo la coincidencia número N; 2g sustituye desde la segunda en adelante

p

Imprime la línea si hubo sustitución; se usa con -n

I o i

Ignora mayúsculas y minúsculas; extensión de GNU

w fichero

Escribe en fichero las líneas modificadas, útil como registro de cambios

En el texto de reemplazo, & representa toda la coincidencia y \1 a \9 los grupos capturados. GNU añade \U y \L para convertir a mayúsculas o minúsculas:

# Seudonimizar los dos últimos octetos de las direcciones IPv4 antes de compartir un registro
sed -E 's/([0-9]{1,3}\.[0-9]{1,3})\.[0-9]{1,3}\.[0-9]{1,3}/\1.x.x/g' auth.log

# Normalizar a minúsculas el valor de una directiva
echo 'PermitRootLogin No' | sed -E 's/^(PermitRootLogin[[:blank:]]+)(.*)/\1\L\2/'

La seudonimización de direcciones IP antes de compartir registros con proveedores o en informes forma parte de las medidas de minimización de datos que exige el RGPD, ya que una dirección IP puede constituir un dato personal.

2.2. Delimitadores alternativos

Cuando el patrón o el reemplazo contienen /, como ocurre con rutas, cualquier carácter que siga a la s puede actuar como delimitador, lo que evita escapar cada barra:

# Ambas órdenes son equivalentes; la segunda es mucho más legible
sed 's/\/usr\/local\/bin/\/opt\/bin/' rutas.txt
sed 's#/usr/local/bin#/opt/bin#' rutas.txt

3. Direccionamiento y comandos

Sin dirección, un comando se aplica a todas las líneas. Una dirección limita su ámbito:

Dirección Selecciona Ejemplo

N

La línea N

sed -n '1p'

$

La última línea

sed -n '$p'

/re/

Las líneas que coinciden con la expresión

sed -n '/^Match /p'

A,B

Desde A hasta B, ambas incluidas; pueden ser números o expresiones

sed -n '/^Match /,$p'

A,+N

La línea A y las N siguientes; extensión de GNU

sed -n '/Failed/,+2p'

0,/re/

Desde el principio hasta la primera coincidencia; extensión de GNU

sed '0,/^#Port/s//Port/'

dirección!

Todas las líneas que no cumplen la dirección

sed '/^#/!s/yes/no/'

Comandos de edición más habituales:

Comando Efecto

p

Imprime el espacio de patrones

d

Elimina la línea y empieza el ciclo siguiente

q / q N

Termina tras imprimir la línea actual; GNU permite devolver el código de salida N

a texto

Añade una línea después de la actual

i texto

Inserta una línea antes de la actual

c texto

Sustituye la línea completa

l

Muestra la línea con los caracteres no imprimibles visibles, como \r o \t

y/abc/xyz/

Transliteración carácter a carácter, equivalente a tr

# Mostrar el bloque Match y todo lo que le sigue en la configuración de SSH
sed -n '/^Match /,$p' /etc/ssh/sshd_config

# Detectar retornos de carro de Windows en un script
sed -n '1,3l' script.sh

# Insertar una cabecera de trazabilidad antes de la primera línea
sed '1i # Gestionado por el script de bastionado. No editar manualmente.' fichero.conf

La sintaxis en una sola línea de a, i y c es una extensión de GNU. POSIX exige la forma a\ seguida de un salto de línea, que es la que debe usarse en scripts que deban funcionar también en BSD o macOS.

4. Edición in situ y buenas prácticas

-i sobrescribe el fichero original: es una operación destructiva y, sin copia de seguridad, irreversible.

4.1. Funcionamiento real de -i

GNU sed no modifica el fichero existente, sino que escribe el resultado en un fichero temporal del mismo directorio y lo renombra sobre el original. Este comportamiento tiene consecuencias que el auditor debe conocer:

  • El fichero obtiene un nuevo inodo. Se rompen los enlaces duros, y los procesos o contenedores que tienen montado el fichero individualmente siguen viendo la versión antigua.

  • Los enlaces simbólicos se sustituyen por ficheros regulares, salvo que se use --follow-symlinks.

  • Se necesita permiso de escritura en el directorio, no solo en el fichero.

  • Los ficheros con el atributo inmutable activado con chattr +i no pueden reemplazarse y la operación falla, lo que es precisamente el objetivo de ese atributo.

  • GNU sed intenta conservar el propietario, los permisos, las ACL y el contexto SELinux. Aun así, debe comprobarse con stat tras cada remediación, porque un fichero como /etc/shadow con permisos alterados es en sí mismo un incumplimiento.

4.2. Copias de seguridad

sudo sed -i.bak -E 's/^[[:blank:]]*PASS_MAX_DAYS[[:blank:]].*/PASS_MAX_DAYS\t365/' /etc/login.defs
diff /etc/login.defs.bak /etc/login.defs

Errores frecuentes con -i

  • sed -iE '…​' no activa ERE: GNU interpreta la E como sufijo de copia de seguridad, crea ficheroE y procesa el patrón como BRE. Escriba siempre -i -E o -E -i.bak.

  • En BSD y macOS, -i exige un argumento: sed -i '' 's/a/b/' fichero.

  • No deje copias de seguridad con una extensión que el servicio cargue. Una copia antiguo.conf dentro de /etc/ssh/sshd_config.d/ se lee como configuración porque coincide con *.conf. El sufijo .bak evita ese problema.

4.3. Ficheros que nunca deben editarse con sed

No modifique con sed -i los ficheros /etc/passwd, /etc/shadow, /etc/group, /etc/gshadow ni /etc/sudoers. sed no respeta los bloqueos que usan useradd, passwd o vipw, no comprueba la coherencia entre ficheros y un error en el patrón puede dejar el sistema sin cuentas válidas o sin acceso administrativo. Utilice las herramientas específicas: usermod -s /usr/sbin/nologin usuario para cambiar un intérprete, chage para la caducidad, vipw y vigr para ediciones manuales, y visudo o ficheros en /etc/sudoers.d/ validados con visudo -cf.

4.4. Validación antes de aplicar

Toda remediación sobre un servicio debe validarse antes de recargarlo, para no provocar una denegación de servicio ni perder el acceso remoto:

sudo sshd -t && sudo systemctl reload ssh
sudo nginx -t && sudo systemctl reload nginx
sudo visudo -c

Con los conceptos explicados en esta sección, ya puede realizar el Ejercicio 3. Edición in situ, inodos y copias de seguridad.

5. Remediación idempotente

Un script de remediación es idempotente cuando ejecutarlo una o varias veces produce el mismo resultado. Una sustitución simple no lo es por completo: si la directiva no existe en el fichero, sed no hace nada y el control queda sin aplicar. El patrón profesional consiste en sustituir si existe y añadir si no existe:

# fijar_directiva CLAVE VALOR FICHERO
fijar_directiva() {
    local clave="$1" valor="$2" fichero="$3"
    if grep -qE "^[[:blank:]]*${clave}[[:blank:]]" "$fichero"; then
        sudo sed -i.bak -E "s|^[[:blank:]]*${clave}[[:blank:]].*|${clave} ${valor}|" "$fichero"
    else
        echo "${clave} ${valor}" | sudo tee -a "$fichero" > /dev/null
    fi
    grep -E "^${clave}[[:blank:]]" "$fichero"
}

fijar_directiva PASS_MAX_DAYS 365 /etc/login.defs
fijar_directiva UMASK 027 /etc/login.defs

La función solo modifica directivas activas: no descomenta líneas, porque en ficheros como login.defs muchos comentarios explicativos empiezan por el nombre de la directiva y acabarían convertidos en directivas duplicadas. Termina mostrando la línea resultante, que actúa como verificación inmediata. Los valores no se escapan, por lo que solo deben provenir del propio script y nunca de datos externos.

5.1. El caso de OpenSSH: ficheros incluidos y precedencia

En OpenSSH prevalece el primer valor leído, y en Debian y Ubuntu los ficheros de /etc/ssh/sshd_config.d/ se leen antes que el resto de sshd_config. Por ello, modificar con sed el fichero principal puede no tener ningún efecto. La estrategia robusta es:

  1. Crear un fichero propio que se lea en primer lugar por su orden alfabético.

  2. Neutralizar con sed las directivas contradictorias de otros ficheros incluidos.

  3. Validar la sintaxis, recargar el servicio y verificar la configuración efectiva.

printf '%s\n' 'PermitRootLogin no' 'PermitEmptyPasswords no' 'MaxAuthTries 4' \
    | sudo tee /etc/ssh/sshd_config.d/00-bastionado.conf > /dev/null

sudo sed -i.bak -E 's/^([[:blank:]]*PasswordAuthentication[[:blank:]]+yes)/# \1/I' \
    /etc/ssh/sshd_config.d/50-cloud-init.conf

sudo sshd -t && sudo systemctl reload ssh
sudo sshd -T | grep -E '^(permitrootlogin|permitemptypasswords|maxauthtries|passwordauthentication) '

Antes de desactivar PasswordAuthentication en un servidor remoto, confirme que el acceso por clave pública funciona y mantenga abierta una segunda sesión hasta comprobar que puede iniciar una sesión nueva. Un error en este paso deja el servidor inaccesible.

Los cambios en /etc/login.defs solo afectan a las cuentas que se creen después. Para aplicar la caducidad a las cuentas existentes se utiliza chage, por ejemplo sudo chage --maxdays 365 usuario, y se verifica con chage -l usuario.

Con los conceptos explicados en esta sección, ya puede realizar el Ejercicio 4. Remediación idempotente.

6. Recetario de sed para el bastionado

  1. Configuración activa sin comentarios ni líneas vacías, sin modificar el original:

    sed -E '/^[[:space:]]*(#|;|$)/d' /etc/ssh/sshd_config
  2. Caducidad máxima de las contraseñas en /etc/login.defs: El CIS Benchmark para Ubuntu exige un valor de 365 días o inferior. Muchas políticas corporativas fijan 90, aunque la guía NIST SP 800-63B desaconseja los cambios periódicos obligatorios si no hay indicios de compromiso.

    sudo sed -i.bak -E 's/^[[:blank:]]*PASS_MAX_DAYS[[:blank:]].*/PASS_MAX_DAYS\t365/' /etc/login.defs
  3. Máscara de creación de ficheros UMASK 027: Las cuentas nuevas crearán ficheros sin permisos para el resto de usuarios y sin escritura para el grupo, como exige el CIS Benchmark.

    sudo sed -i.bak -E 's/^[[:blank:]]*UMASK[[:blank:]].*/UMASK\t\t027/' /etc/login.defs
  4. Comentar una directiva insegura dejando constancia del cambio:

    sudo sed -i.bak -E 's/^([[:blank:]]*X11Forwarding[[:blank:]]+yes)/# \1  # Deshabilitado por bastionado/I' /etc/ssh/sshd_config
  5. Eliminar los retornos de carro de un script creado en Windows:

    sed -i 's/\r$//' script_bastionado.sh
  6. Extraer los eventos de un intervalo temporal de un registro:

    sudo sed -n '/^Oct 18 08:00/,/^Oct 18 09:00/p' /var/log/auth.log
  7. Comprobar un cambio antes de aplicarlo: Ejecute siempre la orden sin -i y compare el resultado con diff:

    sed -E 's/^[#[:blank:]]*PermitRootLogin[[:blank:]].*/PermitRootLogin no/' /etc/ssh/sshd_config | diff /etc/ssh/sshd_config -

7. Ejercicios prácticos

Los ejercicios deben realizarse en orden sobre una máquina virtual Debian o Ubuntu de pruebas. Todas las modificaciones se aplican sobre copias situadas en ~/lab_filtrado/sed, de modo que la configuración real del sistema no se altera. El último ejercicio elimina el laboratorio.

Nº Ejercicio Competencia que desarrolla

1

Preparación del laboratorio y selección de líneas

Usar -n, p, direcciones y rangos

2

Sustituciones y retrorreferencias

Construir sustituciones con grupos y delimitadores alternativos

3

Edición in situ, inodos y copias de seguridad

Comprender los efectos reales de -i

4

Remediación idempotente

Aplicar controles CIS de forma repetible y verificable

5

Limpieza del entorno

Revertir el laboratorio

7.1. Ejercicio 1. Preparación del laboratorio y selección de líneas

Realice los siguientes pasos:

  1. Cree el laboratorio con una copia de /etc/login.defs, una configuración de SSH de muestra y un script con finales de línea de Windows:

    mkdir -p ~/lab_filtrado/sed/sshd_config.d
    cd ~/lab_filtrado/sed
    cp /etc/login.defs login.defs
    cat > sshd_config << 'EOF'
    Include /etc/ssh/sshd_config.d/*.conf
    #Port 22
    #PermitRootLogin prohibit-password
    PermitRootLogin yes
    X11Forwarding yes
    PermitEmptyPasswords no
    
    Match User invitado
        X11Forwarding no
        PermitRootLogin yes
    EOF
    echo 'PasswordAuthentication yes' > sshd_config.d/50-cloud-init.conf
    printf '#!/bin/bash\r\necho "bastionado"\r\n' > script_windows.sh
  2. Muestre únicamente las líneas 1 a 5 de sshd_config y, después, solo la última línea.

  3. Muestre el bloque Match completo y, a continuación, todas las líneas de login.defs relacionadas con PASS_ que no estén comentadas.

  4. Utilice el comando l para demostrar que script_windows.sh contiene retornos de carro. Concédale permiso de ejecución, ejecútelo con ./script_windows.sh y explique el error.


7.2. Ejercicio 2. Sustituciones y retrorreferencias

Realice los siguientes pasos:

  1. Sin modificar el fichero, sustituya en sshd_config el valor de PermitRootLogin por no solo fuera del bloque Match. Compruebe el resultado con diff.

  2. Genere una versión seudonimizada de esta línea de registro que conserve solo los dos primeros octetos de la dirección IP:

    echo 'Failed password for root from 203.0.113.45 port 51234 ssh2' > evento.log
  3. Usando un delimitador alternativo, sustituya en la salida la ruta /etc/ssh/sshd_config.d/*.conf de la directiva Include por /etc/ssh/bastionado.d/*.conf.

  4. Ejecute el apartado 1 con --debug y observe cómo se evalúa la dirección en cada ciclo.


7.3. Ejercicio 3. Edición in situ, inodos y copias de seguridad

Realice los siguientes pasos:

  1. Anote el inodo de script_windows.sh con ls -i, elimine los retornos de carro con sed -i.bak y compruebe que el inodo ha cambiado, que existe la copia .bak y que el script ya se ejecuta correctamente con ./script_windows.sh.

  2. Cree un enlace simbólico enlace.conf que apunte a login.defs, aplique sed -i sobre el enlace sin --follow-symlinks y compruebe con ls -l en qué se ha convertido. Repita el proceso con --follow-symlinks.

  3. Ejecute sed -iE 's/(yes)/no/' sshd_config y explique qué ficheros aparecen y por qué no se ha producido ninguna sustitución. Restaure el original.


7.4. Ejercicio 4. Remediación idempotente

Realice los siguientes pasos:

  1. Defina la función fijar_directiva de la sección 5, sustituyendo sudo sed y sudo tee por sed y tee, ya que trabajará sobre copias de su propiedad. Aplique sobre login.defs los valores PASS_MAX_DAYS 365, UMASK 027 y SHA_CRYPT_MIN_ROUNDS 5000, que en Debian y Ubuntu solo aparece comentada. Compruebe que las líneas de comentario que mencionan PASS_MAX_DAYS no se han modificado.

  2. Calcule el hash SHA-256 de login.defs, ejecute de nuevo las tres llamadas y compruebe que el hash no cambia, lo que demuestra la idempotencia.

  3. Aplique sobre la copia de SSH la estrategia de la sección 5.1: cree sshd_config.d/00-bastionado.conf con PermitRootLogin no y comente con sed la directiva de 50-cloud-init.conf. Muestre después todas las directivas activas de sshd_config.d/ en el orden en que las leería el servicio.


7.5. Ejercicio 5. Limpieza del entorno

  1. Elimine el directorio del laboratorio y la función definida en la sesión.

8. Recursos y referencias

9. 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.