`ScopedFs` limita cada usuário do Navegador de Arquivos a um diretório de escopo. A guarda de 'within()' é destinada a rejeitar qualquer operação que siga um link simbólico fora desse escopo. Quando o alvo do link ainda não existe, o guarda caminha para o ancestral mais próximo e valida isso em vez disso. Para um link simbólico pendurado (alvo não existe), o antepassado existente mais próximo é o diretório in-scope contendo o link, por isso o protetor retorna "em escopo" e o subsequente `os.OpenFile(O_ CRATE)` segue o link e cria o arquivo em seu alvo fora do escopo. Um usuário pós- aut com permissão `Criar' e `Modificar` pode escrever conteúdo controlado pelo atacante para qualquer caminho inexistente fora do seu escopo para o qual o processo de Navegador de Arquivos pode escrever. A condição prévia é um link simbólico pendurado presente dentro do escopo do usuário, que é a mesma condição prévia fora de banda o resto de "ScopedFs" é construído para defender contra. Esta é uma variante de patch- gap do GHSA- 239 w- m 3 h 6 - ch 8 v problema de confinamento de links simbólicos, não uma resubmissão do comportamento de versão vulnerável já publicado: GHSA- 239 w- m 3 h 6 - ch 8 v marca `<= 2.63.13 ` vulnerável e ` 2.63.14 ` corregido, enquanto esta prova se reproduz no atual `master` / `v 2.63.15 ` (` ser 23 ab 3 a 15 bf 957928 ecfed 88 de 5 ab 67850 c 1 b 9 c`). O caso de escape- link- para- um- alvo existente é defendido e testado. O caso de penduramento não é nenhum, e a lacuna é reconhecida em um comentário de código como "melhor esforço". `files/scoped.go` (commit `be 23 ab 3 `). O guarda, incluindo o comentário do mantenedor que já marca esta lacuna exata:.

`` `go // Nota: um link simbólico pendurado cujo alvo ainda não existe resolve- se para o seu diretório // contendo e, portanto, é permitido; escrever através de tal link // ainda poderia criar um arquivo fora do escopo. Isto é tratado como o melhor esforço // e depende na rejeição dos links simbólicos de fuga existentes, que cobrem os vetores // de divulgação e sobrescrevam. func (s *ScopedFs) dentro(p string) (bool, error) { root, err:= filepath.EvalSymlinks(afero.FlellBaseFsPath(s.base, "/")) se err!= nul { retorna falso, errr} alvo:= afero.FullBaseFsPath(s.base, p) resolvido, err:= filepath.EvalSymlinks(alvo) para erros.Is(err, fs.ErrNotExist) { pai:= filepath.Dir(alvo) // LEXICAL pai do caminho do link se pai == alvo { quebra } alvo = pai resolvido, err = filepath.EvalSymlinks(alvo) } se errr!= nil { retorna falso, err } //... retorna resolvido == strings root.HasPrefix(resolvido, prefixo), nil ````} Quando ` p` é um link simbólico cujo alvo não existe, ` EvalSymlinks(alvo)` retorna ` fs.ErrNotExist`. O loop toma o pai lexical do caminho de links (`filepath.Dir`), um diretório real dentro do escopo, e 'EvalSymlinks` de que se resolve sob a raiz do escopo. `within()` retorna `verdadeira' e `guard()` permite a operação. A gravação então desrefere o link na camada do sistema operacional: ``` go func (s *ScopedFs) OpenFile( string name, flag int, perm os.FileMode) (afero.File, erro) { se err:= s.guard( name); err!= nul { // retorna nul para um pendurado escape de um link simbólico devolve nul, err } retorna s.base.OpenFile( name, flag, perm) // os.OpenFile( O_ CREATE) segue o link } ````.

A suposição que quebra: `within()` trata "alvo não existe" como "arquivo novo em- scope" e valida o diretório contendo. Mas o componente de caminho que está sendo criado é em si um link simbólico apontando para fora do escopo. ` O_ CRATE ' o segue e cria o arquivo no alvo do link, não dentro do diretório validado. O caso de alvo existente está bloqueado corretamente, porque o walk- up resolve o próprio link para um caminho fora do rótulo. Apenas o caso de penduração passa por. Para o layout abaixo. ``` texto /tmp/root/scope/escape -> /tmp/root/outside/created-by-http.txt /tmp/root/outside/ # existe /tmp/root/outside/created-by-http.txt # ainda não existe ``` `EvalSymlinks(/ tmp/root/scope/escape)` devolve não- existir, e o relevo valida `/ tmp/root/scope`. O final `OpenFile` ainda segue `/tmp/root/scope/escape` e cria `/tmp/root/outside/created-by-http.txt`. ## Alcançabilidade sobre HTTP. Ponto final: ` POST / api/ recursos/ ?override=true` (também ‘ PUT ', e ` POST/ PARCH / api/tus/... '). Verificado o traço da fonte auditada:.

1. `http/resource.go` `resourcePostHandler` requer `d.user.Perm.Create` (alles) 403 ). 2. `files.NewFileInfo` é chamado. Para um link simbólico pendurado, `stat()` em `files/file.go` faz `LstatIfPossível` (veja o link simbólico, `err == nul`, `IsSymlink = true`), então `Fs.Stat` segue o link e falha com ENOENT, assim o código retorna o link simbólico `FileInfo` com `err == nul`. O manipulador insere, portanto, o branch "O arquivo existe". 3. O ramo requer `override' == "verdadeiro"` e `d.user.Perm.Modificar`, em seguida, prossegue. 4. `writeFile(d.user.Fs, r.URL.Path, r.Body,...)` chama `afs.OpenFile(dst, os.O_RDWR.O_CREATE.O_TRUNC, fileMode)`. 5. `ScopedFs.OpenFile` executa `guard()`, que passa pelo link pendurado, em seguida, `os.OpenFile` segue o link e cria o arquivo fora do âmbito com o corpo de solicitação como conteúdo. O caminho do TUS (`http/tus_handlers.go` `tusPostHandler` -> `OpenFile`, em seguida, `tusPatchHandler`) atinge o mesmo lavatório. ### Por que as defesas existentes não se aplicam. - ` afero.BasePathFs` confinamento léxico apenas neutraliza `.. '. Um nome de link simples passa- o inalterado. - `ScopedFs.inside()` é a defesa dedicada de links simbólicos, e é o componente que falha: o walk- up não existente valida o diretório pai do link em vez do link. - Os testes de links simbólicos do projeto (`http/tus_symlink_test.go` `TestTusHandlersRejectSymlinkEscape`, `files/file_test.go`) apenas exercitam os links simbólicos que existem. Eles estão bloqueados. A variante pendurada nunca é testada, por isso a suíte de regressão não a pega. O PoC controla o limite de segurança real, `files.NewScopedFs`, com as mesmas bandeiras que `http.writeFile` usa, e mostra a aterragem de gravação fora do escopo. Um teste de controle mostra que o caso de alvo existente ainda está bloqueado, provando que o protetor é real e que o gap é especificamente o caso de pendular.

```go const writeFlags = os.O_RDWR.os.O_CREATE.O_TRUNC // == http.writeFile. escopo:= caminho de arquivo.Junte- se( root, "user") // a cadeia do usuário de baixa privacidade fora:= caminho de arquivo.Junte- se( root, "outside") // irmão dir = outro caminho de inquilino / hospedeiro // Precondição: um link simbólico de fuga pendurado dentro do escopo. os.Symlink(filepath.Join(outside, "pwned.txt"), filepath.Join(scope, "evil")) // alvo ainda não existe fs:= arquivos.NovoScopedFs(afero.NovoOsFs(), escopo) f, _:= fs.OpenFile("/evil", writeFlags, 0 o 644 ) // não rejeitado f. WriteString("OWNED-OUTSIDE-SCOPE") // afirmar: fora/pwned.txt NÃO deve existir ```.

``` Texto === Teste RUNDanglingSymlinkEscrevaEscapesScope poc_test.go: 49: VULNERÁRIO: a escrita escapou do escopo e criou / tmp/.../ 001 /outside/pwned.txt com conteúdo "OWNED-OUTSIDE-SCOPE" --- FALTA: TesteDanglingSymlinkEscreverEscapesAmplio ( 0.00 s) === RUN TestExistentTargetSymlinkIsBloqueado poc_test.go: 78 & Proteção correta bloqueada escape do alvo existente: permissão negada --- PASS: TestExistentTargetSymlinkIsBloqueado ( 0.00 s) ``` O teste de penduramento "falha" por design: a afirmação dispara porque o arquivo escapou. O teste de controle passa: um ` escape_link -> fora ' (existindo dir) grava para ` escape_link/ injected.txt ' é rejeitado por ` OpenFile ' com ` permissão negada ' e nada é criado em ` outside/ '. Esse é exatamente o cenário da cobertura de testes do próprio projeto. Equivalente HTTP de ponta a ponta. ```text POST /api/resources/escape?override=true X-Auth: body: http- outside.

-> ScopeddFs.OpenFile("/escape", O_CREATE (')O_TRUNC) segue o arquivo pendurado -> criado no alvo fora do âmbito do link com o corpo do pedido ``` O manipulador retorna ` 200 OK`, e o arquivo externo contém o corpo carregado. - **Principal direta (verificada ao vivo):** criação arbitrária de arquivos com conteúdo controlado pelo atacante em qualquer caminho inexistente fora do escopo do usuário para o qual o usuário do processo de navegador de arquivos pode escrever. - **Integridade do locante do cruzamento (racioinável com fonte):** em implantações multi- usuário os escopos são diretórios de irmãos sob uma raiz de servidor. Um usuário de baixa privacidade pode plantar arquivos em casa de outro usuário (um script, uma página HTML mais tarde servida, uma configuração dos trusts da vítima). - **Persistência / RCE em implantações permissivas (razão- fonte):** criando um `~/.ssh/authorized_keys` ainda não existente para a conta de serviço, um arquivo sob um diretório web- servido ou executado posteriormente, um cron ou fragmento de perfil. A imagem oficial docker é executada como UID não-root 1000, que limita isso a qualquer coisa que o usuário possua. Implementações de metal- descartado/sistema que executam o Navegador de Arquivos como raiz elevam isto para gravação de arquivos de nível host e RCE. - **Limite de alcance (honestidade):** isto é criação de arquivos, não sobreescreva. Sobreposição de um arquivo existente fora do âmbito é genuinamente bloqueada, porque um alvo existente faz com que o link "escaping" e `inside()` o rejeite. O controle negativo confirma isso.

Remediação sugerida. Para gravar/criar/ troncar operações, não trate um link simbólico final pendurado como seguro apenas porque o ancestral mais próximo está dentro do escopo. O walk- up em `inside()` deve tratar "a folha é um link simbólico" como um candidato de fuga em vez de validar seu pai: - Em `guard()` / `within()`, `Lstat` o alvo. Se for um link simbólico, `Readlink` ele, resolve o alvo do link lexicamente (junte-se com o diretório do link, `Limpo`), e requeira que esse alvo esteja dentro da raiz do escopo, independentemente de existir atualmente. - Mais robusto: resolve os componentes do caminho um a um para a operação em tentativa, ou abra o componente final com `O_NOFOLLOW` (`unix.Openat(... O_ NOFOLOW)`) assim que a criação através de um link simbólico falha com o `ELOOP`; ou ` fstat` o descritor após a abertura e verifica que ele se resolve dentro do escopo antes de escrever. Adicione um teste de regressão espelhando `TestTusHandlersRejeitarSymlinkScopeEscape`, mas com um alvo pendurado (`os.Symlink(percurso de arquivo.Junte-se(fora, "novo arquivo"),...)`), afirmando ambos os um 4 xx e que nenhum arquivo é criado fora do escopo.

- Correção incompleta de CVE- 2026 - 54094 / ` GHSA- 239 w- m 3 h 6 - ch 8 v`. - Código afetado: `files/scoped.go` (`ScopedFs.inside`, `ScopedFs.OpenFile`, `ScopedFs.Create`), alcançado a partir de `http/resource.go` (`resourcePostHandler`, `writeFile`) e `http/tus_handlers.go`. - Confirmado sem empaquetado no `be` 23 ab 3 a 15 bf 957928 ecfed 88 de 5 ab 67850 c 1 b 9 c` (` v) 2.63.15 `). Registro de aconselhamento: GHSA- 8 wc 8 - hf 36 - mjh 9. Identificadores relacionados: CVE- 2026 - 55668. Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 20 T 21: 17: 43.000 Z e lista a sua última modificação como 2026 - 07 - 20 T 21: 17: 43.000 Z.

Gravidade: MODERAR. Dados de pontuação publicados: CVSS_V 3: CVSS: 3.1 /AV:N/AC:H/PR:L/UI:N/S:C/C:N/I:H/A:N. Informações sobre software e versão afetadas: Go pacote github.com/filebrowser/filebrowser/v 2 — ECOSISTEM: introduzido 0, corrigido 2.63.16. Classificação e evidência: identificadores de fraqueza CWE- 22, CWE- 59. O registro contém 5 suporte de referências nestes tipos: WEB, AVISO, EMBALAGAMENTO.