路线

​ IDEA
​ Maven
​ Java Web
​ 反射
​ ASM/Javassist
​ JNDI:8u191低版本和高版本怎么打(反序列化/本地工厂),以及如何审计
​ RMI是什么
​ Java Agent:启动原理和RASP的实现原理
​ JMX/JDWP
​ 反序列化基础:gadget链、JEP290是什么
​ FastJson反序列化
​ WebLogic:二次反序列化、XML Decoder、IIOP/T3
​ Xstream反序列化
​ Hessian反序列化:dubbo
​ SnakeYAML反序列化
​ Shiro:Shiro经典漏洞、Padding Oracle漏洞形成原理、如何通过Shiro注入内存马
​ Struts2
​ Spring:Spring4Shell、Spring EL、SpringBoot Actuator利用
​ Tomcat:Tomcat AJP RCE
​ 内存马原理:原理是什么、有哪些内存马 (动手实践调试每一种内存马)
​ 内存马如何查杀
​ Log4J
​ 其他组件漏洞:Apache Solr、Flink
​ 进阶:tabby、codeql等静态分析

反射可以学习下高版本的绕过,jndi这里尝试自己写一个简单的jndi exploit。java agent这里自己尝试能否写一个简易rasp。log4j这里尝试自己写burp工具

最后可以学习下jdbc攻击,尝试自己写一个fake server

image-20230221200505390

分层模型

image-20230221195026804

MVC框架

image-20230221195459454

Servlet

image-20230221201728646

反射

image-20230221220505941

image-20230221230441694
1
2
3
4
5
6
7
8
Class cls = Class.forName("java.lang.String");

Class cls = String.class;

String s = "Hello";
Class cls = s.getClass();


image-20230221230422653 image-20230221225953768

总结

我们先获取到类型A的Class对象,通过Class对象的newInstance方法可以得到A的实例

通过Class对象可以获取到Constructor对象,进一步可以使用Constructor对象来得到A的实例

通过Class对象可以获取到Method对象,通过Method的invoke方法我们可以调用一些方法

通过Class对象可以获取到Field对象,我们可以对这个实例的一些字段进行赋值取值操作

Constructor对象封装了构造方法的所有信息;

通过Class实例的方法可以获取Constructor实例:getConstructor()getConstructors()getDeclaredConstructor()getDeclaredConstructors()

通过Constructor实例可以创建一个实例对象:newInstance(Object... parameters); 通过设置setAccessible(true)来访问非public构造方法

Java的反射API提供的Method对象封装了方法的所有信息:

通过Class实例的方法可以获取Method实例:getMethod()getMethods()getDeclaredMethod()getDeclaredMethods()

通过Method实例可以获取方法信息:getName()getReturnType()getParameterTypes()getModifiers()

通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters)

通过设置setAccessible(true)来访问非public方法;

通过反射调用方法时,仍然遵循多态原则。

Java的反射API提供的Field类封装了字段的所有信息:

通过Class实例的方法可以获取Field实例:getField()getFields()getDeclaredField()getDeclaredFields()

通过Field实例可以获取字段信息:getName()getType()getModifiers()

通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段

通过反射读写字段是一种非常规方法,它会破坏对象的封装

通过Class对象可以获取继承关系:

  • Class getSuperclass():获取父类类型;
  • Class[] getInterfaces():获取当前类实现的所有接口

通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现

JDK动态代理

代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了,只需要在代理类上增加就可以了

image-20230309112558148

一个最简单的动态代理实现如下:

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}

interface Hello {
void morning(String name);
}

在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用
  2. 通过 Proxy.newProxyInstance() 创建 interface 实例,它需要3个参数:
    1. 使用的ClassLoader,通常就是接口类的ClassLoader
    2. 需要实现的接口数组,至少需要传入一个接口进去
    3. 用来处理接口方法调用的InvocationHandler实例
  3. 将返回的Object强制转型为接口

动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning", String.class),
new Object[] { name });
}
}

其实就是JVM帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法

小结

Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例

动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的相对于静态代理类来说,无论有多少接口,动态代理只需要一个代理类

动态代理意义: 少修改代码 适配强
在反序列化漏洞中的作用

1、readObject -> 反序列化自动执行 2、invoke -> 有函数调用 3、拼接两条链 4、任意->固定

要利用反序列化的漏洞是需要一个入口类的,先假设存在一个能够漏洞利用的类为 B.f ,比如Runtime.exec这种,我们将入口类定义为A,我们最理想的情况是 A[O] -> O.f ,那么我们将传进去的参数 O 替换为 B 即可。但是在实战的情况下这种情况是极少的

回到实战情况,比如我们的入口类A存在 O.abc这个方法,也就是 A[O] -> O.abc

如果O 是一个动态代理类,O 的invoke方法里存在 .f 的方法,便可以漏洞利用了

1
2
3
A[O] -> O.abc
O[O2] invoke -> O2.f // 此时将 B 去替换 O2
最后 ----> O[B] invoke -> B.f // 达到漏洞利用效果

动态代理在反序列化当中的利用和readObject是异曲同工的:

readObject方法在反序列化当中会被自动执行,而invoke方法在动态代理当中会自动执行

类的动态加载

Maven基础

Maven是一个Java项目管理和构建工具,它可以定义项目结构、项目依赖,并使用统一的方式进行自动化构建,是Java项目不可缺少的工具

Maven就是是专门为Java项目打造的管理和构建工具,它的主要功能有:

  • 提供了一套标准化的项目结构
  • 提供了一套标准化的构建流程(编译,测试,打包,发布……)
  • 提供了一套依赖管理机制

Maven项目结构

一个使用Maven管理的普通的Java项目,它的目录结构默认如下:

1
2
3
4
5
6
7
8
9
10
a-maven-project
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
└── target

Maven是一个Java项目的管理和构建工具:

  • Maven使用pom.xml定义项目内容,并使用预设的目录结构
  • 在Maven中声明一个依赖项可以自动下载并导入classpath
  • Maven使用groupIdartifactIdversion唯一定位一个依赖

image-20230222095358757

JDBC编程

程序运行的时候,往往需要存取数据。现代应用程序最基本,也是使用最广泛的数据存储就是关系数据库。

Java为关系数据库定义了一套标准的访问接口:JDBC(Java Database Connectivity)

反序列化

序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象,一般用于远程调用、通过网络将对象传输至远程服务器、存储对象到数据库或本地等待重用等场景中

Java中的 ObjectOutputStream 类的 writeObject() 方法可以实现序列化,类 ObjectInputStream 类的 readObject() 方法用于反序列化。如果要实现类的反序列化,则是对其实现 Serializable 接口

远程服务接受不可信的数据并进行反序列化且当前环境中存在可利用的类时,就认为存在反序列化漏洞

常见触发点

  • JDBC 反序列化
  • JSON 反序列化

存在危险的基础库

  • com.mchange:c3p0 0.9.5.2
  • com.mchange:mchange-commons-java 0.2.11
  • commons-beanutils 1.9.2
  • commons-collections 3.1
  • commons-fileupload 1.3.1
  • commons-io 2.4
  • commons-logging 1.2
  • org.apache.commons:commons-collections 4.0
  • org.beanshell:bsh 2.0b5
  • org.codehaus.groovy:groovy 2.3.9
  • org.slf4j:slf4j-api 1.7.21
  • org.springframework:spring-aop 4.1.4.RELEASE

回显方式

  • 通过中间件特性回显
  • 通过抛出异常回显
  • 通过OOB回显
  • 通过写静态文件回显

RMI

RMI (Remote Method Invocation,远程方法调用) 能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端Java虚拟机中的对象上的方法。其中RMI标准实现是Java RMI,之外还有Weblogic RMI、Spring RMI等不同的实现

RMI中比较重要的两个概念是Stub和Skeleton,Stub和Skeleton对同一套接口进行实现,其中Stub由Client端调用,并不进行真正的实现,而是和Server端通信。Skeleton是Server端,监听来自Stub的连接,根据Stub发送的数据进行真正的操作

image-20230222203836536

image-20230222221709057

JNDI注入

JNDI注入是2016年由pentester在BlackHat USA上的 A Journey From JNDI LDAP Manipulation To RCE 议题提出的

其攻击过程如下

  1. 攻击者将Payload绑定到攻击者的命名/目录服务中
  2. 攻击者将绝对URL注入易受攻击的JNDI查找方法
  3. 应用程序执行查找
  4. 应用程序连接到攻击者控制的JNDI服务并返回Payload
  5. 应用程序解码响应并触发有效负载

OGNL表达式语言

OGNL(Object-Graph Navigation Language的简称),对象图导航语言,它是一门表达式语言,除了用来设置和获取Java对象的属性之外,另外提供诸如集合的投影和过滤以及lambda表达式等