Pré-anúncio: correção de segurança do Plone 20261110
Uma vulnerabilidade de execução remota de código afeta todas as versões do Plone com suporte. A correção sai em 10 de novembro de 2026, mas já é possível se proteger agora, bloqueando as URLs que contêm portal_modifier.
Em nome do Plone/Zope Security Team, foi feito o pré-anúncio de uma correção para uma vulnerabilidade de execução remota de código. Ela afeta todas as versões do Plone com suporte e muitas versões anteriores, como o Plone 4.3.
A correção será lançada na terça-feira, 10 de novembro de 2026, por volta das 13h UTC (10h no horário de Brasília). Ela vale para todas as versões do Plone 6 (6.0, 6.1 e 6.2). As versões mais antigas não têm suporte.
Até lá, a recomendação é configurar uma medida de contorno, que funciona em qualquer versão do Plone e continua protegendo o site depois que os detalhes forem publicados: bloquear o acesso a URLs que contêm portal_modifier. Essas URLs não são necessárias no uso cotidiano do Plone.
Hoje em dia, uma inteligência artificial pode ser capaz de criar um exploit só com essas informações. Por isso, não deixe para a última hora: bloqueie a URL agora.
Como bloquear o portal_modifier
Pela aquisição do Zope, o portal_modifier pode ser acessado em qualquer nível do caminho, então o bloqueio deve valer para qualquer posição, não só para a raiz:
- https://dominio.tld/portal_modifier/...
- https://dominio.tld/pasta/pagina/portal_modifier/...
O Apache, o nginx e o Traefik decodificam os caracteres codificados com % antes de comparar, e por isso uma variante como /%70ortal_modifier também é bloqueada. O Varnish não faz isso; veja a ressalva na seção dele.
Apache
Se você encaminha as requisições para o Plone com o mod_rewrite (a configuração mais comum), adicione uma regra antes da sua RewriteRule de proxy:
RewriteEngine On
# Bloqueia portal_modifier em qualquer ponto do caminho (a aquisição do Zope o torna acessível em qualquer nível)
RewriteRule portal_modifier - [F,L]
# ... a sua regra de proxy existente, por exemplo
RewriteRule ^/(.*)$ http://127.0.0.1:8080/VirtualHostBase/https/%{SERVER_NAME}:443/Plone/VirtualHostRoot/$1 [P,L]Também é possível usar o mod_authz em vez das regras de reescrita:
<LocationMatch "portal_modifier">
Require all denied
</LocationMatch>As duas formas retornam 403. Para retornar 404, use RewriteRule portal_modifier - [R=404,L].
nginx
server {
# ...
# Locations com expressão regular têm precedência sobre prefixos simples como "location /"
location ~ portal_modifier {
return 403; # ou 404
}
location / {
proxy_pass http://plone;
# ...
}
}O nginx verifica as locations com expressão regular na ordem em que aparecem e usa a primeira que combinar. Se houver outros blocos location ~ ..., coloque este primeiro. Confira também se nenhuma location de prefixo usa ^~, porque isso pula a verificação das expressões regulares. A variável $uri é decodificada e normalizada (barras duplas são unidas e .. é resolvido), então truques como //portal_modifier também são bloqueados.
Traefik
O Traefik não tem um middleware de negação, e a solução usual é um ipAllowList que só aceita um endereço que nunca será a origem de uma conexão real. Assim, toda requisição recebe 403. Coloque esse middleware num router de alta prioridade.
Use um endereço da faixa TEST-NET, e não 127.0.0.1. Por padrão, o ipAllowList verifica o endereço de origem da conexão TCP, então qualquer coisa na mesma máquina passaria por uma lista que aceita 127.0.0.1: outro Traefik que encaminha para este pelo localhost, verificações de saúde e chamadas locais com curl.
Não adicione um ipStrategy (depth ou excludedIPs) a esse middleware. Com ele, o Traefik passa a ler o cabeçalho X-Forwarded-For, que pode ser manipulado pelo cliente.
Traefik v3, file provider (configuração dinâmica)
http:
middlewares:
deny-all:
ipAllowList:
sourceRange:
- "192.0.2.1/32" # TEST-NET-1 (RFC 5737), nunca é uma origem real
routers:
block-portal-modifier:
rule: "Host(`dominio.tld`) && PathRegexp(`portal_modifier`)"
priority: 10000 # precisa vencer o seu router normal
middlewares:
- deny-all
service: plone # nunca é alcançado, mas um service é obrigatório
entryPoints:
- websecure
tls: {}
plone:
rule: "Host(`dominio.tld`)"
service: plone
entryPoints:
- websecure
tls: {}Traefik v3, labels do Docker
labels:
- "traefik.http.middlewares.deny-all.ipallowlist.sourcerange=192.0.2.1/32"
- "traefik.http.routers.block-pm.rule=Host(`dominio.tld`) && PathRegexp(`portal_modifier`)"
- "traefik.http.routers.block-pm.priority=10000"
- "traefik.http.routers.block-pm.middlewares=deny-all"
- "traefik.http.routers.block-pm.service=plone"
- "traefik.http.routers.block-pm.entrypoints=websecure"
- "traefik.http.routers.block-pm.tls=true"O PathRegexp não é ancorado, então combina com o portal_modifier em qualquer nível do caminho.
Traefik v2
No Traefik v2, o middleware se chama ipWhiteList, e não existe PathRegexp. Use esta regra:
Host(`dominio.tld`) && PathPrefix(`/{p:.*portal_modifier.*}`)Vários sites no mesmo Traefik
Na discussão do anúncio, Jens Klein lembrou que, com vários sites atrás de um mesmo Traefik, não é preciso um router de bloqueio por domínio: um router só com PathRegexp, sem Host, vale para todos os sites de uma vez, inclusive os que forem adicionados depois. Na configuração dinâmica:
http:
middlewares:
deny-all:
ipAllowList:
sourceRange:
- "192.0.2.1/32" # TEST-NET-1, nunca é um cliente real
routers:
block-portal-modifier:
rule: "PathRegexp(`portal_modifier`)"
priority: 10000
entryPoints:
- websecure
middlewares:
- deny-all
service: noop@internal
tls: {}- noop@internal é o service fictício embutido no Traefik, e assim não é preciso apontar para um backend real.
- Confira se 10000 é maior que qualquer priority que você já tenha definido.
- Se o seu entrypoint HTTP sem TLS serve sites, em vez de só redirecionar para HTTPS, adicione um segundo router para ele, sem tls.
- Com o file provider monitorando os arquivos (o padrão), a mudança é aplicada sem reiniciar o Traefik.
Com labels do Docker ou do Swarm, coloque-as no próprio serviço do Traefik (no Swarm, em deploy.labels, junto com a label de restrição, se você filtra por uma):
- "traefik.enable=true"
- "traefik.http.middlewares.deny-all.ipallowlist.sourcerange=192.0.2.1/32"
- "traefik.http.routers.block-pm.rule=PathRegexp(`portal_modifier`)"
- "traefik.http.routers.block-pm.priority=10000"
- "traefik.http.routers.block-pm.entrypoints=websecure"
- "traefik.http.routers.block-pm.tls=true"
- "traefik.http.routers.block-pm.middlewares=deny-all"
- "traefik.http.routers.block-pm.service=noop@internal"No Kubernetes, com os CRDs do Traefik, um único IngressRoute protege todos os domínios do cluster, publicados por Ingress ou por IngressRoute:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: deny-all
namespace: traefik
spec:
ipAllowList:
sourceRange:
- 192.0.2.1/32
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: block-plone-portal-modifier
namespace: traefik
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: PathRegexp(`portal_modifier`)
priority: 10000
middlewares:
- name: deny-all
services:
- kind: TraefikService
name: noop@internal
tls: {}Isso exige o provider de CRDs do Traefik, que o chart Helm e o k3s habilitam por padrão. Use o namespace em que o seu Traefik roda (no k3s, o padrão é kube-system) e mantenha os dois objetos no mesmo namespace.
Várias instâncias do Traefik
Se um Traefik encaminha para outro, coloque o bloqueio no que está exposto à internet. Repetir a regra no interno é uma defesa extra que custa pouco, e, com o endereço TEST-NET, o bloqueio também funciona lá, qualquer que seja a origem da requisição.
Varnish
sub vcl_recv {
if (req.url ~ "portal_modifier") {
return (synth(403, "Access Denied"));
}
# ...
}Ressalva: ao contrário dos servidores acima, o Varnish não decodifica req.url, e variantes codificadas como /%70ortal_modifier passam. O Zope decodifica essas URLs, e elas chegam ao portal_modifier. Conte com a regra no servidor da frente (Apache, nginx ou Traefik) para esses casos e trate esta como uma camada extra.
Cloudflare
Na discussão do anúncio, Kim Nguyen mostrou como fazer o bloqueio com as regras do Cloudflare. No painel do domínio:
- Clique em Security, que abre um submenu.
- Escolha Security rules.
- Clique em Create rule e selecione Custom rules.
- Dê um nome à regra, por exemplo "Block portal_modifier", e preencha:
- Field: URI Path
- Operator: contains
- Value: portal_modifier
- Em Then take action, escolha Block.
- Clique em Deploy.
Caddy
Para o Caddy, a discussão sugeriu esta configuração:
plone.example.com {
@block_plone_rce {
path_regexp portal_modifier
}
respond @block_plone_rce "Forbidden" 403
reverse_proxy 127.0.0.1:8080
}Como conferir o bloqueio
Teste de fora do servidor. As três primeiras chamadas devem retornar 403, e a última, 200:
curl -s -o /dev/null -w '%{http_code}\n' https://example.org/portal_modifier # 403
curl -s -o /dev/null -w '%{http_code}\n' https://example.org/a/b/portal_modifier/c # 403
curl -s -o /dev/null -w '%{http_code}\n' https://example.org/portal%5Fmodifier # 403
curl -s -o /dev/null -w '%{http_code}\n' https://example.org/ # 200Observações
- Falsos positivos: a busca pelo trecho também bloqueia qualquer conteúdo cujo id contenha portal_modifier, como uma página chamada sobre-portal_modifier. Em geral isso não é problema. Para ser mais restritivo, use /portal_modifier(/|$), mas a busca ampla é mais segura.
- Maiúsculas e minúsculas: os ids do Zope diferenciam maiúsculas de minúsculas, e Portal_Modifier não leva ao mesmo objeto. Basta comparar diferenciando maiúsculas.
- Acesso direto: essas regras só protegem o site se a porta do próprio Zope (por exemplo, 8080) não puder ser acessada diretamente de fora. Vincule-a a 127.0.0.1 ou a uma rede interna.
Dúvidas sobre o assunto podem ser enviadas para [email protected]. Se a pergunta não envolver informações sensíveis, também é possível fazê-la na discussão do anúncio, no fórum da comunidade.

