Under the Hood of WP Cerber 9.9
English version: Under the Hood of WP Cerber 9.9
Esta versão foca-se principalmente nas partes do WP Cerber que você só nota quando algo dá errado. Dedicamos tempo a reforçar a forma como as configurações são armazenadas e recuperadas, como o Inspetor de Tráfego lê JavaScript ofuscado e como o plugin se comporta em servidores mais antigos. Também concluímos uma longa migração da API de Configurações do WordPress. Veja a seguir o que mudou internamente e o que isso significa para os seus sites.
Configurações que permanecem mesmo após a corrupção do banco de dados
O WP Cerber mantém sua configuração em uma única opção armazenada, CERBER_CONFIG . Até então, se esse valor fosse corrompido e não pudesse mais ser desserializado, o plugin passava o resultado corrompido diretamente para array_merge() . No PHP 8, isso causava um erro fatal TypeError durante o carregamento do plugin. Como a falha ocorria durante o carregamento, todo o site ficava fora do ar, e não apenas as telas de administração do plugin.
crb_get_settings() agora valida o valor retornado por crb_unserialize() antes de utilizá-lo. Quando os dados armazenados não podem ser analisados e convertidos em um array, o plugin retorna às suas configurações padrão em vez de apresentar uma falha. Ele registra a falha como um problema crítico persistente através de CRB_Issues::add() sob o código ` corrupted_settings . Esse problema se resolve automaticamente na próxima vez que você salvar as configurações, o que é executado dentro de cerber_settings_update() .
Tivemos o cuidado de separar dois casos que parecem semelhantes, mas não são. Um valor armazenado vazio ou inválido significa simplesmente que as configurações ainda não existem. Esse valor nunca é desserializado e o plugin retorna aos valores padrão silenciosamente, sem gerar nenhum alerta. Reportá-lo seria um alarme falso. O problema corrupted_settings agora ocorre apenas quando um valor armazenado não vazio não é desserializado em um array, que é exatamente a condição por trás da falha original em produção.
Duas ramificações próximas na mesma função receberam a mesma disciplina. A mesclagem de compatibilidade com o complemento Cloudflare agora requer um array antes de ser mesclada. A ramificação CERBER_WP_OPTIONS sempre retorna um array quando nenhuma configuração específica é solicitada.
Um sistema de autocura adicional sobre a proteção.
Recorrer às configurações padrão mantém o site em funcionamento, mas também descarta a configuração do administrador. Por isso, adicionamos uma camada de recuperação acima da proteção. Uma nova classe, CRB_Settings_Backup , mantém uma cópia válida da última configuração conhecida do CERBER_CONFIG no próprio armazenamento de chave-valor do plugin.
O backup é armazenado como JSON bruto, juntamente com o ID do usuário que o criou, um registro de data e hora e o contexto em que foi gerado. Ele é atualizado após uma atualização de configurações bem-sucedida, após uma importação de configurações, após uma atualização de plugin e pela tarefa de manutenção diária. Somente uma configuração válida e um código de contexto conhecido podem substituir um backup existente.
Quando o detector de corrupção encontra um CERBER_CONFIG ilegível, a recuperação é executada automaticamente. Em caso de restauração bem-sucedida, o plugin registra um aviso que pode ser descartado, explicando o ocorrido e solicitando que você revise e salve suas configurações. Esse aviso desaparece após a conclusão do procedimento. Se não houver um backup utilizável ou se o valor restaurado não puder ser gravado, o plugin mantém seu comportamento de último recurso e retorna às configurações padrão. Nesse caso, ele registra um problema crítico em vez do aviso.

WP Cerber settings recovery flow. Missing settings quietly fall back to defaults, while corrupted settings trigger automatic recovery from the last-known-valid backup.
Para garantir uma recuperação correta, era necessário respeitar o cache do WordPress. A função recover() exclui a opção CERBER_CONFIG corrompida antes de gravar o valor restaurado. Sem essa etapa, um cache de opções desatualizado poderia fazer com que a função update_site_option() tratasse o valor como inalterado e ignorasse a gravação no banco de dados. O próprio backup é lido e gravado com o cache de objetos ignorado, portanto, apenas o registro cerber_sets permanente é considerado confiável. O conjunto de códigos de contexto aceitos é fechado. O gravador, sync() e o validador de payload aceitam apenas os quatro códigos declarados, de modo que cada payload armazenado possa passar pela mesma validação que a recuperação realizará posteriormente.
Chega de erros fatais em hosts sem mysqlnd
O WP Cerber lê do banco de dados através do mysqli. Um dos caminhos de recuperação, CRB_Database::fetch_result_set() , utiliza mysqli_result::fetch_all() para obter um conjunto de resultados completo em uma única chamada. Esse método só existe quando a extensão mysqli do PHP é compilada com o driver mysqlnd. Em hosts onde o mysqli é compilado com a biblioteca libmysqlclient mais antiga, o método está ausente e chamá-lo resulta em um erro fatal.
A correção segue um padrão já utilizado em outras partes de cerber-common.php . Antes de executar o caminho rápido, o código agora verifica se function_exists('mysqli_fetch_all') está disponível. Quando essa função não está disponível, o conjunto de resultados é lido linha por linha com mysqli_result::fetch_array() , tanto para os formatos MYSQLI_ASSOC quanto para MYSQLI_NUM . A ordem das linhas, a estrutura do resultado, a limpeza e o contrato de retorno Revalt existente são preservados. O método alternativo retorna os mesmos dados que o caminho rápido.
Também fornecemos aos administradores uma maneira de saber quando estão usando o sistema de fallback. Um novo detector no CRB_Issue_Monitor gera um aviso, db_driver_no_mysqlnd , quando a extensão mysqli está carregada, mas mysqli_fetch_all() está ausente. O aviso aparece no widget de Preparação do Sistema. É informativo. Ele confirma que o plugin continua funcionando mesmo com o sistema de fallback e recomenda habilitar o mysqlnd para melhor compatibilidade e desempenho.
Detecção mais precisa de JavaScript ofuscado
CRB_JS_Detector inspeciona os campos de requisição como parte do Traffic Inspector, que está habilitado por padrão. Ele procura por JavaScript que tenha sido ofuscado para ocultar primitivas como eval , script e XMLHttpRequest . O objetivo do projeto é uma detecção de alta confiabilidade com uma baixa taxa de falsos positivos. O detector decodifica apenas entradas que sejam inequivocamente strings codificadas, e ignora todo o resto. Diversas alterações nesta versão corrigiram uma vulnerabilidade real de bypass e ampliaram o escopo da leitura do detector.
Uma regressão que permitia a passagem de strings totalmente escapadas em hexadecimal.
A versão foi lançada com uma correção de bug. A heurística de escape hexadecimal normalizava cada string correspondente com trim( $m, "\\'\"" ) . trim() do PHP trata seu segundo argumento como um conjunto de caracteres a serem removidos, e não como um prefixo literal. A barra invertida nesse conjunto removia a barra invertida inicial do primeiro escape \xNN , juntamente com a aspa de abertura. O que restava tinha um comprimento ímpar de 2N + 1 Uma verificação de comprimento ímpar então decidia que a string não estava codificada em hexadecimal corretamente e ignorava a decodificação por completo. O resultado prático era que uma string construída exclusivamente com escapes \xNN passava despercebida pela inspeção, e primitivas com escape hexadecimal, como eval , script e XMLHttpRequest , não eram detectadas no caminho padrão do campo de requisição. A correção restaura a decodificação correta de strings totalmente escapadas em hexadecimal.
Cobertura mais ampla de caracteres de escape e códigos de caracteres.
Em seguida, ampliamos o que o detector entende. A heurística de escape costumava reconhecer apenas \xNN . Agora, ela também lida com escapes \uNNNN e \u{...} , incluindo strings que misturam esses formatos, mantendo a regra de que apenas strings totalmente escapadas são inspecionadas. A decodificação foi movida para uma função auxiliar dedicada, cerber_decode_js_escapes() , que decodifica pontos de código ASCII e preserva todo o resto. Se ocorrer um erro PCRE interno, essa função auxiliar retorna a entrada original em vez de uma string vazia, portanto, uma falha na decodificação não pode apagar o valor que está sendo verificado.
A heurística de código de caracteres também mudou. Anteriormente, ela examinava a saída decodificada apenas em busca de URLs externas e endereços IP. Agora, ela também procura por primitivas de execução e DOM. Ela decodifica números apenas a partir de uma construção explícita fromCharCode(...) em vez de qualquer matriz numérica que encontrar. Ambas as heurísticas agora compartilham um padrão primitivo com reconhecimento de token e limites de identificador. Essa mudança impede que palavras comuns, como description e evaluation correspondam às substrings eval ou script dentro delas.
Literais inteiros encapsulados
fromCharCode foi definida para aplicar `ToUint16` a cada argumento. Isso significa que um código ASCII somado a qualquer múltiplo de 65536 produz o mesmo caractere. Uma versão anterior da heurística aceitava apenas literais de até seis dígitos, portanto, um valor com sete dígitos encapsulados passava despercebido. Por exemplo, 1048677 é decodificado para a unidade de código 101, a letra " e . O detector agora aceita literais decimais sem sinal e hexadecimais de até Number.MAX_SAFE_INTEGER e reduz cada um à sua unidade de código `ToUint16`, independentemente do tamanho do inteiro do PHP. Ele rejeita decimais com zero à esquerda, pois são ambíguos com o sistema octal legado. Ele limita o processamento por dígito, de modo que um literal muito longo em uma requisição pública não possa gerar computação ilimitada.
Uma correção subsequente resolveu uma lacuna relacionada. Retornar uma string vazia no primeiro argumento não suportado eliminava toda a chamada decodificada. Um atacante poderia adicionar um único token fora do intervalo a uma carga útil detectável e suprimir a detecção. Um desses tokens é 9007199254741024 , que o JavaScript mapeia para um espaço usando ToUint16. Agora, um token estruturalmente válido, mas não suportado, é substituído por um caractere de sublinhado (underscore) como sentinela, e o restante da chamada ainda é decodificado. O sublinhado é um caractere alfanumérico, portanto, também impede a formação de um limite de identificador falsificado próximo a uma palavra-chave.
Argumentos separados por comentários
A última classe de bypass utilizava comentários em JavaScript. A heurística compactava a entrada removendo espaços em branco, o que deixava os comentários intactos. Um comentário inserido entre argumentos numéricos quebrava a correspondência da lista numérica, de modo que uma chamada como String.fromCharCode(101,/*x*/118,97,108,40,49,41,59) evitava a detecção. O detector agora remove comentários de bloco e de linha da lista de argumentos fromCharCode antes de validá-la e decodificá-la, preservando o conteúdo de strings entre aspas. A captura também é executada no modo `dotall`, portanto, uma lista de argumentos que abrange várias linhas é lida como uma única chamada.
Desativação da API de Configurações do WordPress
As páginas de configurações do WP Cerber foram construídas com base na API de Configurações do WordPress. Os formulários eram enviados para /wp-admin/options.php e o plugin utilizava as register_setting() , add_settings_section() , add_settings_field() , settings_fields() e do_settings_sections() para registrá-los e renderizá-los. Isso funcionava, mas vinculava a interface administrativa do plugin a um subsistema procedural do WordPress e suas convenções. Esta versão completa a migração para um mecanismo de formulários próprio do plugin, e fizemos isso em etapas, em vez de uma única alteração.
Primeiro, definimos um limite. Todas as chamadas diretas à API de Configurações foram movidas para uma classe estática, CRB_Legacy_Settings_Manager . Uma análise do código-fonte revelou que apenas cinco das dez funções da API de Configurações eram utilizadas, distribuídas em seis pontos de chamada em dois arquivos. As outras cinco nunca eram chamadas, portanto, a classe de limite não possui métodos para elas. Essa etapa preservou o comportamento exatamente. Nomes de opções, grupos de opções, IDs de seção e campo, callbacks, tempo de execução dos hooks e saída permaneceram inalterados.
Em seguida, criamos a substituição. Uma nova classe, CRB_Settings_Renderer , renderiza seções e linhas de campos diretamente da configuração declarativa retornada por cerber_settings_config() . Os formulários de configurações agora enviam dados de volta para a página de administração do próprio plugin, em vez de options.php . Os envios são processados pelo pipeline existente em admin_init e, em seguida, seguidos por um POST-redirect-GET de volta para a página de configurações. A marcação renderizada é idêntica em nível de byte à produzida anteriormente por do_settings_sections() , incluindo os títulos, blocos de seção e linhas de tabela do formulário.
A verificação do nonce foi alterada. Em vez de usar check_admin_referer() em um grupo de opções da API de Configurações, o plugin agora verifica seu próprio campo cerber_nonce . Esse campo já estava presente em todos os formulários de configurações, incluindo os renderizados por versões anteriores. Um nonce inválido ou expirado não interrompe mais a requisição com wp_die() . Em vez disso, uma notificação administrativa é exibida e o usuário é redirecionado para o formulário.
A remoção do arquivo options.php também eliminou um comportamento legado discreto. O caminho antigo gravava uma cópia bruta de cada grupo de formulário em uma opção por grupo chamada cerber-{group} . Essas cópias eram lidas apenas pela migração para versões anteriores à 9.3.4 e pela limpeza de desinstalação, nunca em tempo de execução. Elas não são mais gravadas.
Cerber.Hub e sites gerenciados
Dois contextos mantiveram o caminho antigo por mais algum tempo, pois ambos dependiam do formato de comunicação da API de Configurações. Um deles era a renderização remota do Cerber.Hub, onde o protocolo entre um site principal e seus sites gerenciados transportava os campos option_page e _wpnonce . O outro era a tela de edição do site gerenciado, que salvava as opções por meio de um filtro pre_update_option disparado pelo options.php . Migramos cada um deles sequencialmente.
O formulário de edição do site gerenciado agora é renderizado por meio do CRB_Settings_Renderer e salvo por meio de uma nova função, nexus_save_client_data_form() . Essa função reproduz exatamente o tratamento de dados do antigo callback, incluindo a resolução de grupos, a limpeza dos detalhes do proprietário strip_tags e a limpeza de grupos não utilizados. Ela mantém o mesmo limite de segurança com uma verificação explícita nexus_is_main() e is_super_admin() . O cliente Cerber.Hub também deixou de emular a API de Configurações. Depois disso, CRB_Legacy_Settings_Manager e vários outros helpers obsoletos foram removidos.
Os formulários de configurações remotas não emitem mais os campos ocultos da API de Configurações, como option_page , action=update , _wpnonce ou _wp_http_referer . Agora, eles contêm os mesmos campos internos que os formulários locais, além do selo Nexus no contexto remoto. A autenticação de transporte Nexus permanece inalterada e continua sendo o limite externo para solicitações de sites gerenciados. nexus_is_valid_request() , nexus_is_granted() e a comunicação bidirecional cerber_nexus_seal funcionam como antes. Um envio encaminhado ainda verifica o nonce do plugin antes que qualquer processamento de configurações seja executado. Também adicionamos uma verificação de ponto de entrada. Uma tela de configurações enviada que não resolve para uma tela conhecida agora falha fechada com um WP_Error antes do início do processamento, e a mensagem de falha inclui o valor enviado para diagnóstico.
Uma transição brusca e uma coisa a saber após a atualização.
Esta foi uma transição completa e deliberada. Não mantivemos um caminho de transição com formato duplo e nenhum processamento options.php foi mantido. Existe um caso excepcional com essa decisão, e vale a pena esclarecê-lo. Se um formulário de configurações foi renderizado pela versão anterior do WP Cerber e você o enviar após a atualização, ele poderá falhar ao ser salvo nessa primeira tentativa. Quando isso acontecer, abra a página de configurações novamente e envie-a outra vez. O formulário recém-renderizado contém o novo contrato interno e será salvo normalmente. O mesmo se aplica a formulários de configurações remotos em sites gerenciados.
Uma limpeza de nomes acompanhou a migração. O group de termos sobrecarregado foi renomeado para settings_screen_id em todos os lugares onde identificava uma tela de configurações, incluindo a constante, o valor do campo oculto do formulário, as assinaturas da função do pipeline e as chaves de configuração. O código de erro remoto foi renomeado de unknown_settings_group para unknown_settings_screen . Um detalhe de compatibilidade é importante para os autores de complementos. A carga útil do evento update_settings ainda carrega a antiga chave group como um alias de settings_screen_id , porque cerber_add_handler() é público e manipuladores externos podem lê-lo.
trabalho básico da UI Factory
Nas telas administrativas, o WP Cerber renderiza HTML por meio de uma UI Factory interna, em vez de usar marcação inline. Esta versão adicionou dois blocos de construção estruturais. A crb_ui_fragment() produz uma coleção mista e ordenada de elementos filhos sem tag de encapsulamento. A crb_ui_element_set() faz o mesmo para uma coleção homogênea com um tipo de elemento filho definido. Renomeamos a classe fluente CRB_UI_Fragment_Builder para CRB_UI_Content_Builder para evitar confusão com o novo nó de fragmento e mantivemos um alias de classe para que o código existente continue funcionando.
Algumas chamadas foram simplificadas como resultado. crb_ui_message_box() agora aceita strings e números simples e os envolve em elementos de parágrafo com caracteres de escape, eliminando a necessidade de os usuários criarem esses parágrafos manualmente. O aviso de erro de busca duplicado no painel de controle e na tela de registro de tráfego foi reunido em um único auxiliar compartilhado. O painel de diagnóstico do ambiente foi reconstruído nos novos nós. Em todos os casos, o HTML renderizado permanece inalterado. Isso prepara o terreno para uma interface de administração independente do renderizador e, atualmente, está invisível na página.
Após a atualização
Uma breve lista de verificação para administradores:
- Se um formulário de configurações não for salvo na primeira vez que você o enviar após a atualização, abra a página de configurações novamente e salve-o.
- Se o WP Cerber alguma vez voltar às configurações padrão ou restaurá-las a partir de um backup, você será notificado por meio de uma mensagem de aviso no painel de administração. Revise suas configurações e salve-as para remover a notificação.
- Se o seu servidor estiver executando PHP sem o driver mysqlnd, procure o aviso no widget de Preparação do Sistema. O plugin continua funcionando e o aviso explica a alteração recomendada.