Конкурс IDS Bypass на PHDays
05.07.2019
На международном форуме Positive Hack Days 2019 впервые проходил конкурс IDS Bypass. Участникам надо было исследовать сегмент сети из пяти узлов, дальше либо эксплуатировать уязвимость сервиса, либо выполнить заданное условие (например, послать определенный HTTP-ответ) и таким образом добыть флаг. Найти эксплойт было легко, но задачу усложняла IDS: система стояла между участниками и узлами и проверяла каждый сетевой пакет. Атакующие видели на дашборде, если сигнатура блокировала их соединение. Ниже я расскажу подробно о самих заданиях и разберу их решение.
100.64.0.11 — Struts
Первым узлом по числу решивших задание был Struts. После сканирования портов Nmap, находим сервис Apache Struts на порте 8080.
nmap -Pn -sV -p1-10000 100.64.0.11
631/tcp open ipp CUPS 2.1 8005/tcp open mxi? 8009/tcp open ajp13 Apache Jserv (Protocol v1.3) 8080/tcp open http Apache Tomcat/Coyote JSP engine 1.1
Уязвимость для Apache Struts отгремела в 2017 году: используя OGNL-инъекцию, атакующий мог исполнять любой код на Struts без авторизации. Эксплойт есть, например, на GitHub, но ловится IDS:
[Drop] [] [1:1001:1] Apache Struts2 OGNL inj in header (CVE-2017-5638) []
Сам код сигнатуры участникам недоступен, но из сообщения в логах можно понять механизм ее работы. В данном случае сигнатура обнаружила OGNL-инъекцию в HTTP:
GET /showcase.action HTTP/1.1 Accept-Encoding: identity Host: 100.64.0.11:8080 Content-Type: %{(#_='multipart/form-data')...
Если поисследовать поведение IDS, то станет очевидно, что она ловит сочетание %{ в начале заголовка Content-Type. Вариантов решения несколько:
- Участник @empty_jack пробовал разбить комбинацию символов %{ собственным словарем для фаззинга и таким образом пришел к решению со строкой Content-Type: %${.
- Пофаззить сам HTTP-запрос. Участник @c00lhax0r обнаружил, что нулевой символ в начале заголовка также обойдет IDS: Content-Type: \0${.
- Большинство эксплойтов для CVE-2017-5638 делают инъекцию с символом процента. Но некоторые исследователи этой и предыдущих уязвимостей Apache Struts пишут, что инъекция может начинаться как с %, так и с $. Таким образом, сочетание ${ обойдет IDS-сигнатуру и исполнит код на системе. Такое решение было задумано изначально.
Этот задание оказалось самым легким, его решило восемь участников.
100.64.0.10 — Solr
На порте 8983 находился сервер Apache Solr, написанный на Java.
$ nmap -Pn -sV -p1-10000 100.64.0.10 22/tcp open ssh (protocol 2.0) 8983/tcp open http Jetty
Эксплойт для Apache Solr 5.3.0 легко найти — CVE-2019-0192. Злоумышленник может подменить адрес RMI-сервера в коллекции. Для эксплуатации требуется фреймворк ysoserial, который генерирует цепочки Java-объектов (гаджеты) и доставляет их различными способами. Например, с JRMP-сервера.
Разумеется, используя вышеупомянутый эксплойт в лоб, участники увидят срабатывания IDS-сигнатур:
[Drop] [] [1:10002700:3001] ATTACK [PTsecurity] Java Object Deserialization RCE POP Chain (ysoserial Jdk7u21) []
Jdk7u21 это лишь одна из тридцати нагрузок, и их выбор зависит от используемых в уязвимом сервисе библиотек. Цепочка гаджетов Jdk7u21 использует только стандартные классы из Java Development Kit версии 7u21, а цепочка CommonsCollections1 содержит классы из широко используемого Apache Common Collections 3.1.
Атакующий подменяет адрес RMI-сервера в коллекции Solr на свой, а затем запускает JRMP-сервер. Solr запрашивает объект по адресу и получает вредоносный Java-объект. После его десериализации код исполняется на сервере.
Сигнатура срабатывает на последовательность классов в сериализованном Java-объекте. Он передается с машины атакующего и в трафике начинается так:
Решение этой задачи было простым. В сигнатуре явным образом говорится о Jdk7u21. Для обхода надо было пробовать другие цепочки гаджетов. Например, одну из CommonsCollections. Сигнатур для других цепочек в IDS не было. Участник получит шелл на системе и прочитает флаг. С заданием справились пять участников.
100.64.0.12 — SAMR
Одно из самых сложных и интересных заданий конкурса. Это Windows-машина с открытым 445-м портом. Флаг разбит на имена двух пользователей системы, и для выполнения задания надо было получить список всех пользователей на Windows-узле.
Разумеется, MS17-010 и другие эксплойты не работали на этой машине. Перечислить пользователей могли, например, сценарии из Nmap или фреймворка impacket:
$ python samrdump.py 100.64.0.12 Impacket v0.9.15 - Copyright 2002-2016 Core Security Technologies
[] Retrieving endpoint list from 100.64.0.12 [] Trying protocol 445/SMB… Found domain(s): . SAMR . Builtin [] Looking up users in domain SAMR [-] The NETBIOS connection with the remote host timed out. [] No entries received.
Оба сценария делают DCERPC-запросы к машине по 445-му порту. Но не все так просто: некоторые пакеты блокируются IDS, и в этот раз срабатывают уже две сигнатуры:
[] [1:2001:2] SAMR DCERPC Bind [] [Drop] [] [1:2002:2] SAMR EnumDomainUsers Request []
Первая обнаруживает подключение к сервису SAMR и лишь помечает TCP-соединение флагом. А вторая срабатывает на запрос EnumDomainUsers к сервису SAMR. Этот сервис имеет и другие способы получения пользователей: QueryDisplayInfo, QueryDisplayInfo2, QueryDisplayInfo3. Все они также блокировались сигнатурами.
Протокол DCERPC и Windows-сервисы дают огромные возможности по удаленному управлению узлом. Этим протоколом пользуются большинство известных инструментов, например PsExec или BloodHound. Сервис SAMR, то есть SAM Remote Protocol, позволяет работать с учетными записями на узле и в том числе узнать список пользователей.
Для запроса EnumDomainUsers Impacket делает следующее:
DCERPC-соединение к сервису SAMR устанавливается поверх SMB, и все дальнейшие запросы идут в контексте этого сервиса. Сигнатуры срабатывают на первый и последний пакет со скриншота.
К заданию я дал две подсказки:
- Your attempts cause IDS generate 2 alerts. Look closely at the first.
- Which connection commands for this protocol do you know?
Речь идет о протоколе DCERPC и способах установления подключений. В списке доступных PDU за подключение и смену контекста отвечают команды Bind и Alter Context, причем вторая позволяет менять текущий контекст без разрыва соединения.
Для решения надо было переписать логику сценария samrdump:
- Сделать Bind на другой сервис, например с UUID 3919286a-b10c-11d0-9ba8-00c04fd92ef5.
- Использовать Alter Context для переключения на SAMR.
- Отправить запрос EnumDomainUsers.
Изменения умещались на трех строчках:
< dce.bind(samr.MSRPC_UUID_SAMR)
dce.bind(uuid.uuidtup_to_bin(("3919286a-b10c-11d0-9ba8-00c04fd92ef5", "0.0"))) dce.alter_ctx(samr.MSRPC_UUID_SAMR) dce._ctx = 1
Альтернативное решение предложил победитель конкурса @psih1337. Запрос EnumDomainUsers возвращал список пользователей не по именам, а по SID (Security ID). SID это не случайное число. Например, учетная запись LocalSystem имеет SID S-1-5-18, а для пользователей, созданных вручную, он начинается с 1000.
Таким образом, вручную перебрав сиды от 1000 до 2000, можно с высокой долей вероятности обнаружить искомые учетные записи в системе. Они нашлись под сидами 1008 и 1009.
Решение этого задания требовало понимания протокола DCERPC и опыта исследования Windows-инфраструктур. @psih1337 был единственным, кто его решил.
100.64.0.13 — DNSCAT
На 80-м порту есть веб-страница с формой для IP-адреса.
Если указать свой IP, то на 53-й порт прилетает UDP:
17:40:45.501553 IP 100.64.0.13.38730 > 100.64.0.187: 61936+ CNAME? dnscat.d2bc039ce800000000d6eae8eae3bf81fd84d1695f5888aba8dcec06d071.a73b3f0561ca4906d268214f4b70da1bdb50f75739ae0577139096732bf8.0d0a987ce23408bac15426a22e. (173) 17:40:45.501639 IP 100.64.0.187 > 100.64.0.13: ICMP 100.64.0.187 udp port domain unreachable, length 209 17:40:46.520457 IP 100.64.0.13.38730 > 100.64.0.187: 21842+ TXT? dnscat.7f4e039ce800000000d6eae8eae3bf81fd84d1695f5888aba8dcec06d071.a73b3f0561ca4906d268214f4b70da1bdb50f75739ae0577139096732bf8.0d0a987ce23408bac15426a22e. (173) 17:40:46.520546 IP 100.64.0.187 > 100.64.0.13: ICMP 100.64.0.187 udp port domain unreachable, length 209
Понятно, это DNSCAT — инструмент для DNS-туннелей. После указания IP-адреса в форме к нему пытается установить соединение DNSCAT-клиент. Если это получится, то сервер (то есть участник) получит шелл на конкурсной машине и вытащит флаг.
Разумеется, если мы просто поднимем сервер DNSCAT и попытаемся принять подключение, нас постигнет неудача:
[Drop] [] [1:4001:1] 'dnscat' string found in DNS response []
IDS-сигнатура срабатывает на строку "dnscat" в трафике от нашего сервера — это явно говорится в сообщении. Обфусцировать или шифровать трафик тоже не выйдет.
Посмотрев на код клиента, мы обнаружим, что проверки в нем недостаточно строгие. То есть в ответе строки "dnscat" может не быть вовсе! Останется только убрать ее из кода или подменить на лету утилитой NetSED. Подменить сразу куда проще, но я все же приведу патч для кода сервера:
diff -r dnscat2/server/libs/dnser.rb dnscat2_bypass/server/libs/dnser.rb < segments << unpack("a#{len}")
segments << [unpack("a#{len}")[0].upcase]
< name.split(/./).each do |segment|
name.upcase.split(/\./).each do |segment|
diff -r dnscat2/server/tunnel_drivers/driver_dns.rb dnscat2_bypass/server/tunnel_drivers/driver_dns.rb < response = (response == "" ? "dnscat" : ("dnscat." + response))
response = (response == "" ? "dnsCat" : ("dnsCat." + response))
На конкурсе было пять решений для этого задания.
100.64.0.14 — POST
Флаг с этой конкурсной машины не получил никто.
Видим знакомую форму с IP-адресом. Некто предлагает нам поучаствовать в тестировании новой вредоносной программы. Среди ее новшеств — обход IDS неизвестным способом. Для флага надо лишь отправить ей HTTP-заголовок "Server: ng1nx" в ответ. Будет жарко.
Как и ожидалось: получаем GET-запрос на наш IP и отправляем ответ, который блокируется IDS.
[Drop] [] [1:5002:1] 'ng1nx' Server header found. Malware shall not pass []
Есть подсказка:
Sometimes, tasks that look hard are the simplest. If nothing seems vulnerable, maybe you're missing something right under your nose?
Что-то уязвимое прямо перед нашим носом это и есть IDS. Со страницы срабатываний можно обнаружить, что у нас открытая Suricata IDS.
Первая ссылка по запросу "Suricata IDS Bypass" приводит к CVE-2018-6794. Эта уязвимость позволяет обойти проверку пакетов, если нарушить нормальный ход установки связи TCP (TCP handshake) и послать данные до завершения процесса. Выглядит это так:
Client -> [SYN] [Seq=0 Ack=0] -> Evil Server # 1/2 Client <- [SYN, ACK] [Seq=0 Ack=1] <- Evil Server # 2/2 Client <- [PSH, ACK] [Seq=1 Ack=1] <- Evil Server # Data here Client <- [FIN, ACK] [Seq=83 Ack=1] <- Evil Server Client -> [ACK] [Seq=1 Ack=84] -> Evil Server # 3/2 Client -> [PSH, ACK] [Seq=1 Ack= 4] -> Evil Server
Качаем эксплойт, меняет строку на "ng1nx", выключаем RST-пакеты ядра и запускаем.
Как уже было сказано, флагов с этой машины не получил никто, хотя пара участников были близки к решению.
Заключение
В конкурсе зарегистрировалось 49 участников, 12 сдали хотя бы один флаг. Интересно, что конкурсные задания могут иметь сразу несколько решений, особенно задания с протоколом SMB и DCERPC. Может быть, у вас есть свои идеи прохождения некоторых заданий?
Призовые места:
- 1 место: @psih1337
- 2 место: @webr0ck
- 3 место: @empty_jack
Статистика срабатываний сигнатур:
Спасибо всем участникам! В следующем году будет еще больше заданий разного уровня сложности.