Массовые взломы сайтов на WordPress в последнее время стали обыденностью, благодаря изменениям в движке, которые предположительно, должны были облегчить взаимодействие через rest api для разработчиков.
Недавно секурные ресурсы затрубили о тревоге, вордпресс экстренно выпустил обновление 7.0.2 призванный закрыть «дыру» и следом за ним сразу 7.0.3. На момент написания статьи 7.0.3 не имела подробной информации о закрытых проблемах, поэтому пройдёмся по 7.0.2, которое было призвание решить основную проблему. Речь про sql-инъекцию + пакетную обработку запросов rest api, которые могли обходить безопасность. В паре эти две уязвимости давали злоумвшленникам возможность полного захвата контроля над сайтом с вытекающими последствиями: создание новых администраторов и изменение+создание новых исполняемых файлов .php, которые обеспечивало персистенс резиденс вредоносных скриптов, маскирующихся под файлы wp.
На одном из сайтов у меня развёлся целый зоопарк скриптов, которые судя по комментариям в коде были написаны AI и частично зашифрованы, через EVAL и Base64. Скрипты даже пытались дотянуться до других сайтов, расположенных на том же хосте в обход openbase dir. Кроме того они и в бд делали записи. Пришлось делать откат файлов и бд. Если у вас нет нескомпрометированного бэкапа, то будет очень грустно.
Далее комментарий от аи.
Если посмотреть именно на исходный код, а не только на описание CVE, становится понятно, почему ошибка оказалась настолько серьезной.
Первая уязвимость — author__not_in
Параметр author__not_in предназначен для исключения авторов из выборки.
Нормальное использование:
new WP_Query([
'author__not_in' => [3, 8, 15]
]);
В итоге WordPress строит SQL примерно такого вида:
... AND post_author NOT IN (3,8,15)
Где была ошибка
Разработчики предполагали, что значение всегда будет массивом.
В коде было что-то концептуально похожее на:
if ( is_array( $author__not_in ) ) {
$author__not_in = array_map( 'absint', $author__not_in );
}
Если же значение приходило не массивом, а строкой, то преобразование absint() вообще не выполнялось. Затем это значение попадало в построитель SQL практически без необходимой очистки.
Почему обычный WordPress не всегда был уязвим
Сам WordPress редко передает пользовательский ввод напрямую в WP_Query.
Однако многие плагины делают примерно так:
$query = new WP_Query([
'author__not_in' => $_GET['authors']
]);
или
$_POST['authors']
Если разработчик не проверял тип данных, возникала возможность SQL-инъекции. Поэтому уязвимость называют facilitated SQL injection — ядро создавало опасную ситуацию, но обычно требовалось, чтобы плагин или тема передали недоверенные данные.
Вторая уязвимость — REST Batch API
Именно она сделала возможной атаку без участия уязвимого плагина.
В WordPress 6.9 появился механизм пакетных REST-запросов.
Вместо
POST /wp-json/...
POST /wp-json/...
POST /wp-json/...
можно было отправить
{
"requests": [
{...},
{...},
{...}
]
}
и сервер выполнял всё за один запрос.
Ошибка заключалась в том, что во время обработки пакета происходила путаница между маршрутом (route) и обработчиком (handler).
Из-за этого для внутренних запросов могли применяться не те проверки безопасности, которые должны были срабатывать. Исследователи называют это route/handler confusion.
Как выглядела цепочка атаки
Самая опасная особенность заключалась именно в объединении двух ошибок:
- REST Batch API позволял передать данные, обходя ожидаемую валидацию.
- Эти данные попадали в
WP_Query. author__not_inне очищался должным образом.- Выполнялась SQL-инъекция.
- Затем злоумышленник получал возможность изменить данные сайта и, используя вторую часть цепочки, добиться выполнения произвольного PHP-кода. Patchstack независимо подтвердила возможность полного захвата сайта и сообщила об активной эксплуатации вскоре после выхода исправлений.
Почему оценка CVSS сначала была невысокой
Интересно, что первоначально WPScan оценил SQL-инъекцию примерно в 5.9 (Medium), потому что рассматривал её отдельно. Позже CISA повысила оценку до 9.1 (Critical), поскольку стало ясно, что в сочетании с REST Batch API она приводит к полному захвату сайта и уже активно эксплуатируется.
Что изменили разработчики
Исправление оказалось небольшим, но принципиальным:
- значение
author__not_inтеперь приводится к безопасному виду независимо от того, пришёл массив или нет; - в REST Batch API устранена логическая ошибка обработки маршрутов, из-за которой можно было обходить проверки безопасности.
Вывод
Срочно обновляйтесь, если у вас версия WordPress выше 6.9.0 и ниже 7.0.2. Если вы уже словили вирус то лучше откатится на чистый бэкап. Если такой возможности нет то придется долго и нудно вычищать вирус (не только из вордпресса, если он работает под пользователем с широкими правами)