Under the Hood of WP Cerber 9.9
English version: Under the Hood of WP Cerber 9.9
В этом релизе основное внимание уделено тем аспектам WP Cerber, которые вы можете не заметить, пока что-то не пойдет не так. Мы посвятили его усилению защиты от копирования и восстановления настроек, улучшению работы Traffic Inspector с обфусцированным JavaScript и оптимизации поведения плагина на старых хостинговых платформах. Мы также завершили длительную миграцию с API настроек WordPress. Вот что изменилось «под капотом» и что это значит для ваших сайтов.
Настройки, сохраняющиеся при повреждении базы данных
WP Cerber хранит свою конфигурацию в одном параметре, CERBER_CONFIG . До сих пор, если это значение повреждалось и больше не могло быть десериализовано, плагин передавал поврежденный результат напрямую в array_merge() . В PHP 8 это вызывало фатальную TypeError во время загрузки плагина. Поскольку сбой происходил во время загрузки, это приводило к отключению всего сайта, а не только административных экранов плагина.
crb_get_settings() теперь проверяет возвращаемое значение функции crb_unserialize() перед его использованием. Если сохраненные данные не могут быть преобразованы в массив, плагин возвращается к настройкам по умолчанию вместо сбоя. Он регистрирует ошибку как постоянную критическую проблему через CRB_Issues::add() в коде corrupted_settings . Эта проблема автоматически устраняется при следующем сохранении настроек, которое выполняется внутри cerber_settings_update() .
Мы постарались разграничить два случая, которые выглядят похоже, но таковыми не являются. Пустое или иное ложное сохраненное значение просто означает, что настройки еще не существуют. Это значение никогда не десериализуется, и плагин тихо возвращается к значениям по умолчанию, ничего не выдавая. Сообщение об этом было бы ложной тревогой. Проблема corrupted_settings теперь возникает только тогда, когда непустое сохраненное значение не десериализуется в массив, что является точной причиной первоначального сбоя в рабочей среде.
Две соседние ветки в одной и той же функции получили одинаковую дисциплинарную ответственность. Слияние для обеспечения совместимости с дополнением Cloudflare теперь требует массив перед слиянием. Ветка CERBER_WP_OPTIONS всегда возвращает массив, если не запрашивается конкретная настройка.
Самовосстанавливающаяся резервная система поверх защиты
Возврат к настройкам по умолчанию позволяет сайту продолжать работу, но при этом сбрасывается конфигурация администратора. Поэтому мы добавили уровень восстановления поверх защитного механизма. Новый класс, CRB_Settings_Backup , хранит последнюю известную действительную копию CERBER_CONFIG в собственном хранилище ключ-значение плагина.
Резервная копия хранится в формате JSON вместе с идентификатором пользователя, создавшего её, меткой времени и контекстом, в котором она была создана. Она обновляется после успешного обновления настроек, после импорта настроек, после обновления плагина и в рамках ежедневной задачи технического обслуживания. Заменить существующую резервную копию можно только с помощью действительной конфигурации и известного кода контекста.
Когда детектор повреждений обнаруживает нечитаемый файл CERBER_CONFIG , восстановление запускается автоматически. В случае успешного восстановления плагин регистрирует предупреждение, которое можно закрыть, объясняющее произошедшее и предлагающее проверить и сохранить настройки. Это уведомление исчезает после того, как вы это сделаете. Если пригодной для использования резервной копии нет или восстановленное значение не может быть записано, плагин сохраняет свое текущее поведение в крайнем случае и возвращается к настройкам по умолчанию. В этом случае он регистрирует критическую проблему вместо предупреждения.

WP Cerber settings recovery flow. Missing settings quietly fall back to defaults, while corrupted settings trigger automatic recovery from the last-known-valid backup.
Для корректного восстановления необходимо соблюдать кэширование WordPress. recover() удаляет поврежденный параметр CERBER_CONFIG перед записью восстановленного значения. Без этого шага устаревший кэш параметров мог бы привести к тому, что update_site_option() обработала бы значение как неизмененное и пропустила бы запись в базу данных. Сама резервная копия читается и записывается без учета кэша объектов, поэтому доверяется только постоянной записи cerber_sets . Набор принимаемых кодов контекста закрыт. Функция записи sync() и валидатор полезной нагрузки принимают только четыре объявленных кода, поэтому каждая сохраненная полезная нагрузка может пройти ту же проверку, которую позже выполнит восстановление.
Больше никаких фатальных ошибок на хостах без mysqlnd
WP Cerber считывает данные из базы данных через mysqli. Один из способов получения данных, CRB_Database::fetch_result_set() , использует mysqli_result::fetch_all() для получения всего набора результатов за один вызов. Этот метод существует только в том случае, если расширение mysqli в PHP собрано на основе драйвера mysqlnd. На хостах, где mysqli скомпилирован с использованием более старой библиотеки libmysqlclient, этот метод отсутствует, и его вызов приводит к фатальной ошибке.
Исправление использует уже применяемый в других местах cerber-common.php шаблон. Перед использованием быстрого пути код теперь проверяет function_exists('mysqli_fetch_all') . Если эта функция недоступна, она считывает результирующий набор построчно с помощью mysqli_result::fetch_array() для обоих типов данных: MYSQLI_ASSOC и MYSQLI_NUM . Порядок строк, структура результатов, очистка и существующий контракт возврата Revalt сохраняются. Резервный вариант возвращает те же данные, что и быстрый путь.
Мы также предоставили администраторам способ узнать, когда они находятся в режиме резервного копирования. Новый детектор в CRB_Issue_Monitor выдает предупреждение db_driver_no_mysqlnd , когда расширение mysqli загружено, но mysqli_fetch_all() отсутствует. Уведомление отображается в виджете «Готовность системы». Оно носит информационный характер. Оно подтверждает, что плагин продолжает работать в режиме резервного копирования, и рекомендует включить mysqlnd для лучшей совместимости и производительности.
Более точное обнаружение обфусцированного JavaScript
CRB_JS_Detector проверяет поля запроса в рамках Traffic Inspector, который включен по умолчанию. Он ищет JavaScript, обфусцированный для сокрытия примитивов, таких как eval , script и XMLHttpRequest . Цель разработки — высоконадежное обнаружение с низким уровнем ложных срабатываний. Детектор декодирует только те входные данные, которые однозначно являются закодированной строкой, и оставляет все остальное без изменений. В этом релизе были внесены несколько изменений, которые исправили реальный способ обхода защиты и расширили возможности детектора по считыванию данных.
Регрессия, позволявшая пропускать полностью экранированные в шестнадцатеричном формате строки.
В релизе было исправлено одно из ошибок. Эвристический алгоритм экранирования в шестнадцатеричном формате нормализовал каждую найденную строку с помощью trim( $m, "\\'\"" ) . trim() в PHP рассматривает свой второй аргумент как набор символов для удаления, а не как буквальный префикс. Обратная косая черта в этом наборе удаляла начальную обратную косую черту первого экранирования \xNN вместе с открывающей кавычкой. Оставшаяся часть имела нечетную длину 2N + 1 Затем проверка на нечетную длину определяла, что строка не была корректно закодирована в шестнадцатеричном формате, и полностью пропускала декодирование. На практике это приводило к тому, что строка, построенная исключительно из экранирований \xNN , ускользала от проверки, а примитивы, экранированные в шестнадцатеричном формате, такие как eval , script и XMLHttpRequest оставались незамеченными в стандартном пути поля запроса. Исправление восстанавливает корректное декодирование полностью экранированных в шестнадцатеричном формате строк.
Более широкое покрытие экранирования и кодирования символов.
Затем мы расширили возможности детектора. Эвристика экранирования раньше распознавала только \xNN . Теперь она также обрабатывает экранирования \uNNNN и \u{...} , включая строки, в которых смешиваются эти форматы, сохраняя при этом правило, что проверяются только полностью экранированные строки. Декодирование перенесено в специальную вспомогательную функцию, cerber_decode_js_escapes() , которая декодирует кодовые точки ASCII и сохраняет все остальное. Если возникает внутренняя ошибка PCRE, эта вспомогательная функция возвращает исходный ввод, а не пустую строку, поэтому ошибка декодирования не может удалить проверяемое значение.
Эвристический алгоритм кодирования символов также изменился. Ранее он анализировал декодированный вывод только для внешних URL-адресов и IP-адресов. Теперь он также ищет примитивы выполнения и DOM. Декодирование чисел происходит только из явной конструкции fromCharCode(...) а не из любого найденного числового массива. Оба эвристических алгоритма теперь используют один шаблон примитива, учитывающий токены, с границами идентификаторов. Это изменение предотвращает совпадение обычных слов, таких как description и evaluation , с подстроками eval или script внутри них.
Обернутые целочисленные литералы
fromCharCode определена таким образом, чтобы применять ToUint16 к каждому аргументу. Это означает, что ASCII-код плюс любое число, кратное 65536, дает один и тот же символ. Более ранняя версия эвристики принимала только литералы до шести цифр, поэтому семизначное значение, перенесенное в другой формат, оставалось незамеченным. Например, 1048677 декодируется в кодовую единицу 101, букву e . Теперь детектор принимает беззнаковые десятичные и шестнадцатеричные литералы до Number.MAX_SAFE_INTEGER и преобразует каждый из них в свою кодовую единицу ToUint16 независимо от размера целого числа в PHP. Он отклоняет десятичные числа с ведущим нулем, поскольку они неоднозначны в устаревшей восьмеричной системе счисления. Он ограничивает работу с каждой цифрой, поэтому очень длинный литерал в публичном запросе не может привести к неограниченным вычислениям.
Последующее вмешательство устранило аналогичную уязвимость. Возврат пустой строки в первом неподдерживаемом аргументе приводил к потере всего декодированного вызова. Злоумышленник мог добавить один выходящий за пределы допустимого диапазона токен к и без того обнаруживаемой полезной нагрузке и подавить обнаружение. Одним из таких токенов является 9007199254741024 , который JavaScript сопоставляет с пробелом через ToUint16. Теперь структурно допустимый, но неподдерживаемый токен заменяется символом подчеркивания, а остальная часть вызова по-прежнему декодируется. Символ подчеркивания является символом слова, поэтому он также блокирует формирование сфабрикованной границы идентификатора рядом с ключевым словом.
Аргументы, разделённые комментариями
Последний способ обхода использовал комментарии JavaScript. Эвристический алгоритм сжимал входные данные, удаляя пробелы, но оставляя комментарии на месте. Комментарий, вставленный между числовыми аргументами, нарушал сопоставление числового списка, поэтому вызов типа String.fromCharCode(101,/*x*/118,97,108,40,49,41,59) избегал обнаружения. Теперь детектор удаляет блочные и строковые комментарии из списка аргументов fromCharCode перед проверкой и декодированием, сохраняя при этом содержимое строк в кавычках. Захват также работает в режиме dotall, поэтому список аргументов, занимающий несколько строк, считывается как один вызов.
Прекращение поддержки API настроек WordPress.
Страницы настроек WP Cerber были построены на основе API настроек WordPress. Формы отправлялись в /wp-admin/options.php , и плагин использовал register_setting() , add_settings_section() , add_settings_field() , settings_fields() и do_settings_sections() для их регистрации и отображения. Это работало, но привязывало административный интерфейс плагина к процедурной подсистеме WordPress и её соглашениям. Этот релиз завершает переход к собственному механизму форм плагина, и мы сделали это поэтапно, а не одним изменением.
Сначала мы провели границу. Каждый прямой вызов API настроек был перенесен в один статический класс, CRB_Legacy_Settings_Manager . Сканирование кода показало, что использовались только пять из десяти функций API настроек, расположенных в шести местах вызова в двух файлах. Остальные пять никогда не вызывались, поэтому в классе-границе намеренно отсутствуют методы для них. Этот шаг полностью сохранил поведение. Названия параметров, группы параметров, идентификаторы разделов и полей, обратные вызовы, время обработки и вывод остались неизменными.
Затем мы создали замену. Новый класс, CRB_Settings_Renderer , отображает разделы и строки полей непосредственно из декларативной конфигурации, возвращаемой функцией cerber_settings_config() . Формы настроек теперь отправляют данные обратно на собственную страницу администрирования плагина, а не на options.php . Отправка данных обрабатывается существующим конвейером в admin_init , после чего следует POST-redirect-GET запрос обратно на страницу настроек. Отображаемая разметка идентична по байтам тому, что ранее создавала do_settings_sections() , вплоть до заголовков, блоков разделов и строк таблицы формы.
В этом плагине изменилась и проверка nonce. Вместо проверки с помощью check_admin_referer() по группе параметров Settings API, плагин теперь проверяет собственное поле cerber_nonce . Это поле уже присутствовало во всех формах настроек, включая формы, отображаемые в более старых версиях. Недействительный или просроченный nonce больше не останавливает запрос с помощью wp_die() . Вместо этого в очередь ставится уведомление администратора, и вас возвращает к форме.
Перенос файла options.php также удалил один незаметный элемент устаревшего поведения. Старый путь записывал необработанную копию каждой группы форм в параметр для каждой группы с именем cerber-{group} . Эти копии считывались только при миграции до версии 9.3.4 и при удалении программы, никогда не во время выполнения. Теперь они больше не записываются.
Cerber.Hub и управляемые сайты
В двух контекстах старый путь использовался ещё некоторое время, поскольку оба зависели от формата передачи данных через API настроек. Один из них — удалённый рендеринг Cerber.Hub, где протокол между основным сайтом и его управляемыми сайтами передаёт поля option_page и _wpnonce . Другой — экран редактирования управляемого сайта, который сохранял данные через фильтр pre_update_option запускаемый файлом options.php . Мы перевели каждый из них по очереди.
Форма редактирования управляемого сайта теперь отображается через CRB_Settings_Renderer и сохраняется с помощью новой функции nexus_save_client_data_form() . Эта функция точно воспроизводит обработку данных старого коллбэка, включая разрешение групп, проверку данных владельца с помощью strip_tags и очистку неиспользуемых групп. Она сохраняет те же границы безопасности с явным контролем nexus_is_main() и is_super_admin() . После этого клиент Cerber.Hub также отказался от эмуляции API настроек. После этого были удалены CRB_Legacy_Settings_Manager и несколько ныне неактивных вспомогательных функций.
Удаленные формы настроек больше не выводят скрытые поля API настроек, такие как option_page , action=update , _wpnonce или _wp_http_referer . Теперь они содержат те же внутренние поля, что и локальные формы, плюс печать Nexus в удаленном контексте. Аутентификация Nexus Transport остается неизменной и по-прежнему является внешней границей для запросов к управляемым сайтам. nexus_is_valid_request() , nexus_is_granted() и проверка cerber_nexus_seal работают как и раньше. Перенаправленная отправка по-прежнему проверяет nonce плагина перед началом обработки настроек. Мы также добавили проверку точки входа. Отправленный экран настроек, который не ведет к известному экрану, теперь закрывается с ошибкой WP_Error до начала обработки, а сообщение об ошибке включает отправленное значение для диагностики.
Жесткий переход, и это то, что нужно знать после обновления.
Это был преднамеренный жесткий переход. Мы не сохранили переходный путь для двух форматов, и обработка options.php больше не используется. С этим решением связан один особый случай, и его стоит прямо указать. Если форма настроек была сгенерирована предыдущей версией WP Cerber, и вы отправляете ее после обновления, она может не сохраниться в этот раз. В этом случае откройте страницу настроек и отправьте ее снова. Новая сгенерированная форма будет содержать новый внутренний контракт и сохранится нормально. То же самое относится к удаленным формам настроек на управляемых сайтах.
Вместе с миграцией была проведена и оптимизация именования. Перегруженная group терминов была переименована в settings_screen_id везде, где она идентифицировала экран настроек, включая константу, значение скрытого поля формы, сигнатуры функций конвейера и ключи конфигурации. Удаленный код ошибки был переименован из unknown_settings_group в unknown_settings_screen . Одна деталь совместимости важна для авторов дополнений. Полезная нагрузка события update_settings по-прежнему содержит старый ключ group в качестве псевдонима settings_screen_id , поскольку cerber_add_handler() является общедоступной, и внешние обработчики могут ее прочитать.
Основа для UI Factory
В административной панели WP Cerber отображает HTML через внутреннюю фабрику пользовательского интерфейса, а не с помощью встроенной разметки. В этом релизе добавлены два структурных элемента. crb_ui_fragment() создает смешанную, упорядоченную коллекцию дочерних элементов без тега-обертки. crb_ui_element_set() делает то же самое для однородной коллекции с обязательным типом дочернего элемента. Мы переименовали текучий CRB_UI_Fragment_Builder в CRB_UI_Content_Builder , чтобы избежать путаницы с новым узлом фрагмента, и сохранили псевдоним класса, чтобы существующий код продолжал работать.
В результате этого некоторые сайты вызовов стали проще. crb_ui_message_box() теперь принимает обычные строки и числа и оборачивает их в экранированные элементы абзаца, поэтому вызывающим сторонам больше не нужно создавать эти абзацы вручную. Дублированное уведомление об ошибке выборки на панели управления и экран журнала трафика были объединены в один общий вспомогательный класс. Панель диагностики среды была перестроена на новых узлах. В каждом случае отображаемый HTML-код остался неизменным. Это основа для независимого от рендерера административного интерфейса, и сегодня он невидим на странице.
После обновления
Краткий контрольный список для администраторов:
- Если после обновления форма настроек не сохраняется при первой отправке, откройте страницу настроек заново и сохраните изменения еще раз.
- Если WP Cerber когда-либо вернется к настройкам по умолчанию или восстановит их из резервной копии, он сообщит вам об этом через окно административной панели. Проверьте свои настройки и сохраните их, чтобы убрать это уведомление.
- Если на вашем хостинге PHP работает без драйвера mysqlnd, найдите соответствующее уведомление в виджете «Готовность системы». Плагин продолжит работать, а в уведомлении будет указано рекомендуемое изменение.