- 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)
739 lines
42 KiB
Markdown
739 lines
42 KiB
Markdown
---
|
||
name: wordfence
|
||
description: 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, colunas
|
||
`name`/`val`), **não** `wp_options` — `wp option get/patch` não
|
||
funciona para nada disto.
|
||
- 2FA, passkeys, CAPTCHA de login, XML-RPC, "remember device" (módulo
|
||
Login Security) → tabela **separada** `wfls_settings` (colunas
|
||
`name`/`value` — nomes de coluna diferentes de `wfconfig`). 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 de `wfconfig`. 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
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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')
|
||
```
|
||
|
||
```bash
|
||
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) |
|
||
|
||
```bash
|
||
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.
|
||
|
||
```bash
|
||
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 |
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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 |
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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 |
|
||
|
||
```bash
|
||
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`:
|
||
|
||
```bash
|
||
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`):
|
||
|
||
```bash
|
||
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`/`userCanDrop` a `false` → migrações/upgrades do plugin
|
||
podem falhar silenciosamente; verificar antes de reportar "scan não
|
||
actualiza issues".
|
||
- `connectToSelf`/`connectToSelfIpv6` a falhar com sinais de Cloudflare
|
||
(`cf-mitigated: challenge`, `Cloudflare Ray ID` no 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 como
|
||
`WP_DEBUG`, `DISALLOW_FILE_EDIT`, `WP_CACHE`, `FORCE_SSL_ADMIN` — útil
|
||
para confirmar hardening sem abrir `wp-config.php` directamente.
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
```bash
|
||
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.*
|