- emcp-page-building, emcp-content-ops, emcp-site-audit: EMCP Tools MCP workflows verified live (atomic/legacy interplay, apply-template overwrite risk, create-theme-template goes live immediately, false-positive malware pattern in scan-security, change ledger + rollback) - elementor-pro-widgets: 30+5 native Elementor Pro widgets (curated catalog) - elementskit-widgets / powerpack-widgets: 42 + 97 third-party widgets, widgetType extracted from plugin source (not guessed by convention) - plugin.json bumped 1.2.0 -> 1.3.0, keywords + description updated - commits pending wordpress skills already present as untracked files (emcp-tools, wordfence, wp-activity-log, seguranca-descomplicar, webp-express, wp-fastest-cache, wp-font-perf, wp-meteor, wp-activity-log, redis-object-cache, app-for-cloudflare) and pending edits (rank-math, wp-cli, wp-content-seo-gate)
42 KiB
name, description
| name | description |
|---|---|
| wordfence | Auditoria e gestão completa do Wordfence Security via SQL directo em servidores CWP — cobre as 313 chaves de `wfconfig`, a tabela separada `wfls_settings` do módulo Login Security (2FA/passkeys/CAPTCHA/XML-RPC), armazenamento do WAF em ficheiros (`wp-content/wflogs/config*.php`), WAF (modos, categorias de regra, blacklist premium), bloqueio de IPs/países/padrões (UA/referrer/hostname), Live Traffic, os 5 limitadores de Rate Limiting, Diagnostics, Import/Export de config e Wordfence Central. Detecção de IP real atrás do Cloudflare (howGetIPs), scan settings e interacção com plugins de cache. Usar quando "wordfence", "waf wordpress", "firewall wordpress", "2fa wordpress", "passkeys wordpress", "bloqueio de login", "wfconfig", "wfls_settings", "cloudflare real ip wordfence", "howGetIPs", "scan malware wordpress", "brute force wordpress", "rate limiting wordpress", "live traffic wordfence", "wordfence central", "bloqueio de país wordpress". |
/wordfence — Auditoria e Configuração Wordfence via SQL
Gestão do Wordfence Security via wp db query directo no servidor CWP.
A configuração NÃO vive toda no mesmo sítio:
- Grosso da config (WAF nível aplicação, scan, alertas, blocking,
rate limiting) → tabela própria
wfconfig(chave/valor, colunasname/val), nãowp_options—wp option get/patchnão funciona para nada disto. - 2FA, passkeys, CAPTCHA de login, XML-RPC, "remember device" (módulo
Login Security) → tabela separada
wfls_settings(colunasname/value— nomes de coluna diferentes dewfconfig). Ver §6. - Config do motor WAF usada para arrancar antes do WordPress (Extended
Protection/auto-prepend) → ficheiros PHP serializados em
wp-content/wflogs/config*.php, fora de qualquer tabela SQL, sincronizados automaticamente a partir dewfconfig. Ver §10.
Fonte: CONFIG-Plugins-Referencia.md §8 (Wordfence, mapeado
16-08-2026, site piloto emanuelalmeida.pt, versão 9.0.0 gratuita) +
leitura completa do código-fonte do plugin (466 ficheiros PHP) e
verificação SQL/SSH ao vivo feitas nesta sessão — detalhe completo da
cobertura no rodapé "Fonte" no final do documento.
Contexto CWP — sempre obrigatório
PATH=/home/USER/public_html
PREFIX=$(wp db prefix --allow-root --path=$PATH) # ex.: wpne_, wpah_ — nunca assumir wp_
Acesso via SSH: server.descomplicar.pt, porta 9443, user root.
Servidor executa comandos como o utilizador do site: sudo -u USER wp ... --path=$PATH.
Tabela de config real (schema confirmado):
DESCRIBE ${PREFIX}wfconfig;
name varchar(100) PRI
val longblob -- valor (string, número ou JSON serializado)
autoload enum('no','yes')
Não há wp option patch equivalente — é sempre SELECT/UPDATE directo
sobre name/val. 313 linhas de config no site piloto.
Decision tree — qual comando usar
| Operação | Comando |
|---|---|
| Ler 1 chave de config | wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name='...';" |
| Ler várias chaves | SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN (...); |
| Alterar 1 chave (produção) | UPDATE ${PREFIX}wfconfig SET val='...' WHERE name='...'; — preferir sempre a UI do wp-admin para chaves que disparam hooks (wordfence_changed_ip_source, wordfence_changed_license_key) |
| Contar utilizadores com 2FA | SELECT COUNT(*) FROM ${PREFIX}wfls_2fa_secrets; (tabela moderna) — não usermeta (legado, pode ficar desactualizado) |
| Ver todas as tabelas do plugin | SHOW TABLES LIKE '${PREFIX}wf%'; |
| Ver issues de scan pendentes | SELECT COUNT(*) FROM ${PREFIX}wfpendingissues; |
| Ver IPs bloqueados pelo WAF | SELECT COUNT(*) FROM ${PREFIX}wfblockediplog; |
| Confirmar recomendação de proxy do próprio Wordfence | SELECT val FROM ${PREFIX}wfconfig WHERE name='detectProxyRecommendation'; |
| Ler settings do módulo Login Security (2FA/passkeys/CAPTCHA) | SELECT name, value FROM ${PREFIX}wfls_settings ORDER BY name; — tabela diferente, colunas name/value (não val) — ver §6 |
| Ver ficheiros de config do WAF (bootstrap fora do WordPress) | cat wp-content/wflogs/config.php / config-synced.php — não é SQL, é PHP serializado com __halt_compiler() — ver §10 |
1. Auditoria de estado (leitura, sem risco)
PATH=/home/USER/public_html
PREFIX=$(wp db prefix --allow-root --path=$PATH)
# Estado geral do WAF/firewall
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('firewallEnabled','waf_status','disableWAFIPBlocking','isPaid');" --allow-root --path=$PATH
# Login security (rate-limit + 2FA)
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('loginSec_maxFailures','loginSec_countFailMins','loginSec_lockoutMins',
'loginSec_lockInvalidUsers','allowLegacy2FA','loginSec_requireAdminTwoFactor');" --allow-root --path=$PATH
# Quantos utilizadores TÊM 2FA activo de facto (tabela moderna do módulo login-security)
wp db query "SELECT COUNT(*) AS total_2fa FROM ${PREFIX}wfls_2fa_secrets;" --allow-root --path=$PATH
# Detecção de IP real (crítico atrás de proxy/CDN)
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('howGetIPs','howGetIPs_trusted_proxies','howGetIPs_trusted_proxy_preset',
'detectProxyRecommendation');" --allow-root --path=$PATH
# Scans activos
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'scansEnabled_%';" --allow-root --path=$PATH
# Alertas/email
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('alertEmails','email_summary_enabled','liveTrafficEnabled','blockFakeBots','autoUpdate','cacheType');" --allow-root --path=$PATH
# Volume de actividade (contexto do site)
wp db query "SELECT COUNT(*) FROM ${PREFIX}wfblockediplog;" --allow-root --path=$PATH # IPs bloqueados pelo WAF
wp db query "SELECT COUNT(*) FROM ${PREFIX}wfpendingissues;" --allow-root --path=$PATH # issues de scan em aberto
wp db query "SHOW TABLES LIKE '${PREFIX}wf%';" --allow-root --path=$PATH # inventário de tabelas do plugin
Valores reais confirmados (emanuelalmeida.pt, gratuito, 16-08-2026)
| Chave | Valor | Avaliação |
|---|---|---|
firewallEnabled |
1 |
✅ |
waf_status |
enabled |
✅ WAF a funcionar |
disableWAFIPBlocking |
0 |
✅ bloqueio de IP activo (325 IPs bloqueados no log) |
isPaid |
vazio | Versão gratuita — sem WAF em tempo real (updates de regras com atraso), sem scan de reputação de IP premium |
alertEmails |
it@descomplicar.pt |
✅ |
email_summary_enabled |
1 |
✅ |
liveTrafficEnabled |
1 |
✅ |
blockFakeBots |
1 |
✅ bloqueia falsos Googlebot/etc. |
autoUpdate |
1 |
✅ |
cacheType |
disabled |
✅ deliberado — ver §4 |
scansEnabled_core/plugins/themes/malware/diskSpace/suspiciousAdminUsers/wafStatus/... |
todos 1 |
✅ cobertura de scan quase completa |
scansEnabled_highSense |
0 |
Sensibilidade alta de scan desligada — reduz falsos positivos, aceitável |
scansEnabled_scanImages |
0 |
Não faz scan binário a imagens — aceitável, baixo risco típico |
scansEnabled_checkHowGetIPs |
1 |
O próprio Wordfence já tem um scan check dedicado a esta má-configuração (ver §3) |
loginSec_maxFailures / _countFailMins / _lockoutMins |
5 / 60 / 240 |
Bloqueia ao fim de 5 falhas em 60 min, lockout de 4h |
loginSec_lockInvalidUsers |
0 |
Não bloqueia por tentar logins com username inexistente (só falhas de password em conta real) |
allowLegacy2FA |
0 |
✅ não permite métodos 2FA fracos/antigos |
2. GAP real — 2FA não forçado, zero utilizadores protegidos
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name='loginSec_requireAdminTwoFactor';" --allow-root --path=$PATH
# → 0 = 2FA NÃO é obrigatório para administradores
wp db query "SELECT COUNT(*) FROM ${PREFIX}wfls_2fa_secrets;" --allow-root --path=$PATH
# → 0 = zero utilizadores com 2FA activo, incluindo o admin
Achado real (emanuelalmeida.pt): loginSec_requireAdminTwoFactor=0 e
zero linhas em wfls_2fa_secrets — o login continua protegido só por
password + rate-limit (5 tentativas/60min/lockout 4h), sem segundo factor,
mesmo sendo o site pessoal do fundador da empresa.
Forçar 2FA para administradores
Auditoria/leitura sempre segura. Escrita é mutação em produção — confirmar autorização antes de aplicar:
# Ler estado actual antes de alterar
wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='loginSec_requireAdminTwoFactor';" --allow-root --path=$PATH
# Forçar 2FA para o role Administrator
wp db query "UPDATE ${PREFIX}wfconfig SET val='1' WHERE name='loginSec_requireAdminTwoFactor';" --allow-root --path=$PATH
Cuidado operacional: activar isto sem um administrador já ter um
autenticador configurado bloqueia o próprio acesso ao wp-admin no login
seguinte (o Wordfence força a configuração do 2FA no momento do login, não
tranca por fora — mas confirmar sempre com o utilizador antes, e nunca
aplicar em sessão headless sem acesso de recuperação garantido, ex. acesso
SSH/SQL directo para reverter o valor para 0 se algo correr mal).
3. GAP real — howGetIPs vazio atrás do Cloudflare
Este é o achado mais concreto desta auditoria. O próprio Wordfence já tinha calculado a recomendação correcta e guardado numa chave separada, mas nunca a aplicou à chave activa:
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name IN
('howGetIPs','detectProxyRecommendation');" --allow-root --path=$PATH
name val
detectProxyRecommendation HTTP_CF_CONNECTING_IP
howGetIPs (vazio)
detectProxyRecommendation é preenchido automaticamente pelo Wordfence
quando detecta que o tráfego chega via um proxy conhecido (Cloudflare, neste
caso) — mas fica só como sugestão; howGetIPs (a chave que o WAF
realmente usa para decidir qual IP é o "real") continua vazia. Com
howGetIPs vazio, o Wordfence cai no fallback de ordem de headers
(lib/wfUtils.php: HTTP_CF_CONNECTING_IP → HTTP_X_REAL_IP →
REMOTE_ADDR → HTTP_X_FORWARDED_FOR), o que na maioria dos casos até
funciona, mas não é o comportamento explícito/auditável e não activa o
scan check dedicado (scansEnabled_checkHowGetIPs=1 já está ligado — é
precisamente este scan que devia estar a assinalar isto como issue, mas
wfpendingissues está vazia, i.e. o scan periódico pode não ter corrido
recentemente ou o alerta expirou).
Valores válidos de howGetIPs (confirmado no código, wfConfig.php/wfUtils.php)
Cabeçalhos HTTP reconhecidos pelo Wordfence para determinar o IP real:
| Valor | Quando usar |
|---|---|
HTTP_CF_CONNECTING_IP |
Site atrás de Cloudflare — este é o caso do bundle Descomplicar® (todos os sites usam App for Cloudflare®) |
HTTP_X_REAL_IP |
Proxy tipo nginx reverso simples (sem CDN) |
HTTP_X_FORWARDED_FOR |
Load balancer/proxy genérico |
| (vazio) | REMOTE_ADDR directo — só correcto se não houver proxy/CDN nenhum à frente |
Existe ainda howGetIPs_trusted_proxy_preset, que aponta para uma entrada
em ipResolutionList (JSON de ~8KB, sincronizado da API Wordfence, contém
blocos de IP de CDNs conhecidas — ex. cloudflare, cloudfront — usados
para validar que o pedido realmente vem de um proxy confiável antes de
aceitar o header). Consultar presets disponíveis:
wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='ipResolutionList';" --allow-root --path=$PATH \
| python3 -c "import json,sys; print(list(json.load(sys.stdin).keys()))"
Corrigir (copiar a própria recomendação do Wordfence)
# Ler antes de escrever
wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='detectProxyRecommendation';" --allow-root --path=$PATH
# Aplicar — usa o valor que o próprio Wordfence já recomendou
wp db query "UPDATE ${PREFIX}wfconfig SET val='HTTP_CF_CONNECTING_IP' WHERE name='howGetIPs';" --allow-root --path=$PATH
# Confirmar
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name='howGetIPs';" --allow-root --path=$PATH
Sem isto configurado, o rate-limiting de login, os bloqueios de IP do WAF e o Live Traffic podem estar a agir sobre o IP do proxy Cloudflare em vez do IP real do atacante — mais grave em sites com muito tráfego atrás do mesmo edge, onde o "IP" observado pode ser partilhado por múltiplos visitantes.
4. Interacção com cache de página — cacheType=disabled é deliberado
wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='cacheType';" --allow-root --path=$PATH
# → disabled
O Wordfence tem o seu próprio sistema de cache de página interno
(Enable Falcon Engine), mas em sites do bundle Descomplicar® a cache de
página já é feita por outro plugin dedicado (WP Fastest Cache ou
equivalente). cacheType=disabled está correcto — dois sistemas de
cache HTML activos ao mesmo tempo competem, servem versões desalinhadas e
dificultam o purge coordenado. Nunca activar o cache Falcon do Wordfence
num site que já tem WP Fastest Cache/WP Meteor a fazer cache de página —
confirmar sempre qual plugin de cache está activo antes de alterar
cacheType num site novo.
5. Scan settings — cobertura completa
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'scansEnabled_%';" --allow-root --path=$PATH
| Categoria | Chave | Estado (piloto) |
|---|---|---|
| Core WordPress | scansEnabled_core, _coreUnknown, _oldVersions |
1 |
| Plugins/temas | scansEnabled_plugins, _themes |
1 |
| Malware/ficheiros suspeitos | scansEnabled_malware, _fileContents, _fileContentsGSB, _suspectedFiles |
1 |
| Espaço em disco | scansEnabled_diskSpace |
1 |
| Utilizadores admin suspeitos | scansEnabled_suspiciousAdminUsers |
1 |
| Opções de BD suspeitas | scansEnabled_options, _suspiciousOptions |
1 |
| Passwords fracas | scansEnabled_passwds |
1 |
| Comentários/posts (spam/malware injectado) | scansEnabled_comments, _posts |
1 |
| GeoIP / Google Safe Browsing | scansEnabled_geoipSupport, _checkGSB |
1 |
| Config legível externamente | scansEnabled_checkReadableConfig |
1 |
| Detecção de IP mal configurada | scansEnabled_checkHowGetIPs |
1 — ver §3 |
| Estado do WAF | scansEnabled_wafStatus |
1 |
| WPScan (directory listing, full path disclosure) | scansEnabled_wpscan_directoryListingEnabled, _wpscan_fullPathDisclosure |
1 |
| Sensibilidade alta | scansEnabled_highSense |
0 (desligado, reduz falsos positivos) |
| Scan binário de imagens | scansEnabled_scanImages |
0 (desligado) |
Avaliação: cobertura de scan quase total (23 de 25 checks activos), única omissão relevante é a sensibilidade alta — aceitável para reduzir ruído em sites com actividade legítima elevada.
6. Two-Factor / Passkeys / CAPTCHA — tabela SEPARADA wfls_settings
Achado arquitectural importante desta expansão: o módulo "Login
Security" (2FA, passkeys, CAPTCHA de login, XML-RPC, "remember device",
integração WooCommerce, NTP) não guarda a sua configuração em
wfconfig — vive numa tabela própria, wfls_settings, gerida pela
classe WordfenceLS\Controller_Settings (código em
modules/login-security/classes/controller/settings.php). Schema:
DESCRIBE ${PREFIX}wfls_settings;
name varchar(191) PRI
value longtext -- nota: coluna chama-se "value", não "val"
autoload enum('no','yes')
wp db query "SELECT name, value FROM ${PREFIX}wfls_settings ORDER BY name;" --allow-root --path=$PATH
As chaves loginSec_requireAdminTwoFactor e loginSec_enableSeparateTwoFactor
em wfconfig (§2) são a versão legada — um switch global único. A
versão actual do plugin é baseada em papéis (roles):
migrate_admin_2fa_requirements_to_roles() migra o switch legado para
chaves por role (required-2fa-role.<role>) dentro de wfls_settings e
depois apaga a chave legada do wfconfig. Um site "antigo" pode ainda ter
o valor legado; um site já migrado tem-no ausente/zero e a fonte de
verdade passa a ser wfls_settings + capabilities.
Chaves confirmadas em wfls_settings (emanuelalmeida.pt)
| Chave | Valor confirmado | O que controla |
|---|---|---|
xmlrpc-enabled |
1 |
Exigir 2FA também em pedidos XML-RPC autenticados |
allow-xml-rpc |
1 |
Permitir XML-RPC de todo (independente do 2FA) |
ip-source |
vazio (automatic) |
Fonte do IP real para o módulo login-security — independente de howGetIPs do wfconfig (§3); valores: automatic, REMOTE_ADDR, X-Forwarded-For, X-Real-IP |
ip-trusted-proxies |
vazio | Lista de proxies confiáveis (newline-separated) para o cálculo de IP deste módulo |
2fa-user-grace-period |
10 |
Dias de período de graça depois de 2FA se tornar obrigatório por role, antes de bloquear o login |
require-2fa.administrator |
vazio | Legado (ver acima); use required-2fa-role.administrator |
passkey-sign-count-mode |
reject-lower |
Política anti-clonagem de passkeys: allow / reject-lower / reject-lower-and-zero |
passkey-relying-party-override |
vazio | Override do hostname RP para passkeys (multi-domínio) |
remember-device |
vazio (false) |
"Lembrar este dispositivo" para saltar 2FA X dias |
remember-device-duration |
2592000 (30 dias) |
Duração do "remember device" em segundos |
always-show-login-security-menu |
1 |
Mostrar sempre o menu Login Security mesmo sem nada configurado |
enable-auth-captcha |
vazio | CAPTCHA (reCAPTCHA) no formulário de login |
recaptcha-site-key / recaptcha-secret |
vazio | Credenciais reCAPTCHA — só relevantes se enable-auth-captcha=1 |
recaptcha-threshold |
0.5 |
Limiar de pontuação do reCAPTCHA v3 (0–1) |
enable-woocommerce-integration / -account-integration |
vazio | Aplicar 2FA/passkeys também na área de conta WooCommerce |
use-ntp |
1 |
Sincronizar relógio via NTP para códigos TOTP (evita drift do relógio do servidor) |
whitelisted |
vazio | IPs isentos de exigência de 2FA (não confundir com whitelisted do wfconfig, que é do WAF) |
schema-version |
3 |
Versão do schema interno do módulo — não editar manualmente |
Modelo de permissões por role (capabilities, não settings)
"2FA obrigatório" e "2FA opcional" por role não são uma chave de
configuração simples — são geridos como capabilities do WordPress,
sincronizadas por WordfenceLS\Controller_Permissions:
| Capability | Significado |
|---|---|
wf2fa_activate_2fa_self |
O utilizador pode activar/desactivar 2FA na própria conta |
wf2fa_activate_2fa_others |
Pode activar/desactivar 2FA de outros utilizadores |
wf2fa_manage_settings |
Pode editar as definições do módulo Login Security |
wfls_manage_passkey_self / _others |
Equivalente para passkeys |
wfls_show_login_security |
Capability interna que mostra/esconde o menu (sincronizada automaticamente) |
wp eval 'var_export(get_role("administrator")->capabilities);' --allow-root --path=$PATH | grep -i 'wf2fa\|wfls'
# Confirmado em emanuelalmeida.pt: administrator TEM wf2fa_activate_2fa_self (pode activar),
# mas isso é OPCIONAL — só se torna obrigatório se required-2fa-role.administrator tiver timestamp > -1
O estado "obrigatório desde quando" fica em required-2fa-role.<role>
(timestamp Unix) ou -1 (não obrigatório) — chave dinâmica dentro de
wfls_settings, não aparece na lista de defaults porque só existe depois
de alguém a definir pela UI.
Tabelas de dados do módulo (não config, mas relevantes para auditoria)
| Tabela | Conteúdo |
|---|---|
wfls_2fa_secrets |
Segredos TOTP activos por utilizador (contagem = utilizadores com 2FA activo) |
wfls_passkeys |
Passkeys (WebAuthn) registadas por utilizador |
wfls_role_counts |
Cache de contagens de utilizadores por role, usada pela UI |
7. Live Traffic — configuração completa
Todas as chaves vivem em wfconfig (ao contrário do módulo 2FA). Tabela
de dados: wflivetraffichuman (visitas humanas) + wfhits, wfhoover,
wfcrawlers, wflocs, wflogins (dados de suporte ao Live Traffic e ao
dashboard de tráfego).
| Chave | Valor (piloto) | O que controla |
|---|---|---|
liveTrafficEnabled |
1 |
Liga/desliga o registo de Live Traffic |
liveTraf_ignorePublishers |
1 |
Não regista visitas de utilizadores autenticados com capacidade de publicar (reduz ruído do próprio staff) |
liveTraf_ignoreUsers |
vazio | Lista de usernames (separados por vírgula) a ignorar |
liveTraf_ignoreIPs |
vazio | Lista de IPs (separados por vírgula) a ignorar |
liveTraf_ignoreUA |
vazio | User-agent a ignorar (substring) |
liveTraf_maxRows |
2000 |
Quantidade máxima de registos guardados |
liveTraf_maxAge |
30 |
Dias máximos de retenção dos registos |
liveTraf_displayExpandedRecords |
0 |
Preferência de UI (registos expandidos por omissão) — não afecta captura |
Overrides que desligam Live Traffic independentemente da config:
wfConfig::liveTrafficEnabled() força false se a constante
WORDFENCE_DISABLE_LIVE_TRAFFIC estiver definida, ou se
WF_IS_WP_ENGINE for verdadeiro (hosts WP Engine desactivam sempre este
módulo, independentemente do valor em wfconfig) — útil para explicar a
um cliente porque "liguei e não aparece nada" nesse tipo de hosting.
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'liveTraf%' OR name='liveTrafficEnabled';" --allow-root --path=$PATH
8. Blocking completo — País, IP manual, Padrões (UA/Referrer/Hostname)
Todos os bloqueios (manuais e automáticos) vivem numa única tabela,
wfBlocks7 (não em wfconfig), com um campo type que distingue a
natureza do bloqueio (models/block/wfBlock.php):
| Constante | Valor | Origem |
|---|---|---|
TYPE_IP_MANUAL |
1 | Bloqueio de IP manual pelo admin |
TYPE_WFSN_TEMPORARY |
2 | Resposta automática da Wordfence Security Network (rede partilhada entre sites) |
TYPE_COUNTRY |
3 | Bloqueio por país |
TYPE_PATTERN |
4 | Bloqueio avançado (IP range + hostname + user-agent + referrer) |
TYPE_RATE_BLOCK |
5 | Gerado pelo rate limiting (§9), acção block |
TYPE_RATE_THROTTLE |
6 | Gerado pelo rate limiting (§9), acção throttle |
TYPE_LOCKOUT |
7 | Lockout de login (falhas de password/2FA) |
TYPE_IP_AUTOMATIC_TEMPORARY / _PERMANENT |
8 / 9 | Bloqueios automáticos do WAF, temporário vs promovido a permanente por acção do admin |
# Ver todos os bloqueios activos por tipo
wp db query "SELECT type, COUNT(*) FROM ${PREFIX}wfblocks7 WHERE expiration=0 OR expiration>UNIX_TIMESTAMP() GROUP BY type;" --allow-root --path=$PATH
Bloqueio por país
Campos guardados em parameters (JSON): blockLogin (bloquear só a
página de login), blockSite (bloquear o resto do site), countries
(array de códigos ISO). Comportamento configurado globalmente em
wfconfig:
| Chave | Valor (piloto) | O que controla |
|---|---|---|
cbl_action |
block |
O que fazer a um país bloqueado: block (503) ou redirect (redireccionar) |
cbl_redirURL |
vazio | URL de destino se cbl_action=redirect |
cbl_loggedInBlocked |
0 |
Bloquear país mesmo que o visitante já tenha sessão iniciada |
cbl_bypassRedirURL / cbl_bypassRedirDest |
vazio | Um visitante de país bloqueado que aceda a este URL relativo é reencaminhado e recebe um cookie de bypass permanente |
cbl_bypassViewURL |
vazio | Um visitante permitido que veja este URL relativo recebe o cookie de bypass antecipadamente (para o caso de mudar de país/VPN depois) |
cbl_cookieVal |
gerado automaticamente | Valor único do cookie de bypass — regenerar invalida bypasses concedidos |
Nenhum destes está configurado no piloto (site sem bloqueio de país
activo) — confirmar sempre hasCountryBlock()/countryBlocks() antes de
assumir que não há impacto em visitantes internacionais.
Bloqueio manual de IP / IP range
# Confirmar que um IP não está na whitelist antes de o bloquear (a UI faz isto automaticamente)
wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='whitelisted';" --allow-root --path=$PATH
wfBlock::isWhitelisted() verifica dois níveis: a lista manual em
whitelisted (wfconfig, uma entrada por linha, IP/CIDR) e uma lista de
serviços conhecidos (whitelistedServices, JSON — vazio {} no piloto;
quando preenchido guarda ranges de IP de serviços como monitorização
externa que o Wordfence nunca bloqueia mesmo com regras activas).
Bloqueio avançado (Custom Pattern) — hostname / user-agent / referrer
Bloqueio combinável por 4 critérios (pelo menos 1 obrigatório),
type=custom-pattern na validação (wfBlock::validate()):
| Campo | Formato | Exemplo de uso |
|---|---|---|
ipRange |
IP único, CIDR, ou range 1.2.3.4-1.2.3.9 (IPv4 e IPv6 não podem misturar-se no mesmo bloqueio) |
Bloquear uma subnet inteira |
hostname |
wildcard * e . (regex ^[a-z0-9\.\*\-]+$) |
Bloquear por reverse-DNS de um datacenter conhecido |
userAgent |
substring livre | Bloquear scrapers com UA identificável |
referrer |
substring livre | Bloquear tráfego de referrer spam |
wp db query "SELECT id, blockedTime, reason, expiration FROM ${PREFIX}wfblocks7 WHERE type=4 AND (expiration=0 OR expiration>UNIX_TIMESTAMP());" --allow-root --path=$PATH
Como o Wordfence decide não bloquear crawlers do Google
wp db query "SELECT val FROM ${PREFIX}wfconfig WHERE name='neverBlockBG';" --allow-root --path=$PATH
# Confirmado no piloto: neverBlockVerified
Valor de neverBlockBG |
Comportamento |
|---|---|
neverBlockVerified (default, activo no piloto) |
Verifica UA e faz PTR/reverse-DNS lookup para confirmar que é mesmo um IP do Google antes de isentar de bloqueio |
neverBlockUA |
Confia só no User-Agent, sem verificação de IP — menos seguro (fácil de fingir), mas mais barato |
treatAsOtherCrawlers |
Não dá tratamento especial a "Google" — pode ser bloqueado como qualquer outro crawler |
9. Rate Limiting completo — 5 tipos, throttle vs block
models/block/wfRateLimit.php define 5 limitadores independentes,
cada um com um par de chaves <nome> (limiar) + <nome>_action
(throttle ou block). DISABLED como valor do limiar desactiva esse
limitador especificamente.
| Limitador | Chave limiar | Chave acção | Valor confirmado (piloto) | Aplica-se a |
|---|---|---|---|---|
| Global | maxGlobalRequests |
maxGlobalRequests_action |
240 / throttle |
Qualquer pedido, humano ou crawler — se activo, sobrepõe-se aos outros 4 |
| Page views de crawlers | maxRequestsCrawlers |
maxRequestsCrawlers_action |
DISABLED / throttle |
Só User-Agents identificados como crawler (wfCrawl::isCrawler) |
| 404s de crawlers | max404Crawlers |
max404Crawlers_action |
60 / throttle |
Idem, mas só páginas 404 |
| Page views de humanos | maxRequestsHumans |
maxRequestsHumans_action |
DISABLED / throttle |
Visitantes não identificados como crawler |
| 404s de humanos | max404Humans |
max404Humans_action |
60 / throttle |
Idem, mas só páginas 404 |
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'max%Requests%' OR name LIKE 'max404%' OR name='blockedTime';" --allow-root --path=$PATH
No piloto, o rate limit global (maxGlobalRequests=240) está
ACTIVO — qualquer IP (humano ou bot) que exceda 240 pedidos na janela
de 1 minuto (tabela wfTrafficRates, campo eMin) é afectado. Os
limitadores dedicados a crawlers/humanos de page views estão desligados;
só os de 404 (60 num minuto) estão activos, o que é uma configuração
razoável (protege contra scans de 404 sem penalizar navegação normal).
throttle vs block não são só rótulos — têm durações e severidade
diferentes:
throttle→wfBlock::createRateThrottle(), duração fixa de 60 segundos (wfRateLimit::rateLimitThrottleDuration()) — resposta 503 temporária, o visitante volta a conseguir aceder ao fim de 1 minuto.block→wfBlock::createRateBlock(), duração =blockedTime(wfconfig, 300s / 5 min no piloto, configurável) — bloqueio mais longo, e pode ser promovido a permanente manualmente pelo admin na UI de Blocking (TYPE_RATE_BLOCK→TYPE_IP_AUTOMATIC_PERMANENT).
Excepção comum a 404s permitidos: allowed404s (wfconfig, uma
entrada por linha, suporta wildcard *) — URLs que nunca contam para os
limitadores de 404, mesmo que devolvam 404 de facto. Default do piloto:
/favicon.ico, /apple-touch-icon*.png, /*@2x.png,
/browserconfig.xml (ícones/manifestos pedidos por browsers que geram
404 legítimos e não devem disparar rate limiting).
10. WAF — arquitectura de armazenamento, modos e categorias de regra
Achado arquitectural crítico: para o WAF funcionar antes do
WordPress carregar (Extended Protection / auto-prepend), o Wordfence
não pode depender só da base de dados — mantém uma cópia da config
relevante em ficheiros PHP no filesystem, fora de wp_content/plugins:
ls -la wp-content/wflogs/
# config.php — config nuclear do WAF (wafStatus, authKey, versão do formato) — 6 chaves só
# config-synced.php — MIRROR de chaves relevantes do wfconfig + bloqueios activos, sincronizado
# sempre que algo muda (país/IP/pattern blocks, howGetIPs, apiKey, etc.)
# config-livewaf.php — config activa/em uso pelo processo WAF autoprepended
# config-transient.php— cache de dados de ataque (grande, ~1.7 MB no piloto)
# rules.php — ficheiro PHP executável (não serializado) com as regras do WAF, incluído
# directamente no bootstrap; NÃO editar/apagar manualmente
# attack-data.php, ips.php — logs internos de ataques/IPs vistos pelo WAF
Estes ficheiros começam com <?php exit('Access denied'); __halt_compiler(); ?> seguido de dados serializados em PHP — só são
legíveis fazendo substr() a partir do offset de __halt_compiler() e
unserialize(). Nunca editar estes ficheiros manualmente; qualquer
alteração de config deve passar pela UI ou por wfconfig, que depois é
sincronizada automaticamente para estes ficheiros (via
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings(), chamado
sempre que wfBlock::create*() corre).
O motor de armazenamento é escolhido por WFWAF_STORAGE_ENGINE
(waf/bootstrap.php): file por omissão (confirmado no piloto —
ficheiros em wflogs/ existem e têm conteúdo recente), ou mysqli
forçado automaticamente em hosts WP Engine/Flywheel (ou por constante
explícita), caso em que a config do WAF passa a viver numa ligação MySQL
directa (bypass ao $wpdb) em vez de ficheiros.
Modos e níveis do WAF (models/firewall/wfFirewall.php)
| Constante | Valores possíveis | Significado |
|---|---|---|
firewallMode() |
disabled / learning-mode / enabled |
Estado geral; enabled no piloto (§1, waf_status) |
protectionMode() |
basic / extended |
extended requer WFWAF_AUTO_PREPEND activo (ficheiro .user.ini/.htaccess a fazer auto_prepend_file para wordfence-waf.php antes do WordPress arrancar) — sem isto, o WAF só corre depois do muplugins_loaded, mais tarde no ciclo, "Basic Protection" |
ruleMode() |
community / premium |
Depende de isPaid — piloto usa feed community (regras com atraso de propagação face a sites Premium) |
blacklistMode() |
disabled / enabled |
"Real-Time IP Blocklist" — só disponível com isPaid, mesmo que disableWAFBlacklistBlocking=0 |
learningModeStatus() |
false / true / timestamp |
Se em Learning Mode, indica se há mudança automática agendada para Enabled (learningModeGracePeriodEnabled + learningModeGracePeriod) |
Categorias de regra (fail-score) — confirmado no rules.php do piloto
O motor de regras usa 3 categorias de ataque com pontuação de risco
própria ($this->failScores[...] em wflogs/rules.php):
ssh server "grep -o \"failScores\\['[a-z]*'\\]\" .../wp-content/wflogs/rules.php | sort -u"
# failScores['rce'] — Remote Code Execution
# failScores['sqli'] — SQL Injection
# failScores['xss'] — Cross-Site Scripting
Regras individuais podem ser desactivadas via disabledRules — essa
chave vive dentro do storage do WAF (wp-content/wflogs/config.php
no motor file), não em wfconfig; o piloto não tem nenhuma regra
desactivada (chave ausente = todas activas). A "pontuação de segurança"
mostrada no Dashboard (wfFirewall::overallStatus()) combina: % de
regras activas (35%), blacklist premium activa (35%), Extended Protection
(20%), Rate Limiting/Advanced Blocking ligado (10%) — pesado a favor do
WAF (80% do total), com brute-force protection a contar só 20%.
11. Diagnostics (Tools → Diagnostics)
Gerado por lib/wfDiagnostic.php, sem persistência em BD — é sempre
calculado on-demand quando a página carrega. Secções e o que cada uma
verifica:
| Secção | O que reporta |
|---|---|
| Wordfence Status | Versão instalada, versão da base GeoIP, jobs de cron em atraso (>30min) |
| Filesystem | Se o webserver consegue ler/escrever wp-content/plugins/wordfence e wp-content/wflogs |
| Wordfence Config | Teste de escrita/leitura real contra wfconfig (básico e serializado) — falha aqui = sem permissões de escrita na tabela |
| Wordfence Firewall | Auto-prepend activo, motor de storage configurado vs activo, path dos logs, permissões de ficheiros, conteúdo de .htaccess/.user.ini relacionado com o WAF |
| MySQL | Versão do MySQL + privilégios reais do utilizador da BD: SELECT/INSERT/UPDATE/DELETE/CREATE/ALTER/DROP/TRUNCATE (via SHOW GRANTS FOR current_user()) |
| PHP Environment | Versão PHP vs mínimo suportado, dono do processo PHP, suporte OpenSSL/cURL (versões, protocolos), display_errors |
| Connectivity | Ligação de saída a servidores Wordfence (https), ligação de volta ao próprio site (IPv4 e IPv6 — útil para detectar Cloudflare a bloquear o próprio site) |
| Time | Hora da rede Wordfence, hora do servidor, offset NTP, fonte de tempo usada para TOTP (2FA), fuso horário do WordPress |
Casos de uso práticos:
userCanAlter/userCanDropafalse→ migrações/upgrades do plugin podem falhar silenciosamente; verificar antes de reportar "scan não actualiza issues".connectToSelf/connectToSelfIpv6a falhar com sinais de Cloudflare (cf-mitigated: challenge,Cloudflare Ray IDno body) → o próprio Cloudflare está a desafiar o loopback request do Wordfence; relevante para sites do bundle Descomplicar® (todos atrás de Cloudflare via App for Cloudflare®) em diagnóstico de scans que nunca terminam.- A secção "WordPress" (via
wfDiagnostic::getWordpressValues(), não listada acima por ser genérica ao core) expõe constantes comoWP_DEBUG,DISALLOW_FILE_EDIT,WP_CACHE,FORCE_SSL_ADMIN— útil para confirmar hardening sem abrirwp-config.phpdirectamente.
12. Tools → Import/Export de configuração
Não é um ficheiro local (ao contrário de outros plugins do bundle) —
wfImportExportController (lib/wfImportExportController.php) faz
export()/import() através da API do wordfence.com: o export
gera um token remoto (export_options), que depois se cola manualmente
no site de destino (import_options + token). Isto exige que ambos os
sites tenham apiKey válida e liguem para fora ao wordfence.com — não
funciona offline nem é um download/upload de ficheiro local.
O que é exportado: todas as wfConfig::getExportableOptionsKeys()
(essencialmente os grupos checkboxes + otherParams do
$defaultConfig, não o grupo defaultsOnly), o agendamento de scans
(scanSched, serializado), e todos os registos da tabela de bloqueios
(wfBlock::exportBlocks() — inclui bloqueios de país, IP e pattern, mas
não lockouts nem bloqueios automáticos temporários).
Não é adequado para "clonar config para outro site" via SQL directo
(copiar a tabela wfconfig entre sites não é suportado oficialmente e
arrasta chaves específicas da instalação como encKey/longEncKey —
preferir sempre o fluxo de token oficial acima ou reconfigurar via UI).
13. Wordfence Central — gestão remota multi-site
wp db query "SELECT name, val FROM ${PREFIX}wfconfig WHERE name LIKE 'wordfenceCentral%';" --allow-root --path=$PATH
Achado confirmado no piloto: este site ESTÁ ligado ao Wordfence
Central (wordfenceCentralConnected=1), associado a
it@descomplicar.pt desde 27/02/2025
(wordfenceCentralConnectTime=1740679066), com um wordfenceCentralSiteID
próprio (UUID). Central é a consola de gestão remota multi-site do
Wordfence (central.wordfence.com) — permite monitorizar/alertar e (em
planos pagos) configurar Wordfence em vários sites a partir de um único
painel, autenticado via JWT (wordfenceCentralAccessToken,
wordfenceCentralUserSiteAccessToken) trocado através de um par de
chaves pública/privada gerado no site (wordfenceCentralPK,
wordfenceCentralSecretKey).
| Chave relevante | O que é |
|---|---|
wordfenceCentralConnected |
1/0 — liga/desliga a integração |
wordfenceCentralConnectEmail / _ConnectTime |
Quem ligou e quando |
wordfenceCentralPluginAlertingDisabled |
Se 1, silencia alertas duplicados no próprio wp-admin porque já chegam via Central |
wordfenceCentralConfigurationIssue |
Flag interna — 1 indica falha na renovação do token JWT, útil para diagnosticar "Central parou de reportar" |
wordfenceCentralSiteData |
Cache JSON da última resposta da API Central sobre este site |
Widget do dashboard nativo do WordPress:
email_summary_dashboard_widget_enabled (wfconfig, 1 no piloto)
controla o widget "Activity Report" no /wp-admin/index.php (WordPress
Dashboard nativo) — não confundir com o próprio Dashboard do
Wordfence (/wp-admin/admin.php?page=Wordfence, componente Vue sem
chave de config individual para mostrar/esconder secções).
Gotchas / erros comuns
| Sintoma | Causa | Solução |
|---|---|---|
wp option get wordfence_... devolve vazio/erro |
Config do Wordfence não vive em wp_options |
Usar sempre wp db query contra ${PREFIX}wfconfig (colunas name/val) |
SELECT value FROM wfconfig falha (Unknown column 'value') |
A coluna chama-se val, não value |
SELECT name, val FROM ${PREFIX}wfconfig |
Contagem de 2FA por usermeta.meta_key='wordfence_2fa_secret' parece desactualizada |
Essa é a chave legada; o módulo login-security moderno guarda em tabela própria | Preferir SELECT COUNT(*) FROM ${PREFIX}wfls_2fa_secrets |
howGetIPs vazio mas o site parece "funcionar" na mesma |
O Wordfence tem fallback de ordem de headers (HTTP_CF_CONNECTING_IP → HTTP_X_REAL_IP → REMOTE_ADDR → HTTP_X_FORWARDED_FOR) — mascara o problema em vez de o resolver |
Configurar explicitamente howGetIPs, não confiar no fallback silencioso |
wfpendingissues vazia mesmo com scansEnabled_checkHowGetIPs=1 e howGetIPs mal configurado |
O scan periódico pode não ter corrido recentemente, ou o alerta já expirou/foi dispensado | Não usar wfpendingissues vazia como prova de "nada errado" — cruzar sempre com leitura directa da config |
Activar loginSec_requireAdminTwoFactor sem aviso prévio |
Bloqueia o fluxo normal de login até o admin configurar um autenticador | Confirmar com o utilizador antes; garantir acesso alternativo (SSH/SQL) para reverter |
Prefixo de tabela assumido como wp_ |
Sites CWP podem ter prefixo custom (confirmado wpne_ no piloto) |
wp db prefix --allow-root --path=$PATH sempre primeiro |
Editar 2FA/passkeys/CAPTCHA via UPDATE ${PREFIX}wfconfig sem efeito |
Essas definições vivem em wfls_settings (colunas name/value, não name/val) — ver §6 |
UPDATE ${PREFIX}wfls_settings SET value='...' WHERE name='...'; |
Editar regras/bloqueios do WAF directamente em wp-content/wflogs/*.php |
São ficheiros PHP serializados com __halt_compiler(), regenerados a partir da BD — editar à mão desalinha-os da BD e pode partir o WAF |
Alterar sempre via wfconfig/UI; a sincronização para ficheiro é automática (§10) |
Achar que loginSec_requireAdminTwoFactor=0 prova que 2FA nunca é obrigatório |
Essa chave é legada; sites migrados usam required-2fa-role.<role> dentro de wfls_settings |
Verificar sempre required-2fa-role.* e as capabilities wf2fa_* do role antes de concluir (§6) |
Assumir que maxRequestsHumans/maxRequestsCrawlers="DISABLED" significa "sem rate limiting nenhum" |
Cada um dos 5 limitadores (§9) é independente — maxGlobalRequests pode estar activo e a aplicar-se a toda a gente mesmo com os outros 4 desligados |
Verificar sempre os 5 pares <limite>/<limite>_action, não só um |
Fonte: CONFIG-Plugins-Referencia.md §8 (Wordfence) + verificação
SQL/SSH ao vivo em emanuelalmeida.pt (16-08-2026, WP 7.0.4/PHP 8.2.31,
Wordfence 9.0.0 gratuito) — schema de wfconfig (313 chaves confirmadas
via SELECT DISTINCT name), contagem real de 2FA/passkeys,
detectProxyRecommendation, valores de scan e login security. Expandido
nesta sessão com leitura completa do código-fonte do plugin (466
ficheiros PHP, ~147k linhas) — lib/wfConfig.php (schema completo de
defaults, 3 grupos: checkboxes/otherParams/defaultsOnly),
lib/wfJavascriptBridge.php (labels humanos de todas as opções da UI),
models/block/wfBlock.php e wfRateLimit.php (tipos de bloqueio e os 5
limitadores de rate limiting), models/firewall/wfFirewall.php (modos e
scoring do WAF), lib/wfDiagnostic.php (secções de diagnóstico),
lib/wfImportExportController.php (mecanismo real de import/export via
token wordfence.com), lib/wfCentralAPI.php (integração Central) e o
módulo modules/login-security/ completo — incluindo a descoberta de que
2FA/passkeys/CAPTCHA vivem numa tabela SQL separada (wfls_settings,
colunas name/value) e que o WAF mantém uma cópia de config em
ficheiros PHP serializados (wp-content/wflogs/config*.php), fora de
wfconfig. Confirmado ao vivo: wfls_settings completa, capabilities
wf2fa_*/wfls_* do role administrator, ficheiros WAF (config.php,
config-synced.php, rules.php), categorias de regra (sqli/xss/rce)
e ligação activa a Wordfence Central desde 27/02/2025.