如何在Node.js中防止恶意*.js脚本执行

5
我正在使用Node.js创建Web服务。在实现中,我使用了许多通过npm安装的第三方模块。如果其中有恶意*.js脚本,则存在安全问题。例如,恶意代码可能会删除我的所有磁盘文件,或者悄悄地收集秘密数据。
针对此事,我有几个问题:
1. 如何检测模块是否存在安全问题? 2. 如何防止Node.js执行恶意*.js脚本?
非常感谢您能分享任何构建node.js服务的经验。
谢谢, Jeffrey
4个回答

5
你没有提出的一个问题是,一个模块可能会尝试直接连接到你的数据库本身,或者到你内部网络上的其他服务。通过设置模块难以找到的密码,可以避免这种情况发生。
1. 限制磁盘访问
该项目是去年在NodeConf上介绍的,它试图在你所描述的情况下精确地限制文件系统访问。

https://github.com/yahoo/fs-lock

"该模块的目标是帮助您在加载第三方模块时限制其访问权限。"

这听起来有点像Jeffrey在Plato的回答中提出的建议。

(如果您想进一步了解如何挂钩操作系统调用,hookit项目可能会提供一些想法。虽然它目前只包装回调函数,但它可能会提供关于挂钩什么以及如何挂钩的灵感。这里是一个示例。)

2. 分析敏感数据的流动

如果您只担心数据窃取(而不是文件系统或数据库访问),那么可以将重点放在以下问题上:

  • 您最应该关注那些传递了敏感数据的包。假设您的 Web 服务上的一些数据已经公开展示给了公众!

  • 大多数包将无法访问您的应用程序的完整堆栈,只能访问您传递给它们的数据位。如果一个包仅传递了少量敏感数据,并且从未传递其他数据,它可能无法对其接收到的数据进行任何恶意操作。(例如,如果您将所有用户名传递给一个包进行处理,将所有地址传递给另一个包,那么这就比将所有用户名地址信用卡号码传递给同一个包更加安全!)

  • 识别您的应用程序中的敏感数据,并注意它们传递到哪些模块的哪些函数中。

3. 执行高效的代码审查

您可能不需要去Github阅读代码。大多数软件包都在其安装文件夹内的node_modules中提供所有源代码。(但有一些软件包提供二进制文件,这些自然更难验证。)
如果您确实想要检查代码,可能有几种方法可以减少工作量:
  • 为了保护你自己的应用程序,你不需要阅读项目中所有包的全部源代码。你只需要审查那些实际被调用的函数。

  • 你可以通过阅读代码或使用基于文本的调试器GUI调试器来跟踪代码。(当然,你应该注意分支,不同的输入可能会导致调用模块的不同部分)

  • 在调用你不信任的模块时设置断点,这样你就可以逐步执行被调用的代码并查看它做了什么。你可能能够得出结论,只有模块的一小部分被使用,因此只需要验证那部分代码。

  • 虽然跟踪流程应该涵盖运行时敏感数据的问题,但为了检查文件访问或数据库访问,我们还应该查看每个所需模块的初始化代码以及从那里进行的所有调用(包括require)。

4. 其他措施

package.json中锁定每个包的版本号可能是明智的,这样你就不会在决定需要时意外安装新版本的包。

您可以使用社交因素来建立对包的信心。检查作者的声誉。他是谁,他为谁工作?作者及其雇主是否有声誉要维护?同样,谁使用他的项目?如果该包非常受欢迎,并被行业巨头使用,则很可能已经有其他人审查了代码。

您可能希望访问Github并通过“观察”存储库来为您正在使用的所有顶级模块启用通知。这将在将来报告包中任何漏洞时通知您。


您可以通过像Node Security Projectretire.jsSnyk这样的工具获取有关软件包漏洞(不一定是恶意的)的通知。 - joeytwiddle

0

如果您的项目依赖树足够大,审查所有依赖项不是一种可行的长期策略。

Joey的原始答案提供了一些针对特定情况可以使用的对策。我也看到过https://github.com/berstend/node-safe - 可以让您在Mac上稍微更安全一些。

然而,问题的一个通用解决方案正在形成中。

如何保护项目免受恶意软件包的影响

  1. 确保您不运行生命周期(postinstall)脚本,除非它们是已知且必要的(请参见我的有关此主题的演讲
  2. 将第三方代码放入隔间中,锁定环境,决定向每个软件包传递哪些强大的API。

第二步需要使用Compartment,这是TC39中的一个正在进行中的工作https://github.com/tc39/proposal-compartments/

但是已经存在一个shim。并且已经在该shim之上构建了一些工具。

你可以直接使用SES-shim并实现自己的控件,或者使用LavaMoat的便利性。

LavaMoat允许您生成和调整每个包策略,您可以决定它应该访问哪些全局变量和内置变量。LavaMoat还提供了一个工具来管理安装脚本。

这是我的关于SES和LavaMoat的演讲,其中包含演示。

如何设置LavaMoat

有关更多详细信息,请参见LavaMoat文档

  1. 通过@lavamoat/allow-scripts禁用/允许依赖项生命周期脚本(例如“postinstall”)
npm i --ignore-scripts -D @lavamoat/allow-scripts
npx --no-install allow-scripts setup
npx --no-install allow-scripts auto
  • 然后,在 package.json 中编辑 allow-list
  • 每次安装/重新安装后运行 allow-scripts
  1. 在 lavamoat-node 中运行您的服务器或构建过程
npm i -D lavamoat

在你的 package.json 文件中添加类似以下内容:
"scripts": {
  "lavamoat-policy": "lavamoat app.js --autopolicy",
  "start": "lavamoat app.js"
  • 每次更改依赖树并审查策略时,请运行 lavamoat-policy(参见:策略覆盖)
  • 运行 npm start 启动您的应用程序

免责声明:我为LavaMoat和Endo做出了贡献。它们是采用宽松许可证的开源项目。

0
我建议在这种情况下使用虚拟化技术。 我更倾向于使用Docker进行容器化。 你可以运行预先安装了构建工具(如node等)的Docker镜像,并将工作目录挂载为卷。
例如(根据你的需求更改端口和node版本):
docker run --rm -it \
  -v "$PWD:$PWD" \
  -w "$PWD" \
  -p "127.0.0.1:9000:9000" "node:18" bash

0

大多数(全部?)模块在Github上都有源代码可用,您可以阅读源代码并查找安全问题,或者聘请安全专业人员来完成工作。

我只是冒险尝试 - 尽管我倾向于使用具有数百个提交、积极维护和问题列表的流行软件包。


1
感谢您分享您的经验。当使用新模块或更新现有模块时,检查每个模块的源代码是一项巨大的工作量。我正在考虑覆盖 require 函数,以便可以拦截风险模块(如子进程、文件系统、进程、操作系统、虚拟机)的加载请求。但这并不能填补所有漏洞。 - Jeffrey
一个想法:你可以在一台机器上设置主应用程序,然后在第二台机器上设置第二个应用程序。第二个应用程序将处理所有的 require 语句,因此在发生漏洞的情况下,它只能影响第二台机器(可能)。第一个应用程序将与第二个应用程序通信,但不会直接 require 任何东西(除了你需要进行通信的内容)。 - Plato
2
即使在我的项目中,构建工具及其依赖项也包括338个独特的软件包(没有应用程序代码)。您需要10个“安全专业人员”来审核它们。尽管我认为审计是一个好主意,但告诉 N 家公司雇用人员反复审计同一软件包的同一版本是愚蠢的。问题在于npm生态系统。只需在npmjs.com上注册并上传软件包即可开始审核。服务器可能不安全,因此黑客可以更改软件包。由于它们没有签名,因此无法检测更改。 - try-catch-finally

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接