
XSS armazenado do Bloomberg Memray via metadados de linha de comando não escapados
XSS Armazenado no Bloomberg Memray via Metadados de Linha de Comando sem Escape
Encontrei este problema ao revisar o Memray, o profiler de memória Python da Bloomberg, com uma pergunta simples em mente:
Metadados de runtime controlados por um atacante podem cruzar para a saída de relatórios renderizada no navegador de forma insegura?
Neste caso, a resposta foi sim.
O bug estava no caminho de geração de relatórios HTML do Memray, onde os metadados da linha de comando eram renderizados em um relatório aberto no navegador sem escape. Isso transformou um campo operacional em um sink HTML executável e, no final, tornou-se CVE-2026-32722.
Projeto: Memray no GitHub
Advisory: GHSA-r5pr-887v-m2w9 /CVE-2026-32722
attacker-controlled argv → metadata.command_line → HTML template sink without escaping → raw markup in generated report → browser-side JavaScript execution
O Memray é um profiler de memória Python.
Ele instrumenta um processo Python, registra o comportamento de alocação e produz relatórios que ajudam os desenvolvedores a entender:
Alguns desses relatórios são emitidos como HTML e abertos em um navegador.
Isso torna a geração de relatórios um limite de segurança real.
A pergunta relevante não é se o Memray é uma "ferramenta local".
A pergunta relevante é se dados controlados por um atacante podem cruzar para a saída renderizada no navegador de forma insegura.
Neste caso, podiam.
Muitas pessoas subestimam as ferramentas de desenvolvimento.
Isso é um erro.
Quando uma ferramenta:
ela herda os mesmos riscos de codificação de saída de uma aplicação web.
Esse era o problema central aqui.
Este bug não estava na lógica de profiling. Não estava no rastreamento de alocações. Não estava no tratamento de memória nativa.
Foi uma falha clássica de limite de confiança:
Isso é suficiente para criar uma vulnerabilidade real.
Não abordei o Memray fazendo fuzzing de opções aleatórias de CLI ou perseguindo crashes.
A abordagem mais forte era identificar primeiro a superfície de segurança de maior probabilidade.
Para o Memray, essa superfície era a geração de relatórios HTML.
Por quê?
Porque a saída HTML introduz um sink de navegador, e sinks de navegador transformam bugs comuns de metadados em problemas de segurança se:
Foi exatamente o que aconteceu aqui.
O bug se resume a duas linhas.
Em:
def get_render_environment() -> jinja2.Environment:
loader = jinja2.PackageLoader("memray.reporters")
env = jinja2.Environment(loader=loader)
o ambiente Jinja é criado sem autoescape.
Então em:
Command line: <code>{{ metadata.command_line }}</code><br>
metadata.command_line é renderizado diretamente em HTML.
Essa é toda a vulnerabilidade.
Porque metadata.command_line é influenciado por um atacante.
O Memray registra a linha de comando usada para executar o programa analisado.
Isso significa que valores controlados pelo usuário de argv são preservados como metadados e posteriormente inseridos no relatório.
Portanto, a cadeia de exploração é direta:
metadata.command_lineIsso transforma metadados em conteúdo de navegador executável.
A distinção importante é a execução.
Muitos bugs produzem HTML malformado. Isso sozinho não é suficiente.
Aqui, o conteúdo controlado pelo atacante não estava meramente visível no código-fonte da página. Ele foi interpretado pelo navegador como HTML ativo e executado como JavaScript.
Essa é a diferença entre:
Portanto, a pergunta não era:
"O HTML pode aparecer no relatório?"
A pergunta real era:
"O HTML controlado por um atacante pode se tornar executável quando o relatório é aberto?"
A resposta era sim.
Meu reprodutor inicial usava um script explícito mais um argumento controlado pelo atacante:
cat > victim.py <<'PY'
x = [b"A" * 1024 for _ in range(1000)]
print("done")
PY
python -m memray run -o poc.bin victim.py ''
python -m memray flamegraph -o poc.html poc.bin
O HTML gerado continha marcação crua controlada pelo atacante:
Command line: <code>~/memray/src/memray/__main__.py run -o poc.bin victim.py </code><br>
Abrir ou atualizar o relatório gerado disparava a execução de JavaScript.
Isso estabeleceu a afirmação central:
Mais tarde, durante a divulgação coordenada, o mantenedor simplificou ainda mais o reprodutor:
python -m memray run -o poc.bin -c '# '
Essa versão é melhor porque isola o limite vulnerável de forma mais direta:
O payload foi intencionalmente simples:
Não se trata de payloads chamativos.
É uma sonda de execução limpa porque:
Para essa classe de bug, isso é suficiente.
Um único tipo de relatório já teria sido suficiente para justificar o problema.
Mas eu queria saber se isso era isolado ou estrutural.
Confirmei o mesmo comportamento em:
--no-webIsso importava por dois motivos.
Mostrou que o sink vulnerável era reutilizado em múltiplas saídas HTML.
Provou que o bug não dependia de ativos hospedados em CDN externo.
O --no-web ainda reproduzia o problema, o que significa que o problema estava no HTML gerado pelo próprio Memray e no tratamento do template, não no comportamento de JS remoto.
Isso tornou o caso muito mais forte.
A principal preocupação do mantenedor era o controle prático do atacante.
Isso é justo.
Não é o tipo de problema em que um atacante remoto não autenticado atinge um endpoint HTTP exposto e obtém impacto imediato.
A condição de exploração é mais restrita:
Portanto, a classificação realista era severidade baixa.
Isso não faz dele um bug fraco.
Severidade diz respeito às condições de exploração e ao impacto provável. Validade diz respeito a se o problema é real.
Este problema era claramente real:
É por isso que ainda se tornou um CVE.
A correção foi mínima e correta.
O mantenedor alterou:
{{ metadata.command_line }}
para:
{{ metadata.command_line|e }}
Essa é a correção certa porque trata diretamente do sink vulnerável.
Em vez de emitir marcação crua como:
o template agora emite texto com escape:
<img src=x onerror=alert(1)>
Isso preserva o valor informativo do campo de linha de comando enquanto remove o caminho de execução no navegador.
O mantenedor também revisou o restante do contexto do template e concluiu que:
|tojson
Portanto, o problema foi corretamente reduzido a metadata.command_line.Esse é exatamente o tipo de revisão de correção que você quer em uma divulgação real.
Isso foi relatado em particular por meio do GitHub Security Advisories.
Os mantenedores:
O problema recebeu o identificador: CVE-2026-32722
A lição principal aqui é simples:
metadados não são automaticamente confiáveis apenas porque parecem operacionais.
Nada disso importa quando conteúdo controlado por um atacante cruza para a saída renderizada no navegador sem escape.
No momento em que uma ferramenta emite HTML, ela precisa ser tratada como uma aplicação que produz HTML.
Essa é a verdadeira conclusão.
Esta vulnerabilidade não era sobre um payload engenhoso.
Era sobre identificar o limite certo.
O Memray pegou metadados de linha de comando influenciados por um atacante e os renderizou em HTML gerado sem aplicar escape. O navegador fez o resto.
É por isso que isso se tornou CVE-2026-32722.
Corrigido no Memray 1.19.2.
