Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-34212 — O Docmost aceitou uma URL javascript: dentro de um nó de anexo, preservou-a através do armazenamento e renderização, e a transformou novamente em uma âncora clicável na origem do Docmost. | Kitploit
Ferramentas/GitHubGitHub/0xmrma/cve-2026-34212
Análise de VulnerabilidadesExploraçãoSegurança WebTestes de PenetraçãoPapers e PesquisaAprendizado e Educação
GitHub0xmrma/cve-2026-34212

CVE-2026-34212

O Docmost aceitou uma URL javascript: dentro de um nó de anexo, preservou-a através do armazenamento e renderização, e a transformou novamente em uma âncora clicável na origem do Docmost.

Ver Repositório
há 1 mêsAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-34212

O Docmost aceitou uma URL javascript: dentro de um nó de anexo, preservou-a durante o armazenamento e a renderização, e a transformou novamente em um âncora clicável na origem do Docmost.

Introdução

Identifiquei, divulguei responsavelmente e reproduzi um problema de XSS armazenado de gravidade Alta no Docmost, a plataforma de documentação colaborativa de código aberto.

O site oficial do Docmost o apresenta como uma wiki on-premises pronta para empresas com mais de 3 milhões de downloads, e afirma ser confiável por equipes de organizações como Vilnius City, Bechtle, o Governo Australiano, a Cruz Vermelha e ETS Quebec.

O bug estava em um local fácil de passar despercebido em sistemas de texto rico:

não na extensão de link comum, mas em um tipo de nó personalizado separado usado para anexos de arquivos.

Estava revisando o pipeline do editor com uma pergunta muito específica em mente:

se links normais bloqueiam URLs javascript:, os nós de anexo aplicam a mesma regra antes de chegar a um sink de âncora?

Em versões vulneráveis, não aplicavam.

O Docmost aceitou um nó de anexo malicioso no JSON da página, armazenou seu atributo url inalterado e, posteriormente, renderizou esse valor de volta em um elemento <a href="javascript:..."> clicável.

Esse problema se tornou CVE-2026-34212.

Docmost: docmost/docmost
Advisory: GHSA-cf68-cff9-hq4w
CVE: CVE-2026-34212
Corrigido em: v0.71.0

photo0

Cadeia de Ataque

URL do nó de anexo controlada pelo atacante -> JSON da página aceito e armazenado inalterado -> Renderização HTML/React transforma essa URL em href de âncora -> vítima clica na ação do anexo -> JavaScript controlado pelo atacante é executado na origem do Docmost


O Que Esta Parte do Docmost Faz

O Docmost armazena o conteúdo da página em um formato JSON compatível com ProseMirror/Tiptap.

Esse modelo de conteúdo inclui nós de bloco personalizados para coisas como:

  • imagens
  • diagramas
  • embeds
  • anexos

O nó de anexo armazena campos como:

  • url
  • name
  • mime
  • size
  • attachmentId

O servidor aceita o conteúdo da página em vários formatos:

  • json
  • markdown
  • html

e o normaliza para JSON ProseMirror antes de armazená-lo.

Isso significa que qualquer tipo de nó que possa carregar uma URL faz parte de um limite de confiança direto.

Se um desses tipos de nó eventualmente renderizar em um <a href>, o tratamento de esquema de URL não é opcional. Faz parte do modelo de segurança.


Por Que Essa Superfície Valia a Pena Ser Analisada

Extensões de editor personalizadas são uma fonte frequente de desvio de segurança.

O sistema base já pode saber como lidar corretamente com URLs perigosas, mas cada nó personalizado ainda precisa reaplicar as mesmas regras em seus próprios sinks.

Isso cria uma estratégia de revisão previsível:

  • encontre cada tipo de nó que armazena um campo do tipo URL
  • rastreie onde esse campo é aceito
  • rastreie onde esse campo é renderizado
  • compare seu comportamento de sanitização com o tratamento de link normal da plataforma

Foi exatamente isso que expôs esse bug.

A extensão de link normal do Docmost já tratava javascript: como perigoso.

Seu nó de anexo não tratava.

Uma vez que você vê essa assimetria, a questão de segurança se torna óbvia:

posso persistir um nó de anexo cuja url seja javascript: e fazê-lo ser renderizado de volta em uma âncora viva?

A resposta foi sim.


Causa Raiz

A causa raiz foi sanitização inconsistente de URL entre tipos de nós de conteúdo.

O caminho de conteúdo do lado do servidor aceitava URLs de anexo arbitrárias, desde que o conteúdo geral correspondesse ao esquema ProseMirror.

Na versão vulnerável:

  • CreatePageDto aceitava content?: string | object
  • PageService.parseProsemirrorContent() normalizava markdown, html ou json
  • o servidor então chamava jsonToNode(prosemirrorJson)
  • se a validação do esquema passasse, o conteúdo era armazenado

Essa etapa de validação verificava a validade estrutural, não a segurança da URL.

A parte crítica da lógica vulnerável do servidor era efetivamente:

root@kitploit:~
prosemirrorJson = content;
jsonToNode(prosemirrorJson);
return prosemirrorJson;

Nenhuma normalização de esquema de URL de anexo ocorria ali.

Posteriormente, a extensão de anexo renderizava o valor controlado pelo atacante diretamente.

O nó de anexo vulnerável fazia isso:

root@kitploit:~
url: {
  default: "",
  parseHTML: (element) => element.getAttribute("data-attachment-url"),
  renderHTML: (attributes) => ({
    "data-attachment-url": attributes.url,
  }),
},

e depois:

root@kitploit:~
[
  "a",
  {
    href: HTMLAttributes["data-attachment-url"],
    class: "attachment",
    target: "blank",
  },
  `${HTMLAttributes["data-attachment-name"]}`,
]

No lado do cliente, a visualização do nó React envolvia isso novamente em:

root@kitploit:~
<a href={getFileUrl(url)} target="_blank">

Mas getFileUrl() só tratava casos especiais:

  • URLs http absolutas
  • /api/...
  • /files/...

Qualquer outra coisa era retornada inalterada.

Assim, um payload como:

root@kitploit:~
javascript:alert(document.domain)

sobrevivia a:

  • armazenamento JSON
  • validação de esquema do lado do servidor
  • renderização HTML
  • tratamento de URL do lado do cliente

Isso por si só já seria suficiente para XSS armazenado.

O que torna a causa raiz especialmente clara é o ponto de comparação.

A extensão de link normal do Docmost bloqueava explicitamente javascript::

  • rejeitava javascript: em parseHTML()
  • zerava um href javascript: em renderHTML()

Portanto, o produto já sabia que esse esquema era perigoso.

O nó de anexo simplesmente falhou em aplicar a mesma política.

É por isso que isso não era "XSS genérico no editor."

Era uma lacuna de limite de confiança específica do nó.


Por Que Isso é um Problema de Segurança, Não Apenas Sanitização Ausente

Este bug não era meramente sobre estética HTML insegura.

Permitia que um atacante que pudesse editar uma página persistisse um payload malicioso que mais tarde seria executado na origem do Docmost quando outro usuário interagisse com o anexo renderizado.

Isso importa porque um script na origem pode:

  • ler dados que a vítima pode acessar
  • emitir requisições autenticadas como a vítima
  • modificar conteúdo que a vítima tem permissão para modificar
  • abusar de qualquer superfície DOM ou API exposta à sessão

A exigência de um clique não reduz isso a um problema trivial.

O clique faz parte do comportamento normal do produto: a interface apresenta intencionalmente o anexo como um link/ícone acionável.

Portanto, a questão de segurança não é "o atacante pode forçar JS arbitrário sem qualquer interação?"

A verdadeira questão é:

a aplicação armazena conteúdo malicioso contendo script controlado pelo atacante e posteriormente o apresenta a outros usuários como um caminho de interação confiável?

Em versões vulneráveis, sim.

Isso é XSS armazenado.


Por Que a Exploração Era Prática

O caminho de exploração era direto:

  • qualquer usuário com direitos de edição de página podia plantar o payload
  • a URL maliciosa sobrevivia ao armazenamento inalterada
  • a página era renderizada normalmente
  • os visualizadores só precisavam de acesso padrão à página
  • um clique na ação do anexo era suficiente para desencadear a execução

Isso também tornava usuários com privilégios mais altos alvos realistas.

Se um proprietário do workspace, administrador ou editor amplamente confiável visualizasse conteúdo controlado pelo atacante e clicasse na ação do anexo, o script do atacante seria executado no contexto de sessão mais privilegiado.

Esse é o ponto prático importante:

o requisito de privilégio do atacante era apenas baixo. O nível de privilégio da vítima determinava quanto valor a sessão XSS carregava.


Prova de Conceito

Validei o problema ao vivo contra Docmost v0.70.3.

A PoC usou apenas requisições HTTP normais e as próprias APIs de página da aplicação.

O fluxo foi:

  1. Faça login como um usuário que pode editar uma página.
  2. Crie ou selecione uma página.
  3. Envie POST /api/pages/update com format: "json" e um nó de anexo cuja url seja um payload javascript:.
  4. Solicite a página de volta através de POST /api/pages/info.
  5. Confirme que o JSON armazenado ainda contém a URL maliciosa.
  6. Solicite a mesma página em formato HTML e confirme que o servidor retorna uma âncora cujo href ainda é javascript:....
  7. Na interface, um visualizador clicando na ação do anexo renderizado executa o payload na origem do Docmost.

O conteúdo malicioso mínimo era:

root@kitploit:~
{
  "pageId": "<pageId>",
  "content": {
    "type": "doc",
    "content": [
      {
        "type": "attachment",
        "attrs": {
          "url": "javascript:alert(document.domain)",
          "name": "policy.pdf",
          "mime": "application/pdf",
          "size": 1
        }
      }
    ]
  },
  "operation": "replace",
  "format": "json"
}

O resultado ao vivo observado do meu teste foi:

  • a API aceitou o nó de anexo malicioso inalterado
  • o ID da página armazenada era 019d18cf-4212-70b0-894a-fe20080fb0f1
  • POST /api/pages/info retornou o JSON armazenado com:
root@kitploit:~
"url": "javascript:alert(document.domain)"
  • POST /api/pages/info com format: "html" retornou HTML contendo:
root@kitploit:~
<div data-type="attachment" data-attachment-url="javascript:alert(document.domain)" data-attachment-name="policy.pdf" data-attachment-mime="application/pdf" data-attachment-size="1"><a href="javascript:alert(document.domain)" class="attachment" target="blank">policy.pdf</a></div>

Essa resposta HTML é a prova crítica.

Não precisei confiar em uma afirmação vaga de que "um navegador pode fazer algo interessante."

A própria aplicação renderizou o sink executável exato.

Assim que um usuário clicar nesse link/ícone de anexo, o navegador executa a URL javascript: na origem da página que a criou.


Por Que a PoC Foi Escolhida Dessa Forma

Para XSS orientado a editor, capturas de tela sozinhas são evidências fracas.

Elas mostram sintomas, não a falha de limite.

Por isso, estruturei a PoC em torno de dois pontos de verificação explícitos:

  1. prova de armazenamento
  2. prova de sink renderizado

A prova de armazenamento mostrou que o servidor aceitou e preservou o esquema perigoso.

A prova de sink renderizado mostrou que a aplicação transformou esse valor armazenado de volta em:

root@kitploit:~
<a href="javascript:...">

Essa divisão é importante.

Se um produto armazena entrada perigosa mas a neutraliza antes de cada sink, você pode ter uma lacuna de hardening, mas não necessariamente um XSS vivo.

Se o produto armazena entrada perigosa e posteriormente a renderiza em um sink de execução real, você tem a cadeia completa da vulnerabilidade.

Foi o que aconteceu aqui.


Análise da Correção

A correção foi enviada na v0.71.0 e abordou o caminho de exploração renderizado aplicando sanitização de URL às URLs de anexo.

A extensão de anexo agora importa e usa sanitizeUrl, incluindo:

  • sanitizar data-attachment-url durante o parsing
  • sanitizar data-attachment-url durante a renderização
  • sanitizar o href da âncora

Conceitualmente, o patch mudou o nó de anexo de:

  • confiar na URL bruta do anexo
  • emitir a URL bruta do anexo

para:

  • normalizar a URL do anexo antes que ela se torne parte do nó renderizado

O helper do lado do cliente getFileUrl() também foi atualizado para que esquemas desconhecidos não passem mais incólumes. Na versão corrigida, o caminho de fallback retorna sanitizeUrl(src) em vez de retornar src como está.

Essa é uma parte importante da correção porque o design vulnerável tinha dois problemas reforçadores:

  • o nó renderizava um href bruto
  • o fallback do cliente tratava esquemas desconhecidos como aceitáveis

O patch removeu ambas as suposições.

Esta foi uma boa correção para o caminho ativo de XSS porque trouxe o tratamento de URL de anexo de volta ao alinhamento com o restante do modelo de segurança do editor.

Dito isso, ainda há uma lição mais ampla de hardening:

a sanitização no lado do cliente ou no momento da renderização é necessária aqui, mas a rejeição no lado do servidor de esquemas perigosos durante a criação/atualização da página seria uma invariante ainda mais forte.

O modelo mais seguro a longo prazo é:

  • rejeitar esquemas obviamente perigosos na ingestão
  • sanitizar novamente nos limites de renderização

Defesa em profundidade é importante em sistemas de conteúdo rico.


Casos de Regressão Que Importam

Para cobertura de longo prazo, estes são os casos que mais importam:

  • atualizações de página JSON contendo attachment.attrs.url = "javascript:..."
  • importações HTML contendo data-attachment-url="javascript:..."
  • a renderização de anexos nunca deve emitir href="javascript:..."
  • helpers de fallback do cliente não devem retornar esquemas executáveis desconhecidos inalterados
  • nós de anexo e nós de link normais devem compartilhar política de esquema de URL equivalente
  • caminhos de anexo interno seguros como /api/files/... e /files/... devem continuar funcionando normalmente

O ponto chave é consistência.

Se links normais são sanitizados, mas nós personalizados que carregam URL não são, o editor não tem realmente uma política de segurança de URL única.

Ele tem fragmentos, e fragmentos são onde bugs de XSS vivem.


Gravidade e Classificação

O advisory publicado classificou este problema como:

  • CWE-79: Neutralização Incorreta de Entrada Durante a Geração de Páginas Web
  • CVSS v3.1:
root@kitploit:~
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N

Isso resulta em 7.6 / Alto.

Esta é uma classificação defensável.

As propriedades importantes são:

  • requisito de privilégio baixo do atacante
  • payload armazenado
  • execução na origem do Docmost
  • mudança de escopo
  • impacto significativo de confidencialidade porque o script pode acessar dados visíveis pelo usuário no aplicativo

A interação do usuário permanece necessária porque a vítima precisa ativar o link/ícone do anexo. É por isso que UI:R está correto.

Mas uma vez que essa interação acontece, o limite de segurança já falhou muito antes: a aplicação armazenou um esquema perigoso e o renderizou de volta em um sink de execução.


Divulgação

Relatei o problema privadamente através do GitHub Security Advisories com:

  • análise de causa raiz
  • uma PoC HTTP ao vivo
  • evidência JSON armazenada
  • evidência de sink HTML renderizado
  • um laboratório de teste descartável fixado

O problema foi aceito, recebeu o identificador CVE-2026-34212 e foi publicado em 14 de abril de 2026.

O advisory público atualmente lista:

  • versão afetada: 0.70.3
  • versão corrigida: 0.71.0

Minha validação ao vivo foi realizada na v0.70.3, que correspondia à versão vulnerável publicada.


O Que Este Bug Realmente Ensina

A principal lição aqui não é simplesmente "sanitize URLs."

Todo mundo já sabe disso.

A lição mais interessante é:

se uma aplicação tem um tipo de nó que carrega URL seguro e um tipo de nó que carrega URL inseguro, o inseguro é a política real.

Sistemas de texto rico frequentemente acumulam extensões personalizadas mais rápido do que acumulam revisão de segurança.

Isso cria exatamente esse tipo de assimetria:

  • o caminho de link padrão é endurecido
  • o caminho de anexo é tratado como "interno" ou "especial"
  • o caminho especial silenciosamente se torna o sink XSS mais fácil

Este bug também mostra por que a validação de esquema não é suficiente.

jsonToNode() verificou que o conteúdo era estruturalmente válido segundo o ProseMirror. Não provou que o conteúdo era seguro para renderizar.

Essas são perguntas diferentes.

A revisão de segurança se torna muito mais nítida quando você mantém essas perguntas separadas:

  • este conteúdo é estruturalmente válido?
  • este conteúdo é seguro para armazenar?
  • este conteúdo é seguro para renderizar em cada sink?

O nó de anexo passou na primeira pergunta e falhou na terceira.

É assim que bugs de conteúdo armazenado sobrevivem dentro de pipelines de editor de outra forma bem estruturados.


Pontos-Chave

  • O Docmost aceitava URLs brutas de nós de anexo no conteúdo da página.
  • A validação de página do lado do servidor verificava a forma do esquema ProseMirror, não a segurança do esquema de URL.
  • O nó de anexo vulnerável renderizava data-attachment-url e o href da âncora diretamente a partir da entrada controlada pelo atacante.
  • O helper do cliente getFileUrl() retornava esquemas desconhecidos inalterados.
  • Nós de link normais já bloqueavam javascript:, mas nós de anexo não.
  • Um editor com privilégios baixos podia plantar o payload uma vez e visar visualizadores posteriores.
  • A PoC ao vivo provou tanto a persistência armazenada quanto o sink executável renderizado.
  • A correção na v0.71.0 adicionou tratamento sanitizeUrl ao nó de anexo e ao caminho de fallback do cliente.

Palavras Finais

Esta vulnerabilidade não era sobre uma peculiaridade do navegador.

Era sobre um nó de conteúdo personalizado que contornava as próprias suposições de segurança de URL da aplicação.

O Docmost aceitou uma URL de anexo controlada pelo atacante, preservou-a durante o armazenamento e depois a renderizou de volta em uma âncora viva na origem da aplicação.

É por isso que se tornou CVE-2026-34212.

O patch na v0.71.0 fechou o caminho ativo de XSS de forma limpa, mas a lição mais ampla é a que vale a pena guardar:

em aplicações pesadas de editor, cada nó personalizado que pode carregar uma URL é seu próprio limite de segurança, e precisa ser revisado como tal.

Baixar ferramenta