Unc1e
Unc1e
NodeJs安全笔记

Node.Js安全

eval()

eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。和PHP中eval函数一样,如果传递到函数中的参数可控并且没有经过严格的过滤时,就会导致漏洞的出现。

利用
执行命令(有回显)

https://zuoxueba.org/wp-content/uploads/2020/10/008f35b3107ef6c980d0e4074739d39b.png

反弹shell(linux):

读取文件(linux), 利用curl的-F参数来将文件内容带外

https://zuoxueba.org/wp-content/uploads/2020/10/e4215027e7fae92a091601994995f44a.png

但其实用-d参数来POST DATA也是可以的

https://zuoxueba.org/wp-content/uploads/2020/10/b32bf906bc430e53c96035061ccbb27e.png

如果上下文中没有require(类似于Code-Breaking 2018 Thejs)

Node.js 原型污染漏洞

Javascript原型链参考文章:继承与原型链

  • 在javascript中,每一个实例对象都有一个prototype属性,prototype 属性可以向对象添加属性和方法。
    例子:

  • 在javascript中,每一个实例对象都有一个__proto__属性,这个实例属性指向对象的原型对象(即原型)。
    可以通过以下方式访问得到某一实例对象的原型对象:

  • 不同对象所生成的原型链如下(部分):

原型链污染原理

对于语句:object[a][b] = value
如果可以控制a、b、value的值,将a设置为__proto__,我们就可以给object对象的原型设置一个b属性,值为value。
这样所有继承object对象原型的实例对象在本身不拥有b属性的情况下,都会拥有b属性,且值为value。
来看一个简单的例子:

看吧, 直接查看object1,只看到有ab两个属性,但通过__proto__.foo给它的【原型】赋值后, 查看object1.foo却出现了Hello World, 证明【原型】已被污染.

merge操作导致原型链污染

merge操作是最常见可能控制键名的操作,也最能被原型链攻击。
简单例子:

最终输出的结果为:

看——object3根本没有b属性,可见b是从原型中获取到的,
说明Object已经被污染了。

同时, 需要注意的点是:在JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历object2的时候会存在这个键。

实战

这是一篇价值一万美刀的漏洞报告, 整个挖洞的逻辑是: 先FUZZ参数, 通过报错信息了解到后端是nodejs, 且使用了Dustjs这款开源组件, 进而审计到关键的漏洞点, 也就是我们的eval代码注入点, 然后利用数组, 绕过【对字符串的过滤】,进而实现RCE
[demo.paypal.com] Node.js code injection (RCE)
我将其中涉及到的核心部分代码整理到了下面, 读者可自行复现, 感受一下代码注入的快乐

这里是一些可用的payload

CodeBreak的Thejs漏洞

这个题目已经有很多的分析文章了,但因为它是一个比较好的学习原型链污染的题目,还是值得自己再过一遍。题目源码下载:http://code-breaking.com/puzzle/9/直接npm install可以把需要的模块下载下来。

通过构造chile_process.exec()就可以执行任意代码了。最终可以构造一个简单的Payload作为传递给主页面的的POST数据(windows调用计算器):

(这里直接用require会报错:ReferenceError: require is not definedp神给了一个更好的payload:

在复现时, 一定要注意请求头中:Content-Type: application/json, 直接注入即可

https://zuoxueba.org/wp-content/uploads/2020/10/cbd5bbd79620fc201ee5374c4c7eaab4.png

CISCN2020 Final-Monster Battle

这是源码, 先npm install安装依赖, 再node app.js运行环境即可
题目给了源码, 打斗的逻辑并不复杂, 关键部分其实主要就下面这些

那么只需污染没有初始化的参数即可,要么【让我们变强】,要么【让怪兽变弱】,显然前者更为容易,因为有一个特殊的机——buff。
在源码中, buff的作用是在计算纯粹伤害的时候体现,因此,只要我们将buff的值设置为很大,即可达到瞬秒怪兽的效果。

当然,此题还有一点需注意,要想保持高buff, 对人物角色和技能都有一定限制

上面这点, 决定了我们的身份只能是high_priest, 因为当流程进入其它两个分支时, 都会将buff变为正常值.
而下面的代码, 则决定了我们的技能最好不要是BYZF, 因为当进入这个分支时, buff值也会变为正常值(可以设定为第二、三回合再使用技能, 但不够暴力没内味儿)

最终payload, 同样:请注意请求头Content-Type: application/json

https://zuoxueba.org/wp-content/uploads/2020/10/34aa2b9096a82d2c15d47ab97b541b75.png

附录:OWASP NodeJS security cheatsheet

翻译版本下载链接

应用安全

设置请求大小限制

请求主体的缓冲和解析对于服务器而言可能会占用大量资源。如果对请求的大小没有限制,则攻击者可以使用大型请求正文发送请求,从而耗尽服务器内存或填充磁盘空间。

但为【所有请求】都设置请求大小限制可能不是正确的行为,因为某些请求(例如用于将文件上传请求)有更多请求内容。

另外,由于解析JSON是一项阻塞操作,因此使用JSON类型的输入比使用multipart输入更为危险。因此,您应该为不同的内容类型设置请求大小限制。

您可以使用Express中间件非常轻松地完成此任务,如下所示:
app.use(express.urlencoded({ limit: “1kb” }));
app.use(express.json({ limit: “1kb” }));
app.use(express.multipart({ limit:”10mb” }));
app.use(express.limit(“5kb”)); // this will be valid for every other content type

同时应注意,攻击者可以更改请求的内容类型(content type)来绕过请求大小限制。因此,在处理请求之前,应针对请求标头中所述的内容类型验证请求中包含的数据。如果每个请求的内容类型验证严重影响性能,则您只能验证特定的内容类型或请求的大小大于预定大小。

执行输入验证

输入验证是应用程序安全性的关键部分。输入验证失败可能导致许多不同类型的应用程序攻击。其中包括SQL注入,跨站点脚本编写,命令注入,本地/远程文件包含,拒绝服务,目录遍历,LDAP注入和许多其他注入攻击。为了避免这些攻击,应首先清理对应用程序的输入。最好的输入验证技术是使用接受输入的白名单。但是,如果无法做到这一点,则应首先根据预期的输入方案检查输入,并应逃避危险的输入。为了简化Node.js应用程序中的输入验证,提供了一些模块,如 validator和mongo-express-sanitize。有关输入验证的详细信息,请参阅输入验证备忘单。

执行输出转义

除了输入验证之外,您还应转义通过应用程序显示给用户的所有HTML和JavaScript内容,以防止跨站点脚本(XSS)攻击。您可以使用escape-html或node-esapi库执行输出转义。

采取预防措施防止暴力破解

暴力破解是所有Web应用程序的常见威胁。攻击者可以使用暴力破解作为密码猜测攻击来获取帐户密码。因此,应用程序开发人员应采取预防措施,防止暴力攻击,尤其是在登录页面中。 Node.js为此提供了几个模块。Express-bouncer,express-brute和rate-limiter, 它们只是一些示例。

根据您的需求和要求,您应该选择一个或多个这些模块并相应地使用。Express-bouncer和Express-brute模块的工作原理非常相似,它们都会增加失败请求后的延迟。它们都可以安排为特定的路由。这些模块可以按如下方式使用:

除了express-bouncer和express-brute之外,rate-limiter模块还有助于防止强行攻击。它使您能够指定特定IP地址在指定时间段内可以发出多少个请求。

验证码的使用也是防止暴力破解的另一种常见机制。有为Node.js CAPTCHAs开发的模块。Node.js应用程序中使用的常见模块是svg-captcha。demo如下:

此外,建议使用帐户锁定来使攻击者远离您的有效用户。使用mongoose之类的许多模块都可以锁定帐户。您可以参考此博客文章,了解如何在mongoose中实现帐户锁定。

使用Anti-CSRF token

跨站点请求伪造(CSRF)旨在代表经过身份验证的用户执行授权的操作,而用户不知道该操作。CSRF攻击通常用于状态更改请求,例如更改密码,添加用户或下订单。Csurf是可用于减轻CSRF攻击的中间件。demo如下:

编写此代码后,您还需要添加csrfToken到HTML表单,可以很容易地完成以下操作:

有关跨站点请求伪造(CSRF)攻击和预防方法的详细信息,您可以参考跨站点请求伪造预防。

防止HTTP参数污染

HTTP参数污染(HPP)是一种攻击方式,攻击者使用相同的名称发送多个HTTP参数,这会使您的应用程序以不可预测的方式解释它们。当发送多个参数值后,Express会将它们填充到数组中。为了解决这个问题,可以使用hpp模块。使用时,该模块将忽略req.query和/或中为参数提交的所有值,req.body而仅选择最后提交的参数值。您可以按以下方式使用它:
var hpp = require(‘hpp’);
app.use(hpp());

使用对象属性描述符

对象属性包括3个隐藏属性:(writable如果为false,则不能更改属性值),enumerable(如果为false,则不能在for循环中使用属性)和configurable(如果为false,则不能删除属性)。通过分配定义对象属性时,这三个隐藏属性默认设置为true。这些属性可以设置如下:

除此之外,还有一些对象属性的特殊功能。Object.preventExtensions()防止将新属性添加到对象。

平台安全性

请勿使用危险功能

有一些JavaScript函数很危险,应尽可能避免使用此类功能和模块, 仅在绝对必要的情况下使用。

第一个例子是【eval()函数】。此函数接受一个字符串参数,并将其作为任何其他JavaScript源代码执行。结合用户输入,此行为固有地导致远程执行代码漏洞。同样,【调用child_process.exec函数】也很危险。该函数充当bash解释器,并将其参数发送到/ bin / sh。通过向此功能注入输入,攻击者可以在服务器上执行任意命令。

除了这些功能之外,还有一些模块在使用时需要特别注意。例如,【fs模块处理文件系统操作】。但是,如果不正确地清理用户输入到该模块,则您的应用程序可能容易受到文件包含和目录遍历漏洞的攻击。同样,该vm模块提供用于在V8虚拟机上下文中编译和运行代码的API。由于它可以自然执行危险的动作,因此应在沙箱中使用它。

公平地说,这些功能和模块均不应使用,但是,应谨慎使用它们,尤其是在与用户输入一起使用时。另外,还有一些其他功能可能会使您的应用程序容易受到攻击, nodejs中常见的危险函数列表如下

漏洞利用

简单举个例子

慎用正则表达式

正则表达式拒绝服务(ReDoS)是一种使用正则表达式的拒绝服务攻击。某些正则表达式(Regex)实现会导致极端情况,从而使应用程序非常慢(与输入大小成指数关系)。攻击者可以使用这种正则表达式实现使应用程序陷入这些极端情况并挂起很长时间。

通常,这些正则表达式是通过重复分组和重叠重叠进行开发的。例如,以下正则表达式^(([a-z])+.)+A-Z+$可用于指定Java类名称。但是,很长的字符串(aaaa … aaaaAaaaaa … aaaa)也可以与此正则表达式匹配, 从而导致性能下降。有一些工具可以检查正则表达式是否有可能导致拒绝服务。一个例子是vuln-regex-detector。
使用严格模式

JavaScript具有许多不应该使用的不安全和危险的旧功能。为了删除这些功能,ES5为开发人员提供了严格的模式。使用此模式,将抛出以前静默的错误。它还可以帮助JavaScript引擎执行优化。在严格模式下,以前接受的错误语法会导致实际错误。由于这些改进,您应该始终在应用程序中使用严格模式。为了启用严格模式,您只需要”use strict”;在代码之上编写即可。

以下代码将ReferenceError: Can’t find variable: y在控制台上生成,除非使用严格模式,否则不会显示该代码。

遵守一般应用程序安全性原则

该列表主要关注Node.js应用程序中常见的问题。另外,针对这些问题的建议针对于Node.js环境。除此之外,无论应用程序服务器中使用哪种技术,都有适用于Web应用程序的一般设计安全原则。在开发应用程序时,还应牢记这些原则。另外,您始终可以参考OWASP备忘单系列,以了解有关Web应用程序漏洞和针对这些漏洞的缓解技术的更多信息。

参考文章

发表评论

textsms
account_circle
email

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

Unc1e

NodeJs安全笔记
Node.Js安全 eval() eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。和PHP中eval函数一样,如果传递到函数中的参数可控并且没有经过严格的过滤时,就会导致漏洞的出现…
扫描二维码继续阅读
2020-10-12
%d 博主赞过: