恶意NPM软件包及其危险一直是讨论的经常话题 - 是hundreds of command-and-control Cobalt Strike malware packages,typosquatting还是发布给NPM注册表(包括PYPI等)的通用恶意软件。为了帮助开发商和维护者抗衡这些安全风险,Snyk published a guide to npm security best practices。
所有话虽如此,Yagiz Nizipli向长期维护者提醒的以下攻击范围以及与数据折衷相关的现实风险是一个很好的例子,说明将使用软件包执行任意命令执行的风险是多么重要经理,例如通过NPM的postinstall
生命周期钩使用的经理。
NPM的生命周期脚本
节点软件包管理器(NPM)为开发人员和软件包维护者提供了一组脚本,以维护软件包的生命周期事件。这些脚本通过使开发人员能够执行各种任务或配置作为软件包安装过程的一部分,从而为开发人员提供了重要价值。例如,使用postinstall
脚本,开发人员可以自动化任务,例如构建资产,设置环境变量,运行迁移或可以自动执行的任何任务。
package.json
文件的scripts
属性定义了由软件包的生命周期触发的命令以及您正在开发的软件包的依赖。截至今天,npm
在package.json
文件的任何scripts
属性中支持limited number of life cycle scripts。
为简单起见,本文的其余部分将重点关注postinstall
命令。但是,本文提供的所有概念也适用于其他生命周期操作。
过去的安全事件
有几起备受瞩目的事件对JavaScript开发人员产生了实际影响,包括:
- Oscar Bolmsten发现的cross-env security incident。
- The eslint-scope security compromise.
- 加密货币应用程序开发人员的event-stream spear-headed attack。
在Awesome Node.js Security存储库上策划了许多其他JavaScript和Node.js安全事件。
及格安全性
安全专业人员在数据存储或在Data-at-rest
中存储时确定资产的保护,而不是在运输或正在处理的情况下进行保护。它重点是保护存储在数据库,文件系统或持续存储中的敏感信息。止境安全性旨在防止在休眠状态时未经授权的访问,披露或篡改数据。可以使用各种措施来确保及格的数据安全性,例如:
- 按需解密:仅解密执行当前任务所需的数据并存储其余的数据加密以防止禁止访问。
- 访问控制逻辑:通过密码,两因素身份验证或操作系统提供的生物特征(例如FaceID)等机制验证请求者的身份不需要的人的资源。
开发人员的攻击表面
行业最佳实践迫使我们使用并遵循原则来开发应用程序。这些最佳实践在与不同的团队和开发人员合作时也具有许多优势,但也会增加攻击表面。
开发人员机器中未加密的哪种数据?
- 环境变量通过纯文本文件,例如
.env
(可通过dotenv
软件包消费)。 - 用于存储为JSON文件的项目的配置文件,例如
config.json
。 - ssh键,用于访问github/gitlab,可在
~/.ssh
文件夹中可用。 - 和... macOS键盘快捷键!
MacOS文本替换
默认情况下, macOS具有隐藏在系统首选项应用程序中的名为Text Replacements
的功能。此功能允许用户快速用另一个单词替换单词。就在最近,我了解到,一家知名公司的一家开发人员正在使用文本替换来用信用卡信息替换@card
关键字。即使没有到期日期或CVV的信用卡号也不会向局外人展示您的钱,但它为他们增加了攻击表面。
拆卸键盘文本更换
键盘快捷键存储在defaults
下,该快捷键对应于您本地文件夹中某个地方的文件系统备份的.plist
文件。执行以下命令将返回您已配置的文本替换,也可以通过系统首选项应用程序可用。
请记住,以下代码不需要sudo
访问,并且可以通过计算机中的任何过程执行。
> defaults read -g NSUserDictionaryReplacementItems
(
{
on = 1;
replace = "@ssh-key";
with = "my-secret-password";
}
)
可以通过node.js中的execSync
执行相同的命令,并通过npm
package manager支持的postinstall
生命周期操作而没有任何麻烦。
以下是node.js脚本的示例
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)
要确保在安装此软件包时运行上述代码,我们将如下更新包装清单文件,如下所示:
{
"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"
}
通过NPM分发并由开发人员下载时,该库将直接执行我们的自定义脚本以检索和处理键盘替换。如果您要小心,很容易错过包含> 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
保护
作为开发人员,您该怎么做,以减轻恶意NPM软件包的安全风险以及依赖树中包裹执行任意命令执行的一般安全问题?
忽略NPM软件包安装上的脚本
可以保护自己免受利用postinstall
脚本的包裹。 NPM安装软件包时提供--ignore-scripts
配置。
➜ npm i --ignore-scripts
up to date, audited 1 package in 124ms
found 0 vulnerabilities
使用安全的NPM默认值
NPM软件包管理器还具有一个名为.npmrc的配置文件。您可以使用npm
cli更改默认首选项以确保安全默认值:
➜ npm config set ignore-scripts true
➜ npm i
up to date, audited 1 package in 126ms
found 0 vulnerabilities
安全存储
最重要的是,您绝对不应用纯文本存储敏感信息。如果由于其他要求,必须将其存储在纯文本中,则应始终通过多因素身份验证使资源可访问。