Skip to main content

Como padrões inseguros do npm podem roubar seus atalhos de teclado no macOS

Escrito por
feature open source

28 de junho de 2023

0 minutos de leitura

Pacotes maliciosos do npm e seus perigos são tema frequente de discussão — sejam centenas de pacotes de malware Cobalt Strike de comando e controle, typosquatting ou malware em geral publicado no registro do npm (incluindo PyPI e outros). Para ajudar desenvolvedores e mantenedores a se protegerem desses riscos de segurança, a Snyk publicou um guia de boas práticas de segurança para o npm.

Dito isso, o escopo do ataque a seguir, sobre o qual Yagiz Nizipli alertou mantenedores de longa data, e o risco real de comprometimento de dados são um ótimo exemplo da importância de minimizar os riscos de execução arbitrária de comandos por gerenciadores de pacotes, como ocorre por meio dos hooks de ciclo de vida postinstall do npm.

Scripts de ciclo de vida do npm

O Node Package Manager (npm) oferece um conjunto de scripts para que desenvolvedores e mantenedores de pacotes gerenciem os eventos do ciclo de vida de um pacote. Esses scripts são muito úteis para desenvolvedores, pois permitem realizar diversas tarefas ou configurações durante a instalação do pacote. Por exemplo, com scripts postinstall, é possível automatizar tarefas como compilar recursos, configurar variáveis de ambiente, executar migrações ou qualquer outra tarefa que possa ser executada automaticamente.

A propriedade scripts de um arquivo package.json define os comandos acionados pelo ciclo de vida do pacote e pelas dependências do pacote em desenvolvimento. Atualmente, o npm oferece suporte a um número limitado de scripts de ciclo de vida em qualquer propriedade scripts de um arquivo package.json.

Para simplificar, o restante deste artigo se concentra no comando postinstall. No entanto, todos os conceitos apresentados também se aplicam a outras operações de ciclo de vida.

Incidentes de segurança anteriores

Vários incidentes de grande repercussão tiveram impacto real sobre desenvolvedores JavaScript, incluindo:

Vários outros incidentes de segurança envolvendo JavaScript e Node.js estão reunidos no repositório Awesome Node.js Security.

Segurança dos dados armazenados

Profissionais de segurança usam o termo Data-at-rest para se referir à proteção de ativos quando os dados estão armazenados, em contraste com os dados em trânsito ou em processamento. O foco é proteger informações confidenciais armazenadas em bancos de dados, sistemas de arquivos ou armazenamento persistente. A segurança dos dados armazenados busca impedir acesso não autorizado, divulgação ou adulteração dos dados enquanto estão inativos. Há várias medidas para garantir essa segurança, como:

  • Descriptografia sob demanda: descriptografe apenas os dados necessários para a tarefa em andamento e mantenha os demais criptografados para impedir o acesso não autorizado.

  • Lógica de controle de acesso: valide a identidade de quem solicita acesso por meio de mecanismos como senha, autenticação de dois fatores ou biometria fornecida pelo sistema operacional (como o Face ID), limitando a exposição do recurso a pessoas não autorizadas.

A superfície de ataque de um desenvolvedor

As boas práticas do setor nos orientam a seguir princípios no desenvolvimento de aplicativos. Elas oferecem muitas vantagens para o trabalho de diferentes equipes e desenvolvedores, mas também ampliam a superfície de ataque.

Que tipo de dado fica armazenado sem criptografia na máquina de um desenvolvedor?

  • Variáveis de ambiente em arquivos de texto simples, como .env (disponíveis para uso pelo pacote dotenv).

  • Arquivos de configuração de projetos armazenados como JSON, como config.json.

  • Chaves SSH para acessar Github/Gitlab, disponíveis na pasta ~/.ssh.

  • E... atalhos de teclado do macOS!

Substituições de texto no macOS

Por padrão, o macOS tem um recurso chamado Text Replacements, oculto no app Preferências do Sistema. Ele permite substituir rapidamente uma palavra por outra. Recentemente, descobri que um desenvolvedor de uma empresa conhecida usava substituições de texto para trocar a palavra-chave @card pelos dados do cartão de crédito. Embora o número do cartão, sem a data de validade nem o CVV, não permita que terceiros usem seu dinheiro, isso amplia a superfície de ataque que eles podem explorar.

Ajustes do teclado do macOS mostrando a substituição de texto que mapeia “@ssh-key” para “my-secret-password”
O recurso Substituições de Texto está disponível no aplicativo Preferências do Sistema, no item de menu `Teclado`.

Exfiltração das substituições de texto do teclado

Os atalhos de teclado são armazenados em defaults, que corresponde a um arquivo .plist no sistema de arquivos, em algum lugar da sua pasta local. Ao executar o comando a seguir, você verá as substituições de texto configuradas, que também podem ser consultadas no app Preferências do Sistema.

Lembre-se de que o código a seguir não precisa de acesso sudo e pode ser executado por qualquer processo no seu computador.

> defaults read -g NSUserDictionaryReplacementItems
(
  {
    on = 1;
    replace = "@ssh-key";
    with = "my-secret-password";
  }
)

O mesmo comando pode ser executado por meio de execSync no Node.js e analisado sem dificuldade usando a operação de ciclo de vida postinstall, compatível com o gerenciador de pacotes npm.

Veja um exemplo de script Node.js que agentes maliciosos podem usar para acessar as substituições de texto do macOS e exfiltrar dados confidenciais:

import { execSync } from 'node:child_process'

const decoder = new TextDecoder()
const res = execSync('defaults read -g NSUserDictionaryReplacementItems')
const text_replacements = decoder.decode(res)

console.log(text_replacements)

Para garantir que o código acima seja executado quando o pacote for instalado, atualize o arquivo de manifesto do pacote package.json da seguinte forma:

{
  "name": "my-useful-library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "node ./retrieve.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Quando distribuída pelo npm e baixada por um desenvolvedor, esta biblioteca executa diretamente nosso script personalizado para recuperar e processar as substituições do teclado. Se você não prestar atenção, é fácil deixar passar a linha > node ./retrieve.js.

➜  vulnerable npm i

> my-useful-library@1.0.0 postinstall
> node ./retrieve.js

up to date, audited 1 package in 192ms

found 0 vulnerabilities
➜  vulnerable

Proteção

Como desenvolvedor, o que você pode fazer para reduzir os riscos de segurança de pacotes maliciosos do npm e os riscos gerais da execução arbitrária de comandos por pacotes na sua árvore de dependências?

Ignore scripts durante a instalação de pacotes do npm

É possível se proteger de pacotes que usam scripts postinstall. O npm oferece a opção de configuração --ignore-scripts durante a instalação de pacotes.

➜  npm i <package name> --ignore-scripts
up to date, audited 1 package in 124ms
found 0 vulnerabilities

Use configurações padrão seguras no npm

O gerenciador de pacotes npm também tem um arquivo de configuração chamado .npmrc. Você pode alterar as preferências padrão usando a CLI do npm para garantir configurações seguras:

➜  npm config set ignore-scripts true
➜  npm i
up to date, audited 1 package in 126ms
found 0 vulnerabilities

Armazenamento seguro

Acima de tudo, nunca armazene informações confidenciais em texto simples. Se precisar armazená-las assim por outros requisitos, sempre restrinja o acesso ao recurso com autenticação multifator.

Comece a resolver desafios de capture the flag

Aprenda a resolver desafios de capture the flag assistindo sob demanda ao nosso workshop virtual introdutório.