
Приватный PoC от Fortbridge для цепочки RCE CVE-2026-32740 Next.js/sharp leak-to-memcpy-GOT
Рабочий proof of concept, который превращает CVE-2026-32740 в
жёстко закодированном стеке изображений Next.js/sharp в запись по выбранному адресу и
подтверждённый обратный вызов /usr/bin/id.
Это приватный исследовательский репозиторий Fortbridge. Используйте его только против включённой лаборатории или другой системы, на тестирование которой у вас есть явное разрешение.
Эксплойт использует только маршруты HTTP-загрузки и оптимизации изображений целевой системы:
g_module_open_full — процедуры GModule,
которая оборачивает dlopen для загрузки нативной разделяемой библиотеки из пути к файлу.
Эксплойт вычисляет её адрес во время выполнения как восстановленную базу libvips
плюс жёстко закодированное смещение профиля 0x3e995e. Загрузка библиотеки запускает
её конструктор./usr/bin/id. Он отправляет этот ELF через публичный
маршрут загрузки под именем изображения x.jpg и медиатипом image/jpeg.memcpy@GOT - 16.uploads/x.jpg непосредственно перед
слотом GOT и заменяет memcpy@GOT на выведенный загрузчик GModule. Следующая
строка вызывает этот загрузчик с путём к библиотеке, уже находящимся в RDI./usr/bin/id по TCP. Токен для каждой попытки предотвращает учёт устаревшего
обратного вызова как успеха; возвращённая строка uid=... является доказательством.Исходные прогоны валидации относительно libvips записаны в
evidence/libvips-gmodule-rce-10x.json.
Все десять свежих процессов вернули корректный вывод /usr/bin/id с десятью различными
рандомизированными базами libvips и десятью независимо выведенными адресами загрузчика.
Точные данные прогона на Ubuntu находятся в
evidence/libvips-gmodule-pie-rce-10x.json.
Точные данные прогона на Debian 13 APT находятся в
evidence/debian13-apt-libvips-gmodule-rce-10x.json.
Мы протестировали полный эксплойт против 10 заново запущенных процессов Node на Ubuntu
и 10 заново запущенных процессов Node на Debian. Все 20 прогонов достигли выполнения
команды и вернули вывод /usr/bin/id целевой системы. Эксплойт также
вычислил другой адрес загрузчика GModule для каждой рандомизированной базы libvips.
Перед созданием финальной полезной нагрузки эксплойту может потребоваться несколько попыток утечки ASLR. Каждая попытка загружает AVIF с утечкой, отправляет его через маршрут оптимизации и проверяет возвращённый PNG на наличие четырёх указателей, идентифицирующих один профиль и одну базу libvips. Ubuntu потребовалось от 3 до 23 попыток. Debian потребовалось от 3 до 15. Неполные или неоднозначные данные вызывают ещё одну попытку вместо угадывания профиля.
В проекте также есть 96 автоматизированных регрессионных тестов. Это проверки на уровне кода, а не 96 дополнительных прогонов эксплойта. Они покрывают валидацию профиля, классификацию возвращённых указателей, калибровку кучи, вычисление адресов, построение полезной нагрузки и безопасный отказ, когда данные не соответствуют поддерживаемой цели.
Обе цели x86-64 используют Next.js 15.5.23, sharp 0.34.4, встроенный libvips 8.17.2, встроенный libheif 1.20.2, ASLR и NX. Их нативные среды выполнения различаются:
| Профиль | Node | glibc | libstdc++ | Соотношение селектора |
|---|---|---|---|---|
| Ubuntu | 25.8.1, PIE ET_DYN | 2.43-2ubuntu2.4 | 6.0.35 | 0x6000 - 0x690 = 0x5970 |
| Debian 13 | Debian APT 20.19.2, PIE ET_DYN | 2.41-12+deb13u4 | 6.0.33 | 0x6000 - 0x3a0 = 0x5c60 |
Полные build ID и значения SHA-256 находятся в
profiles/native_stack_profiles_pie.json.
Пакет Debian, артефакт, проверка маршрута и измерения раскладки за пять жизненных циклов
зафиксированы в
evidence/debian13-profile-derivation.json.
Цель Debian использует стандартный дистрибутивный пакет
nodejs=20.19.2+dfsg-1+deb13u3; Node не компилируется из исходников.
Цепочке не нужна рандомизированная база Node. Её цель управления — внутренняя
процедура g_module_open_full внутри libvips, чья рандомизированная база
восстанавливается из возвращённых пикселей. Каждый версионный профиль жёстко кодирует
специфичные для сборки константы, необходимые эксплойту:
memcpy@GOT и смещение g_module_open_full плюс его проверяющие
байты инструкций;Рандомизированная база libvips, результирующий адрес загрузчика во время выполнения и финальный двухбайтовый селектор не жёстко закодированы. Они выводятся для каждого целевого процесса из возвращённых пикселей и выбранного профиля.
Эксплойт отказывает безопасно, когда профиль некорректен или возвращённые пиксели не выбирают ровно одну поддерживаемую пару профиль/база. Стоковый профиль также требует своей полной возвращённой записи кучи. Профиль — это точное заявление о совместимости, поэтому оператор должен проверить целевые артефакты офлайн перед его использованием.
ABI загрузчика важен. Перезаписанный вызов передаёт путь к библиотеке в RDI, указатель на строку изображения в RSI и длину копирования 58 байт в RDX. Эта точная профилированная процедура GModule использует только поддерживаемые биты флагов из ESI и не разыменовывает RDX на успешном пути загрузки. Офлайн-стенд проверил эту точку входа и сигнатуру инструкций перед её использованием в измеренных прогонах.
ffmpeg с кодировщиком libaom-av1cc на машине атакующегоУстановите единственную зависимость Python:```bash python3 -m venv .venv . .venv/bin/activate python3 -m pip install -r requirements.txt
Подтвердите поддержку кодирования AV1:```bash
ffmpeg -hide_banner -encoders | grep libaom-av1
Лаборатория намеренно принимает произвольные загрузки и передаёт выбранные файлы в sharp. Не предоставляйте к ней доступ из недоверенной сети.```bash cd lab npm ci npm run build npm run start
Цель затем доступна по адресу `http://127.0.0.1:3000`.