Saltar al contenido
sudoquest@root
Blue Team

Contener sin parar el negocio

Cortar una salida de datos que no debería estar, en el servidor y en el router, sin tirar la tienda, los pagos ni los pedidos.

35 min de lectura

Son las cinco y media de la tarde en el SOC y salta una alerta de volumen de salida: srv-bodega, el servidor de la Bodega Viña Alta, lleva horas mandando muchos megas por hora a una IP de fuera que no es de nadie de su ficha. En ese servidor está su tienda online, que cobra por una pasarela de pagos y manda los pedidos a la distribuidora.

No sabes quién hay al otro lado ni qué es lo que sale, y para contener no hace falta saberlo: lo que sale son datos que la bodega pierde, y cada minuto cuenta. Contener es cortar esa salida sin cortar el negocio. Apagar el servidor contiene, pero la tienda deja de vender; cerrar todo lo que sale por el 443 también, pero los pagos no salen. Lo que se busca es cortar justo lo que no debería salir, y comprobar que de verdad ha dejado de salir.

Desde el SOC entras en el servidor por SSH. Su ficha dice qué es de la bodega:

tecnico@srv-bodega:~$ cat ~/ficha-bodega.txt
Bodega Viña Alta · srv-bodega (192.168.88.10)
Ficha del SOC, revisada el 2 de octubre de 2026

Lo que sirve:
la tienda online, en el 443 (y el 80, que lleva al 443)

Lo que sale (todo al 443), con la cuenta tienda:
192.0.2.40      la pasarela de pagos (programa pagos)
198.51.100.20   la distribuidora, con los pedidos (programa pedidos)

Nada más tiene que salir. Si se corta algo de esto, la tienda no cobra o
los pedidos no llegan: avisar a la bodega antes de tocarlo.

Qué sale y hacia dónde

ss sin -l enseña las conexiones establecidas (con -l, lo que escucha; -t, solo TCP; -n, con números en vez de nombres):

tecnico@srv-bodega:~$ ss -tn
State  Recv-Q  Send-Q     Local Address:Port        Peer Address:Port  Process
ESTAB  0       0          192.168.88.10:33027      198.51.100.20:443
ESTAB  0       0          192.168.88.10:45886         192.0.2.40:443
ESTAB  0       0          192.168.88.10:56059     198.51.100.194:443

Tres conexiones al 443. Dos están en la ficha; la tercera, a 198.51.100.194, no (en tu partida será otra IP). Con -p, de qué programa es cada una. Son de la cuenta de la tienda, así que sin sudo, ss no lo dice:

tecnico@srv-bodega:~$ sudo ss -tnp
State    Recv-Q    Send-Q       Local Address:Port          Peer Address:Port   Process
ESTAB    0         0            192.168.88.10:33027        198.51.100.20:443     users:(("pedidos",pid=401,fd=3))
ESTAB    0         0            192.168.88.10:45886           192.0.2.40:443     users:(("pagos",pid=402,fd=3))
ESTAB    0         0            192.168.88.10:56059       198.51.100.194:443     users:(("sincroniza",pid=403,fd=3))

Y cuánto lleva: -i añade lo que sabe TCP de cada conexión, y bytes_acked es lo que ya ha llegado al otro lado. Dos veces seguidas:

tecnico@srv-bodega:~$ ss -tni dst 198.51.100.194 | grep -o 'bytes_acked:[0-9]*'
bytes_acked:2138675001
tecnico@srv-bodega:~$ ss -tni dst 198.51.100.194 | grep -o 'bytes_acked:[0-9]*'
bytes_acked:2138812501

Más de dos gigas, y sigue creciendo. ps cuenta el resto: con qué cuenta corre el programa, cuánto lleva en marcha (ELAPSED) y qué es:

tecnico@srv-bodega:~$ ps -o user,pid,etime,args -p 403
USER         PID     ELAPSED COMMAND
tienda       403    04:19:16 /usr/local/bin/sincroniza

Ejercicio

Lo que sale: ss -tnp, ss -ti y ps

srv-bodega manda mucho a una IP que no está en la ficha (~/ficha-bodega.txt). Encuentra la conexión, cuánto lleva enviado y de qué programa es.

Preparando la máquina…

Cortarla en el servidor

ufw también corta lo que sale: deny out to IP (o reject out to IP, que contesta «Connection refused» al momento en vez de dejar esperando). Las de salida salen aparte en ufw status, con DENY OUT:

tecnico@srv-bodega:~$ sudo ufw deny out to 198.51.100.194
Rule added
tecnico@srv-bodega:~$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)

198.51.100.194             DENY OUT    Anywhere

Pero mira lo que lleva enviado:

tecnico@srv-bodega:~$ ss -tni dst 198.51.100.194 | grep -o 'bytes_acked:[0-9]*'
bytes_acked:2139362501

Sigue creciendo. Una regla nueva corta las conexiones nuevas; la que ya iba, no.

Quien recuerda las conexiones establecidas es conntrack, el seguimiento de conexiones del núcleo (la herramienta viene en el paquete conntrack; en srv-bodega la puso el SOC). Su entrada:

tecnico@srv-bodega:~$ sudo conntrack -L -d 198.51.100.194
tcp      6 431999 ESTABLISHED src=192.168.88.10 dst=198.51.100.194 sport=56059 dport=443 src=198.51.100.194 dst=192.168.88.10 sport=443 dport=56059 [ASSURED] mark=0 use=1
conntrack v1.4.6 (conntrack-tools): 1 flow entries have been shown.

Si se borra, el siguiente paquete de esa conexión pasa por las reglas como si fuera nuevo, y la regla lo corta:

tecnico@srv-bodega:~$ sudo conntrack -D -d 198.51.100.194
tcp      6 431999 ESTABLISHED src=192.168.88.10 dst=198.51.100.194 sport=56059 dport=443 src=198.51.100.194 dst=192.168.88.10 sport=443 dport=56059 [ASSURED] mark=0 use=1
conntrack v1.4.6 (conntrack-tools): 1 flow entries have been deleted.
tecnico@srv-bodega:~$ ss -tn dst 198.51.100.194
State  Recv-Q  Send-Q     Local Address:Port        Peer Address:Port  Process
ESTAB  0       99912      192.168.88.10:56059     198.51.100.194:443

La conexión sigue ahí (ESTAB), pero lo que el programa manda se queda en su cola sin salir: Send-Q, llena (99912 bytes), y bytes_acked ya no crece. Otra forma es cerrarla: ss -K, como root, cierra las conexiones que casan con el filtro (el programa sigue vivo):

tecnico@srv-bodega:~$ sudo ss -K dst 198.51.100.194
Netid State Recv-Q  Send-Q   Local Address:Port      Peer Address:Port  Process
tcp   ESTAB 0       99912    192.168.88.10:56059   198.51.100.194:https

Matar el programa también la corta, pero se lleva lo que enseña (el proceso, lo que tenía abierto) a quien venga después a investigar; y si algo lo vuelve a lanzar, vuelve a salir. Lo que no vuelve a salir, con la regla puesta, es una conexión nueva hacia esa IP.

Para que nadie lo vuelva a lanzar sin borrarlo (es una prueba), se le quita el permiso de ejecución: sudo chmod a-x /usr/local/bin/sincroniza; lo que ya corre sigue igual, pero ya no se puede volver a arrancar. Y si alguien tiene que analizarlo, mejor una copia aparte (cp), también sin ejecución, y el original donde está.

Ejercicio

Cortar la salida en ufw (y lo que ya iba)

Corta en ufw la salida hacia la IP que no está en la ficha, sin tocar los pagos ni los pedidos. Después, comprueba si de verdad ha dejado de salir.

Preparando la máquina…

No cerrar el grifo entero

El primer impulso suele ser cerrar todo. Es lo que hizo alguien por la mañana en otro turno: todo el 443 de salida, cerrado.

tecnico@srv-bodega:~$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)

443/tcp                    DENY OUT    Anywhere
443/tcp (v6)               DENY OUT    Anywhere (v6)

Cada compra abre una conexión nueva con la pasarela de pagos, y ahora no sale:

tecnico@srv-bodega:~$ nc -zv -w 3 192.0.2.40 443
nc: connect to 192.0.2.40 port 443 (tcp) timed out: Operation now in progress

Y lo que ya iba hacia 198.51.100.194, por lo mismo de antes, sigue saliendo:

tecnico@srv-bodega:~$ ss -tni dst 198.51.100.194 | grep -o 'bytes_acked:[0-9]*'
bytes_acked:2138537501

Tira el negocio y ni siquiera corta. Hay que dejarlo en una regla solo para esa IP. ufw status numbered numera las reglas (las de salida llevan (out)):

tecnico@srv-bodega:~$ sudo ufw status numbered
Status: active

   To                         Action      From
   --                         ------      ----
[ 1] OpenSSH                    ALLOW IN    Anywhere
[ 2] 80/tcp                     ALLOW IN    Anywhere
[ 3] 443/tcp                    ALLOW IN    Anywhere
[ 4] 443/tcp                    DENY OUT    Anywhere                   (out)
[ 5] OpenSSH (v6)               ALLOW IN    Anywhere (v6)
[ 6] 80/tcp (v6)                ALLOW IN    Anywhere (v6)
[ 7] 443/tcp (v6)               ALLOW IN    Anywhere (v6)
[ 8] 443/tcp (v6)               DENY OUT    Anywhere (v6)              (out)

ufw delete con la regla escrita quita la de IPv4 y la de IPv6 de una vez (con el número, solo esa):

tecnico@srv-bodega:~$ sudo ufw delete deny out 443/tcp
Rule deleted
Rule deleted (v6)
tecnico@srv-bodega:~$ sudo ufw reject out to 198.51.100.194
Rule added

Y se comprueba lo que importa: el pago nuevo sale y la IP de fuera no.

tecnico@srv-bodega:~$ nc -zv -w 3 192.0.2.40 443
Connection to 192.0.2.40 443 port [tcp/https] succeeded!
tecnico@srv-bodega:~$ nc -zv -w 3 198.51.100.194 443
nc: connect to 198.51.100.194 port 443 (tcp) failed: Connection refused

Ejercicio

No cerrar el grifo entero: ufw status numbered y delete

Por la mañana alguien cerró en ufw todo el 443 de salida para cortar esa IP. Mira qué ha roto y déjalo en una regla solo para ella.

Preparando la máquina…

En el router, para toda la red

srv-bodega no es el único equipo de la bodega. El router ve lo que sale de todos, y una regla en él corta para toda la red. Es un MikroTik (R-Bodega): el operador en ether1 y el servidor en ether2. Torch enseña lo que pasa por un puerto en ese momento, conexión a conexión:

[admin@R-Bodega] > /tool torch interface=ether2 duration=2s
-- [Q quit|D dump|C-z pause]Columns: MAC-PROTOCOL, IP-PROTOCOL, DSCP, SRC-ADDRESS, SRC-PORT, DST-ADDRESS,
       DST-PORT, TX
MA  IP-  DSCP  SRC-ADDRESS    SRC-PORT  DST-ADDRESS     DST-PORT     TX
ip  tcp     0  192.168.88.10  56059     198.51.100.194  443 (https)  44.3kbps
ip  tcp     0  192.168.88.10  45886     192.0.2.40      443 (https)  9.0kbps
ip  tcp     0  192.168.88.10  33027     198.51.100.20   443 (https)  3.0kbps
-- [Q quit|D dump|C-z pause]
[admin@R-Bodega] > /ip firewall connection print
Flags: S - SEEN-REPLY; A - ASSURED; C - CONFIRMED; F - FASTTRACK; s - SRCNAT
Columns: PROTOCOL, SRC-ADDRESS, SRC-PORT, DST-ADDRESS, DST-PORT, TCP-STATE,
       TIMEOUT
#       PRO  SRC-ADDRESS    SRC-P  DST-ADDRESS     DST  TCP-STATE    TIMEOUT
0 SACFs tcp  192.168.88.10  33027  198.51.100.20   443  established  23h59m59s
1 SACFs tcp  192.168.88.10  45886  192.0.2.40      443  established  23h59m59s
2 SACFs tcp  192.168.88.10  56059  198.51.100.194  443  established  23h59m59s

where filtra, y detail enseña todo de una: orig-bytes es lo que ha salido por esa conexión y orig-rate, a qué ritmo sale ahora.

[admin@R-Bodega] > /ip firewall connection print detail where dst-address~"198.51.100.194"
Flags: S - SEEN-REPLY; A - ASSURED; C - CONFIRMED; F - FASTTRACK; s - SRCNAT
2 SACFs protocol=tcp src-address=192.168.88.10 src-port=56059
      dst-address=198.51.100.194 dst-port=443
      reply-src-address=198.51.100.194 reply-src-port=443
      reply-dst-address=203.0.113.2 reply-dst-port=56059
      tcp-state=established timeout=23h59m59s orig-packets=1 478 031
      orig-bytes=2 140 187 500 orig-fasttrack-packets=0
      orig-fasttrack-bytes=0 repl-packets=26 875 repl-bytes=38 912 500
      repl-fasttrack-packets=0 repl-fasttrack-bytes=0 orig-rate=1100.0kbps
      repl-rate=20.0kbps

Ejercicio

Lo que sale de la red: Torch y la tabla de conexiones

Desde el router de la bodega, mira qué sale de su red y hacia dónde. En la ficha solo están la pasarela de pagos (192.0.2.40) y la distribuidora (198.51.100.20): busca la conexión que manda mucho a otra IP.

Encendiendo el router…

La regla, delante de lo establecido

En el router pasa lo mismo que en el servidor. Una regla de corte añadida al final de forward:

[admin@R-Bodega] > /ip firewall filter add chain=forward dst-address=198.51.100.194 action=drop comment="Salida rara"
[admin@R-Bodega] > /ip firewall filter print stats
Flags: D - DYNAMIC
Columns: CHAIN, ACTION, BYTES, PACKETS
#   CHAIN    ACTION                        BYTES    PACKETS
;;; special dummy rule to show fasttrack counters
0 D forward  passthrough                       0          0
;;; defconf: fasttrack
1   forward  fasttrack-connection  2 239 968 750  1 546 956
;;; defconf: accept established,related, untracked
2   forward  accept                2 239 968 750  1 546 956
;;; defconf: drop invalid
3   forward  drop                              0          0
;;; defconf: drop all from WAN not DSTNATed
4   forward  drop                              0          0
;;; Salida rara
5   forward  drop                              0          0

Cero. La conexión ya establecida la deja pasar antes la regla 2, «accept established», y nunca llega a la del final. Hay que ponerla delante. print da los números:

[admin@R-Bodega] > /ip firewall filter print
Flags: D - DYNAMIC
0 D ;;; special dummy rule to show fasttrack counters
  chain=forward action=passthrough

1   ;;; defconf: fasttrack
  chain=forward action=fasttrack-connection
  connection-state=established,related

2   ;;; defconf: accept established,related, untracked
  chain=forward action=accept connection-state=established,related,untracked

3   ;;; defconf: drop invalid
  chain=forward action=drop connection-state=invalid

4   ;;; defconf: drop all from WAN not DSTNATed
  chain=forward action=drop connection-state=new connection-nat-state=!dstnat
  in-interface=ether1

5   ;;; Salida rara
  chain=forward action=drop dst-address=198.51.100.194

La 0 la pone el propio router en cuanto hay una regla de fasttrack (D, dinámica), y delante de ella no se puede mover nada:

[admin@R-Bodega] > /ip firewall filter move [find comment="Salida rara"] destination=0
failure: cannot move builtin
[admin@R-Bodega] > /ip firewall filter move [find comment="Salida rara"] destination=1
[admin@R-Bodega] > /ip firewall filter print stats
Flags: D - DYNAMIC
Columns: CHAIN, ACTION, BYTES, PACKETS
#   CHAIN    ACTION                        BYTES    PACKETS
;;; special dummy rule to show fasttrack counters
0 D forward  passthrough                       0          0
;;; Salida rara
1   forward  drop                          1 680          4
;;; defconf: fasttrack
2   forward  fasttrack-connection  2 242 123 750  1 548 452
;;; defconf: accept established,related, untracked
3   forward  accept                2 242 123 750  1 548 452
;;; defconf: drop invalid
4   forward  drop                              0          0
;;; defconf: drop all from WAN not DSTNATed
5   forward  drop                              0          0

Ahora sí: la regla está delante y sus contadores suben con lo que corta (los reintentos del servidor), y lo de esa conexión ya no llega a «accept».

Si son varias IPs, una lista: cada IP en la lista y una sola regla con dst-address-list.

[admin@R-Bodega] > /ip firewall address-list add list=bloqueadas address=198.51.100.194 comment="Salida rara"
[admin@R-Bodega] > /ip firewall address-list print
Columns: LIST, ADDRESS, CREATION-TIME
# LIST        ADDRESS         CREATION-TIME
;;; Salida rara
0 bloqueadas  198.51.100.194  2026-10-16 17:30:52

La regla que la usa se pone ya delante de lo establecido, con place-before y el número de la de fasttrack en el último print: /ip firewall filter add chain=forward dst-address-list=bloqueadas action=drop place-before=1. Para otra IP, basta con añadirla a la lista.

Ejercicio

Cortar en el router: delante de lo establecido

Corta en el router la salida hacia la IP que no está en la ficha, para toda la red, sin tocar la pasarela de pagos (192.0.2.40) y la distribuidora (198.51.100.20). Ojo: esa conexión ya está establecida.

Encendiendo el router…

Lo que cuesta cada decisión

  • Apagar srv-bodega: contiene, pero la tienda deja de vender, y se pierde lo que estaba en memoria.
  • Cerrar todo el 443 de salida (en el servidor o en el router): los pagos y los pedidos dejan de salir, y lo que ya iba hacia fuera sigue saliendo.
  • Una regla solo para esa IP en el servidor: contiene srv-bodega, si además se corta lo que ya estaba establecido (conntrack, ss -K).
  • La misma en el router, delante de lo establecido: contiene para toda la red.
  • Y después, mirar otra vez: si el volumen sigue, hacia otra IP o desde otro equipo, la primera regla no bastaba.

En la sala: el SOC de tarde

En la sala estás en el SOC a las cinco y media, con la alerta del volumen que sale de srv-bodega en la pared y dos terminales en tu puesto: la del servidor y, en su pestaña, la consola de R-Bodega. Mientras siga saliendo, cada quince minutos la alerta vuelve con los megas perdidos; a las siete y media, si no se ha cortado, el día se cierra así. Si el programa ve su conexión cortada un rato, prueba con otra IP: hay que mirar otra vez después de cortar. Lo que tires (la tienda, los pagos, los pedidos, o tu propio SSH si aíslas el servidor) cuenta como servicio caído, y la nota mira además los datos que salieron, que lo cortes también en el router y que el programa siga vivo para quien investigue después.