
Prova de conceito para CVE-2026-93349, uma injeção de comando do sistema operacional no CLI explore do Frictionless <= 5.20.0rc1 por meio de caminhos maliciosos de descritores de Data Package.
<= 5.20.0rc1 explore do FrictionlessSeveridade: Alta, CVSS 3.1 8.8
Vetor: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Afetado: todas as versões do Frictionless <= 5.20.0rc1
Componente: frictionless.console.commands.explore
CWE: CWE-78 (Injeção de Comando de SO), CWE-20 (Validação de Entrada Imprópria)
Reportado por: Sai Teja Erukude
As versões do Frictionless até e incluindo a pré-lançamento 5.20.0rc1 contêm uma
vulnerabilidade de injeção de comando de SO no comando CLI frictionless explore.
O comando carrega um descritor de Data Package fornecido pelo usuário, coleta
caminhos de recursos, deriva resource.normpath, une esses caminhos em uma única
string de comando e passa essa string diretamente para os.system:
paths = [resource.normpath for resource in resources if resource.normpath]
os.system(f"vd {' '.join(paths)}")
Como resources[*].path é metadado controlado pelo descritor proveniente de
datapackage.json, um atacante pode incluir metacaracteres de shell em um caminho
de recurso. Quando uma vítima executa frictionless explore no descritor
malicioso, o shell interpreta esses metacaracteres e executa o comando injetado.
Um atacante pode executar comandos arbitrários no contexto de segurança do usuário
que executa frictionless explore.
Isso pode afetar fluxos de trabalho nos quais usuários inspecionam, validam ou exploram descritores de Data Package de projetos, arquivos, repositórios ou conjuntos de dados compartilhados não confiáveis.
Dependendo do ambiente local, isso pode permitir:
O caminho vulnerável é o comando explore:
frictionless explore <package>
-> Package.from_descriptor(...)
-> Resource.list()
-> resource.normpath
-> os.system(f"vd {' '.join(paths)}")
O PoC gera um descritor de Data Package no qual resources[0].path contém um
payload inofensivo de escrita de arquivo marcador.
No Windows, o caminho gerado usa separadores de comando &:
data.csv & echo frictionless_explore_command_injection_triggered>"...\marker.txt" & rem
Em sistemas POSIX, o caminho gerado usa separadores de comando ponto e vírgula:
data.csv; printf %s frictionless_explore_command_injection_triggered > .../marker.txt; #
O executor invoca o comando real frictionless explore do pacote instalado. Ele
limpa o PATH para o processo filho para que o visualizador vd pretendido não
seja iniciado interativamente, mas o comando injetado ainda é executado quando o
shell analisa a string de comando construída.
package_builder.py cria o descritor de Data Package
controlado pelo atacante e um arquivo CSV benigno.
run_poc.py valida o descritor, invoca frictionless explore e
imprime evidências de execução.
Execute apenas em um ambiente de teste local:
python -m venv .venv
.venv\Scripts\activate
python -m pip install -r requirements.txt
python -B run_poc.py
O requirements.txt incluído fixa frictionless==5.20.0rc1, a pré-lançamento
vulnerável mais recente confirmada nesse intervalo.
Evidência esperada em versões vulneráveis:
frictionless_version : 5.20.0rc1
installed_package : True
descriptor_valid : True
listed_resource_count : 1
raw_path_contains_payload : True
normpath_contains_payload : True
explore_returncode : 0
viewer_launch_disabled : True
marker_exists : True
marker_contents
frictionless_explore_command_injection_triggered
success : True
A evidência importante é que o descritor malicioso é válido, o caminho controlado
pelo descritor sobrevive até resource.normpath, e marker.txt é criado após a
execução de frictionless explore.
O payload é intencionalmente inofensivo. Ele apenas escreve esta string de marcador local:
frictionless_explore_command_injection_triggered
Ele não abre uma sessão de shell, não se conecta a um serviço de rede, não lê segredos, não exclui dados nem modifica arquivos fora do diretório do PoC.
Evite passar caminhos controlados pelo descritor para um shell.
Substitua a invocação baseada em shell por subprocess.run usando um vetor de
argumentos:
subprocess.run(["vd", *paths], check=False)
Isso passa cada caminho de recurso como um argumento literal para vd.
Metacaracteres de shell dentro de um caminho permanecem caracteres comuns dentro
dessa única entrada de argv e não são executados.
Colocar caracteres específicos de caminho em uma lista de bloqueio é mais fraco porque a sintaxe do shell varia entre plataformas e shells. A fronteira de invocação do comando é a causa raiz, então a correção duradoura deve evitar a interpretação pelo shell.
Consulte fix.md para uma correção sugerida.
Descoberto e reportado por Sai Teja Erukude, coordenado através da VulnCheck.
exploit_walkthrough.mdfix.md