
A Documentation of CVE-2025-68116
Autor: @x0root
Vulnerabilidade: Cross-Site Scripting (XSS) Armazenado via Uploads Renderizáveis pelo Navegador (SVG / HTML)
Software Afetado: FileRise (< 2.7.1)
Versão Corrigida: 2.7.1
CVE Oficial (solicitado via GHSA): CVE-2025-68116 (rastreamento/aviso: GHSA-35pp-ggh6-c59c)
Aviso relacionado anterior (mitigação original que foi contornada): GHSA-qrcv-vjvf-fr29
Avaliação CVSS:
A avaliação do relator considera os Privilégios Necessários (PR) no momento da exploração, e não no momento da inserção da vulnerabilidade.
A exploração ocorre quando uma vítima acessa um link de compartilhamento público gerado, o que não requer autenticação ou privilégios (PR:N).
A avaliação do CNA considera PR com base na capacidade de fazer upload de um arquivo malicioso. No entanto, o CVSS v3.1 define Privilégios Necessários (PR) como os privilégios que um atacante deve possuir no momento em que a vulnerabilidade é explorada, e não os privilégios necessários para colocar ou preparar a condição vulnerável.
Dessa forma, PR:N reflete com mais precisão as condições reais de exploração, resultando em uma classificação de gravidade Crítica (9.6).
Nota: O GHSA-qrcv-vjvf-fr29 introduziu uma mitigação que impedia a renderização de SVGs na interface web do FileRise (painel de pré-visualização). Este relatório documenta uma bypass dessa mitigação—especificamente os endpoints de download/compartilhamento do backend—que é rastreada como GHSA-35pp-ggh6-c59c / CVE-2025-68116.
Este documento é um registro técnico completo do CVE-2025-68116: um XSS Armazenado no FileRise que persistiu após uma mitigação anterior e foi finalmente corrigido na v2.7.1. Inclui descoberta, provas de conceito de exploração, correções falhas repetidas, uma análise precisa do fluxo de controle da causa raiz (com evidências), verificação final da correção e uma análise das características de explorabilidade relevantes para a avaliação CVSS. Todo o conteúdo abaixo é baseado em testes reproduzidos, inspeção do controlador e no fio de discussão do aviso público.
Um aviso anterior, GHSA-qrcv-vjvf-fr29, abordou o XSS Armazenado via uploads SVG bloqueando a renderização inline na interface web do FileRise. Essa mitigação não abordou como os arquivos SVG eram servidos por endpoints do backend, tais como:
/api/file/download.php/api/file/share.phpO CVE-2025-68116 (rastreado como GHSA-35pp-ggh6-c59c) documenta uma bypass da mitigação GHSA-qrcv-vjvf-fr29: um atacante pode armazenar um SVG manipulado e entregá-lo às vítimas por meio de links de compartilhamento públicos ou certos comportamentos de download, levando à execução de scripts na origem do FileRise.
Para validar se o backend ainda expunha SVGs de forma renderizável, fiz upload de um SVG PoC simples:
Acessar o arquivo via:
/api/file/download.php?…/api/file/share.php?token=…resultou na execução de alert(). A mitigação original do GHSA-qrcv-vjvf-fr29 (bloqueio de pré-visualização na interface) foi contornada pelo acesso direto a esses endpoints.
Um alert() é um PoC; testei o impacto real fazendo com que o payload interagisse com APIs internas.
Payload de teste usado:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">
fetch('/api/upload/upload.php')
.then(response => response.text())
.then(data => alert('API Response: ' + data));
</script>
</svg>
Quando um administrador logado abria um link de compartilhamento contendo este SVG, o script era executado e fazia requisições autenticadas à API. Efeitos observados incluíram:
{"csrf_expired":true,"csrf_token":"..."})Classificação de impacto demonstrada durante os testes:
Relatei o problema de forma privada. O mantenedor lançou várias correções incrementais:
Ao longo das versões v2.6.0 → v2.7.0, o endpoint de link de compartilhamento continuou servindo o SVG de forma que permitia renderização inline e execução de scripts. A análise da causa raiz abaixo explica por que as correções anteriores falharam em fechar completamente o vetor.
A causa subjacente não foi um único cabeçalho ausente, mas sim ordenação de fluxo de controle e saída dentro de shareFile() (controlador) que impedia a aplicação dos cabeçalhos de segurança em muitos caminhos de execução. Duas classes de problemas estavam presentes:
exit; precoces que interrompiam a função antes que os cabeçalhos de segurança fossem definidos.Usei uma varredura com awk para listar ocorrências de header() e exit; dentro de shareFile() até a chamada readfile():
Comando: awk '/function shareFile(/ {flag=1} /readfile(/ {flag=0} flag && /(header|exit;)/ {printf "%4d | %s\n", NR, $0}' src/controllers/FileController.php
Saída observada (resumida da minha execução):
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
Os cabeçalhos de segurança (a lógica de fortalecimento) começam na linha ~1743:
1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
Como a função emite cabeçalhos + exit; mais cedo em muitos caminhos, essas requisições nunca chegaram ao código de fortalecimento que define Content-Disposition, nosniff ou o tipo restritivo.
No fluxo de compartilhamento protegido por senha, a função emitia o HTML do prompt de senha cedo:
if (!empty($record['password']) && empty($providedPass)) { header("Content-Type: text/html; charset=utf-8"); ... exit; }
Esse caminho envia Content-Type: text/html e sai antes da lógica de fortalecimento de SVG, causando renderização inline no navegador para compartilhamentos protegidos por senha onde nenhuma senha foi fornecida.
A vulnerabilidade não se limitava a fluxos protegidos por senha. Uma requisição de compartilhamento sem senha também retornava text/html nos meus testes:
Comando: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type"
Observado: < Content-Type: text/html; charset=UTF-8
Isso confirma que mesmo no caso geral (sem senha) a resposta era text/html, e o SVG era renderizado inline.
Capturei uma requisição bruta ao endpoint de compartilhamento que incluía avisos do PHP emitidos antes do fortalecimento de cabeçalho. Instantâneo (resumido):
Comando: curl -s "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" | head -n 30
Saída bruta observada (resumida):
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
Esses avisos mostram que a saída foi produzida (notas de obsoleto) antes do fortalecimento de cabeçalho, tornando impossível que chamadas header() subsequentes tivessem efeito nessas execuções.
nosniff existia, mas não era alcançado em muitos caminhos de código devido a saídas precoces e saída.Após os relatórios de causa raiz, o mantenedor aplicou alterações que abordaram a ordenação do fluxo de controle e da saída. Na v2.7.1:
exit; anteriores que contornavam o fortalecimento foram corrigidos/tratados.Verificação final (meu teste na v2.7.1):
Comando: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition"
Observado: < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg
Resultado: O navegador forçou um download; o SVG não foi renderizado inline e o payload XSS não foi executado. Considero que a v2.7.1 resolveu o problema no meu ambiente.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L — Pontuação: 9.6 (Crítica)
Documentei essa justificativa no fio de discussão do aviso e solicitei que PR:N fosse usado.
O vetor CVSS da CNA reflete um modelo de ameaça restrito.
Testes empíricos demonstram um caminho de exploração mais severo e reproduzível, que se alinha a uma pontuação base CVSS 3.1 mais alta sob regras de pontuação padrão.
Avaliadores independentes são encorajados a avaliar a gravidade usando as condições de exploração observadas descritas neste documento, garantindo que a gravidade pública reflita o impacto real em vez de uma linha de base com escopo estreito.
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
Cabeçalhos de segurança começam em ~1743: 1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type" < Content-Type: text/html; charset=UTF-8
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
...seguido pelo payload SVG sendo impresso e renderizado inline.
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition" < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg