java安全反射机制
反射
指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对任意一个对象,都能调用它的任意一个方法。这种动态获取信息,以及动态调用对象方法的功能,叫做java语言的反射机制。
Class类:代表一个类。
Field类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方法。
Constructor类:代表类的构造方法。
获取class对象 学生对象
package com.test;public class Student { public String name; private String sex; private int age; public Student () { System.out.println("hello" ); } public Student (String name, String sex, int age) { this .name = name; this .sex = sex; this .age = age; } private Student (String name, String sex) { this .name = name; this .sex = sex; } public String getName () { System.out.println(111 ); return name; } public void setName (String name) { this .name = name; System.out.println(name); } public String getSex () { System.out.println(222 ); return sex; } private void setSex (String sex) { System.out.println(sex); this .sex = sex; } public int getAge () { System.out.println(333 ); return age; } public void setAge (int age) { System.out.println(age); this .age = age; } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", sex='" + sex + '\'' + ", age=" + age + '}' ; } }
获取对象的方法有三种:
第一种:通过对象获取,因为任何对象都必须和class对象关联
Student student = new Student(); Class student1 = student.getClass(); System.out.println(student1.getName());
第二种:通过类对象直接获取
Class student1 = Student.class; System.out.println(student1.getName());
第三种:通过类加载器获得,因为类加载器读取class文件会返回class对象
Class student1 = Class.forName("com.test.Student" ); System.out.println(student1.getName());
实例化对象 newinstance()实例化对象,它生成的对象只能反射无参的构造函数
实例化无参构造方法
Student.class.newInstance(); Class.forName("com.test.Student" ).newInstance(); Object o = Student.class.getConstructor().newInstance(); System.out.println(o);
实例化有参构造方法
配合获取到有参构造方法来实例
Constructor<Student> constructor = Student.class.getConstructor(new Class[]{String.class, String.class, int .class}); Object o = constructor.newInstance(new Object[]{"aming" ,"boy" ,16 }); System.out.println(o);
实例化有参构造方法,调用私有构造方法
Constructor constructor = Student.class.getDeclaredConstructor(new Class[]{String.class}); constructor.setAccessible(true ); Object o = constructor.newInstance("aming" ); System.out.println(o);
获取所有构造函数
Student student = new Student(); Class c1 = student.getClass(); Constructor[] Constructors = c1.getConstructors();for (int i = 0 ;i<Constructors.length;i++){ System.out.println(Constructors[i]); }
调用对象方法 Method类,方法的对象,一个成员方法就是一个Method对象
Method getMethod(String name, Class[] params) – 使用特定的参数类型,获得命名的公共方法
Method[] getMethods() – 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) – 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() – 获得类声明的所有方法
invoke执行方法
如果这个方法是普通方法,那么第一个参数时类对象
如果这个方法是静态方法,那么第一个参数时类
实例化对象方法,调用有参方法
Student student = new Student(); Class c1 = student.getClass(); Method method = c1.getMethod("setName" ,String.class); method.invoke(c1.newInstance(),"xiaoming" );
实例化对象方法,调用无参方法
Student student = new Student(); Class c1 = student.getClass(); Method method = c1.getMethod("getName" ); method.invoke(c1.newInstance());
实例化对象方法,调用私有方法
反射机制的默认行为受限于Java的访问控制,可通过 setAccessible 绕过控制
Student student = new Student(); Class c1 = student.getClass(); Method method = c1.getDeclaredMethod("setSex" ,String.class); method.setAccessible(true ); method.invoke(c1.newInstance(),"boy" );
获取所有方法,并且打印
Student student = new Student(); Class c1 = student.getClass(); Method[] methods = c1.getDeclaredMethods();for (int i = 0 ;i<methods.length;i++){ System.out.println(methods[i]); }
获取成员变量 getFields()方法获取的是所有的Public的成员变量的信息
getDeclaredFields()获取的是该类所有声明的成员变量信息
set(Object obj, Object value)指定对象变量上此 Field 对象表示的字段设置为指定的新值
get(Object obj)获取属性的值
获取所有的Public的成员变量
Student student = new Student(); Class c1 = student.getClass(); Field[] fields = c1.getFields();for (Field field :fields){ String fieldName = field.getName(); System.out.println(fieldName); }
获取所有成员变量
Student student = new Student(); Class c1 = student.getClass(); Field[] fields = c1.getDeclaredFields();for (Field field :fields){ String fieldName = field.getName(); System.out.println(fieldName); }
设置成员变量,并且获取成员变量
package com.test;import java.lang.reflect.Field;public class Test { public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException { Student student = new Student(); Field field = student.getClass().getDeclaredField("sex" ); field.setAccessible(true ); field.set(student,"boy" ); String sex = (String)field.get(student); System.out.println(sex); } }
通过反射来执行系统命令 首先我们要开始学习Java的命令执行,了解整条调用链
单例模式 简单来说就是一个类只能构建一个对象的设计模式
public class Singleton { private static Singleton instance; private Singleton () {} public static Singleton getInstance () { if (instance == null ) { instance = new Singleton(); } return instance; } }
我们为什么要这样写?
一个类只能构建一个对象,不能随便new操作,所以构造方法是私有
getInstance用来获取单例对象的方法
如果单例instance初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式
如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式
饿汉主动找食物吃,懒汉躺在地上等着人喂
因为Instance是空,所以两个线程同时通过了条件判断,开始执行new操作:
这样一来,显然instance被构建了两次
双重检测机制
package com.test;public class Singleton { private static Singleton instance; private Singleton () {} public static Singleton getInstance () { if (instance == null ) { synchronized (Singleton.class){ if (instance == null ){ instance = new Singleton(); } } } return instance; } }
我们为什么怎么写?
为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)
进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程1构建完对象,线程2也已经通过了最初的判空验证,不做第二次判空的话,线程2还是会再次构建instance对象
像这样两次判空的机制叫做双重检测机制
存在的隐患
这种情况表面看似没什么问题,要么Instance还没被线程A构建,线程B执行 if(instance == null)的时候得到true;要么Instance已经被线程A构建完成,线程B执行 if(instance == null)的时候得到false。
JVM编译器指令重排
java中简单的一句instance = new Singleton
,会被编译器编译成如下JVM指令:
memory=allocate(); ctorInstance(memory); instance=memory;
但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:
memory =allocate(); instance =memory; ctorInstance(memory);
当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。
此时如果线程2抢占到CPU资源,执行 if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象 。如下图所示:
为了避免这种情况,我们需要在instance对象前面增加一个修饰符volatile
volatile修饰符阻止变量访问前后指令重排,保证指令执行顺序
package com.test;public class Singleton { private volatile static Singleton instance; private Singleton () {} public static Singleton getInstance () { if (instance == null ) { synchronized (Singleton.class){ if (instance == null ){ instance = new Singleton(); } } } return instance; } }
经过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:
memory =allocate(); ctorInstance(memory); instance =memory;
如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。
使用静态类实现单例模式
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton () {} public static Singleton getInstance () { return LazyHolder.INSTANCE; } }
注意点:
从外部无法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能得到单例对象INSTANCE。
INSTANCE对象初始化的时机并不是在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。因此这种实现方式是利用classloader的加载机制 来实现懒加载,并保证构建单例的线程安全。
静态内部类实现方法虽好,但是存在单例模式共同问题,无法防止反射重复构建对象
利用反射打破单例
package com.test;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public class Test { public static void main (String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor con = Singleton.class.getDeclaredConstructor(); con.setAccessible(true ); Singleton singleton1 = (Singleton)con.newInstance(); Singleton singleton2 = (Singleton)con.newInstance(); System.out.println(singleton1.equals(singleton2)); } }
如何防止反射的构建方式
通过enum语法糖,jvm会阻止反射获取篇枚举类的私有构造方法
枚举类
package com.test;public enum SingletonEnum { INSTANCE; public void printSomething () { System.out.println("这是枚举创建实例" ); } }
调用枚举类
package com.test;public class Main { public static void main (String[] args) { SingletonEnum.INSTANCE.printSomething(); } }
尝试反射调用
package com.test;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public class Test { public static void main (String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor con = SingletonEnum.class.getDeclaredConstructor(); con.setAccessible(true ); SingletonEnum singleton1 = (SingletonEnum)con.newInstance(); SingletonEnum singleton2 = (SingletonEnum)con.newInstance(); System.out.println(singleton1.equals(singleton2)); } }
枚举实现的优点:
防止反射攻击,但是其他实现中,通过setAccessible()方法可以将私有构造器函数的访问级别设置为public,然后调用构造函数实例化对象newInstance()。但是在反射中通过newInstance()创建对象时,会检查该类是否被Enum修饰,如果是则抛出异常,反射失败。
不会因为序列化而产生新实例。其他实现方式需要使用transient关键字修饰。
执行命令调用链 通过Runtime类执行命令
package com.test;import java.io.IOException;public class Test { public static void main (String[] args) throws IOException { Runtime.getRuntime().exec("calc" ); } }
这是我们一段Runtime类
public class Runtime { private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime () { return currentRuntime; } private Runtime () {}
我们看下如何调用exec的,可以看到构造方法是private,通过public的getRuntime来调用获取到Runtime类调用exec方法
public Process exec (String command, String[] envp, File dir) throws IOException { if (command.isEmpty()) throw new IllegalArgumentException("Empty command" ); StringTokenizer st = new StringTokenizer(command); String[] cmdarray = new String[st.countTokens()]; for (int i = 0 ; st.hasMoreTokens(); i++) cmdarray[i] = st.nextToken(); return exec(cmdarray, envp, dir); }
exec方法
public Process exec (String command) -----在单独的进程中执行指定的字符串命令。public Process exec (String [] cmdArray) ---在单独的进程中执行指定命令和变量public Process exec (String command, String [] envp) ----在指定环境的独立进程中执行指定命令和变量public Process exec (String [] cmdArray, String [] envp) ----在指定环境的独立进程中执行指定的命令和变量public Process exec (String command,String[] envp,File dir) ----在有指定环境和工作目录的独立进程中执行指定的字符串命令public Process exec (String[] cmdarray,String[] envp,File dir) ----在指定环境和工作目录的独立进程中执行指定的命令和变量
我们具体看下调用链,下一个断点
走入到另外一个exec方法
返回了一个ProcessBuilder
对象,调用了它的start
方法,继续跟进:
产生了一个ProcessImpl
对象,调用了它的start
方法:
ProcessImple
类的start
方法,返回了一个ProcessImpl
对象:
最终create方法执行了命令
调用链图:
exec获取返回值 通过read来获取流程和子流程的输入流进行输出
package com.test;import java.io.IOException;import java.io.InputStream;public class Test { public static void main (String[] args) throws IOException { InputStream in =Runtime.getRuntime().exec("whoami" ).getInputStream(); int out = 0 ; while ((out = in.read()) != -1 ){ System.out.println((char )out); } in.close(); } }
我们可以将数据读入缓冲区来读
package com.test;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;public class Test { public static void main (String[] args) throws IOException { InputStream inputStream =Runtime.getRuntime().exec("whoami" ).getInputStream(); byte [] bcache = new byte [1024 ]; int readByte = 0 ; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); while ((readByte = inputStream.read(bcache)) != -1 ){ outputStream.write(bcache,0 ,readByte); } System.out.println(outputStream.toString()); } }
exec
方法返回的是一个Process
对象,它有这些方法:
destroy() 杀掉子进程。 exitValue() 返回子进程的出口值。 InputStream getErrorStream () 获得子进程的错误流。 InputStream getInputStream () 获得子进程的输入流。 OutputStream getOutputStream () 获得子进程的输出流。 waitFor () 导致当前线程等待,如果必要,一直要等到由该 Process 对象表示的进程已经终止。
通过反射来执行Runtime.exec方法
package com.test;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { Class clazz = Class.forName("java.lang.Runtime" ); Method getRuntime = clazz.getMethod("getRuntime" ); Object runtime = getRuntime.invoke(Runtime.class); Method exec = clazz.getMethod("exec" , String.class); Process process =(Process)exec.invoke(runtime,"whoami" ); InputStream inputStream = process.getInputStream(); byte [] bcache = new byte [1024 ]; int readByte = 0 ; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); while ((readByte = inputStream.read(bcache)) != -1 ){ outputStream.write(bcache,0 ,readByte); } System.out.println(outputStream.toString()); } }
通过构造方法来执行命令
package com.test;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, InstantiationException { Class clazz = Class.forName("java.lang.Runtime" ); Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true ); Object runtime = constructor.newInstance(); Method execMethod = clazz.getMethod("exec" ,String.class); Process process = (Process) execMethod.invoke(runtime,"whoami" ); InputStream inputStream = process.getInputStream(); byte [] bcache = new byte [1024 ]; int readByte = 0 ; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); while ((readByte = inputStream.read(bcache)) != -1 ){ outputStream.write(bcache,0 ,readByte); } System.out.println(outputStream.toString()); } }
JDK9开始的新特性,开始对反射做出限制
绕过限制进行执行命令
因为对exec进行限制,我们就要找其他方法进行使用
我们可以使用另一个函数来解决限制问题
public Process exec (String [] cmdArray) ---在单独的进程中执行指定命令和变量
在windows中,以cmd /c
开头。
而在linux下,可以以/bin/sh -c
开头:
package com.test;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;public class Test { public static void main (String[] args) throws IOException { String[] commond = {"cmd" ,"/c" ,"whoami" }; InputStream inputStream = Runtime.getRuntime().exec(commond).getInputStream(); byte [] bcache = new byte [1024 ]; int readByte = 0 ; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); while ((readByte = inputStream.read(bcache)) != -1 ){ outputStream.write(bcache,0 ,readByte); } System.out.println(outputStream.toString()); } }
ProcessBuilder 我们前面有讲到底层ProcessBuilder调用了start方法造成了命令执行,所以我们可以尝试用ProcessBuilder来代替Runtime
我们可以看到下面两个构造方法
public final class ProcessBuilder { private List<String> command; private File directory; private Map<String,String> environment; private boolean redirectErrorStream; private Redirect[] redirects; public ProcessBuilder (List<String> command) { if (command == null ) throw new NullPointerException(); this .command = command; } public ProcessBuilder (String... command) { this .command = new ArrayList<>(command.length); for (String arg : command) this .command.add(arg); } }
这是start方法,省略一部分
public Process start () throws IOException { ...... return ProcessImpl.start(cmdarray, environment, dir, redirects, redirectErrorStream); }
尝试使用ProcessBuilder进行执行
构造方法都是public这也省了不少事
package com.test;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;public class Test { public static void main (String[] args) throws IOException { InputStream inputStream = new ProcessBuilder("whoami" ).start().getInputStream(); byte [] bcache = new byte [1024 ]; int readByte = 0 ; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); while ((readByte = inputStream.read(bcache)) != -1 ){ outputStream.write(bcache,0 ,readByte); } System.out.println(outputStream.toString()); } }
通过反射来调用
package com.test;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, IOException { Class clazz = Class.forName("java.lang.ProcessBuilder" ); Constructor constructor = clazz.getConstructor(String[].class); Method start = clazz.getMethod("start" ); Object processBuilder = constructor.newInstance(new String[][]{{"whoami" }}); InputStream inputStream = ((Process) start.invoke(processBuilder)).getInputStream(); byte [] bcache = new byte [1024 ]; int readByte = 0 ; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); while ((readByte = inputStream.read(bcache)) != -1 ){ outputStream.write(bcache,0 ,readByte); } System.out.println(outputStream.toString()); } }
ProcessImpl 我们可以看到ProcessBuilder的start方法最终调用的是ProcessImpl的start方法,所以我们还可以用ProcessImpl方法来构造命令执行
此类并没有构造方法,我们只能通过反射其他方法来获取
final class ProcessImpl extends Process { private static final sun.misc.JavaIOFileDescriptorAccess fdAccess = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); }
这里有由于没有访问控制,默认都是default,只能在同包和当前类才能调用
这是ProcessImpl的start方法,是一个静态的default方法
static Process start (String cmdarray[], java.util.Map<String,String> environment, String dir, ProcessBuilder.Redirect[] redirects, boolean redirectErrorStream) throws IOException { ...... return new ProcessImpl(cmdarray, envblock, dir, stdHandles, redirectErrorStream); }
通过反射来执行
package com.test;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.Map;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, IOException { Class clazz = Class.forName("java.lang.ProcessImpl" ); Method method = clazz.getDeclaredMethod("start" ,String[].class, Map.class,String.class,ProcessBuilder.Redirect[].class,boolean .class); method.setAccessible(true ); InputStream inputStream = ((Process)method.invoke(null , new String[]{"whoami" }, null , null , null , false )).getInputStream(); byte [] bcache = new byte [1024 ]; int readByte = 0 ; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); while ((readByte = inputStream.read(bcache)) != -1 ){ outputStream.write(bcache,0 ,readByte); } System.out.println(outputStream.toString()); } }
参考:https://blog.csdn.net/rfrder/article/details/119541396
参考:https://www.jb51.net/article/217376.htm
参考:https://blog.csdn.net/bjweimengshu/article/details/78716839