作为一个没有进行过JAVA开发的菜鸡,在审计代码的过程中经常会遇到JAVA的一些特性,了解这些特性是必要的.我采用一边学习一边记录的方式,在学习JAVA安全过程中遇到的各种JAVA特性,概念在这里进行统一的学习记录,以方便查看.
这里我把他们分为两个类型,需要详细深入分析(以下简称详细)**的和了解用法原理(以下简称了解)的,**了解指能让我更好的理解读懂代码,详细指对漏洞触发,漏洞原理有直接影响的的核心特性.
目录:
[TOC]
泛型(了解)
泛型,顾名思义广泛的数据类型,如何让一个变量成为任意的数据类型呢?答:让他成为泛型
例子:在JAVA中List就是一个泛型,我们可以往里面存各种数据,string,int,float,object都可以
可能出现的疑问(会在最后解答):为什么List是一个泛型?
1 | List demo = new ArrayList(); |
demo这个list中存放了两个数据,Integer,String并且我也可以将他们输出出来
现在我们假设这样一个上下文环境,要求这个demo必须是Integer类型的数据,但是通过我们口头约定肯定是不靠谱的,因为demo是一个泛型,能放入任何类型的数据,假如你手瓢了,写了个demo.add(‘haha’),那么就会出现意想不到的问题
为了防止这种问题的出现,我们需要定义这个demo的类型,如下
1 | List demo<Integer> = new ArrayList(); |
这个时候就只能将Integer类型的数据放入demo了,值得注意的是泛型并不会在运行的时候去解析他,因为这个<Integer>只是一个标识符,在编译过程中编译器便会对其进行检查
指定泛型后idea也给我们标红
所以泛型就是一个可以放入任何数据类型的对象,而我们可以用<>标识去定义它的数据类型
同样的泛型类,泛型接口原理都是一样的.下面就是一个泛型类,在类后面加上<T>**(这个T的意思只是一个占位符而已,他就是类似函数中的形参)**告诉这是一个泛型类,我们可以把T理解为函数中的形参,我们在实例化这个类的时候需要将数据类型当作实参传入,用来指定key的数据类型
1 | class Generic<T> { //在类名后增加<T>使其成为泛型类,同样的可以使用<T,E,B,...>定义多个泛型形参 |
泛型接口同理不再赘述
问题解答:到这里你其实应该知道刚刚产生的疑问了,因为List就是一个泛型接口
JAVA类加载机制(详细)
JAVA类加载机制非常多的详解文章,个人认为冗余较多,类加载机制又是极其重要的,所以我尝试精简但又不失详细的分析记录.
类加载
运行java程序时我们写的XXX.java文件被javac编译成XXX.class字节码文件,字节码文件是给JVM虚拟机执行的.但是我们不可能在运行初期就把所有的字节码文件全部读入JVM(因为一个java程序会有很多的.java文件),那样无疑会带来巨量的消耗.JVM通过类加载机制,在需要用到某个类的时候在去加载他,以提高程序执行效率
类加载机制
一张很经典的图说明了加载一个类所经历的过程
.java经过javac编译为字节码文件(.class),如果这个时候这个class需要被使用,那么我们就触发加载流程开始加载这个类
加载:其实就是读取.class文件到JVM中,然后将其转为静态数据结构存放在方法区中,然后在堆中生成一个java.lang.class类型的对象.静态数据结构很唬人,其实就理解为将class文件的元数据(类名称,方法名,….各种数据存起来而已)
**验证:**这个时期会验证class文件格式,元数据是否合法,字节码是否合法,符号引用是否可发,反正就是一些列验证,就是写出来看一眼就忘了,没必要浪费篇幅.
**准备:**主要工作就是分配内存,初始化类变量(static),不会初始化实例变量.这一过程中并不会执行显示赋值,而是会隐式给予他们默认值(如0,null),在初始化阶段在执行显示赋值
**解析:**将符号引用转为直接引用,意思就是将符号替换为符号所代表的真实地址,不了解符号引用的同学自行百度了解
初始化:这个时候就会完成刚刚没有完成的赋值操作,如将类中的变量显示赋值.
在以下情况会触发类的加载:
(1)创建类的实例,也就是new一个对象
(2)访问某个类或接口的静态变量,或者对该静态变量赋值
(3)调用类的静态方法
(4)反射(Class.forName(“com.mengda.test”))
(5)初始化一个类的子类(会首先初始化子类的父类)
(6)JVM启动时标明的启动类,即文件名和类名相同的那个类 只有这6中情况才触发类加载.
类加载器
既然需要动态的去加载我们需要的类(.class字节码文件),那么肯定需要一个加载器来加载他(类加载器),JAVA自带的加载器有三个
1.Bootstrap CLassloder:
启动项加载器,用于加载JVM自身需要的类类库及一些核心类库,需要注意的是这个加载器是由C++实现的,不存在于JAVA代码中
2.Extention ClassLoader:
扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
3.AppClassLoader:
Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。
双亲委派,类加载器的加载顺序
加载器的加载顺序实际上是Bootstrap CLassloder->Extention ClassLoader->AppClassLoader
为什么是这个顺序呢?实际上除了Bootstrap CLassloder加载器之外的加载器都有父加载器,而这些加载器都是很懒惰的,每次需要加载一个类的时候都会让他们的爸爸去加载,如果他们的爸爸不能加载才会自己加载,如果他们的爸爸还有爸爸那就会继续给他们爸爸的爸爸进行加载,总之整个类加载顺序无论如何就是从最顶层开始往下执行的(双亲委派)
看这样一段代码
输出:sun.misc.Launcher$AppClassLoader@b4aac2
很明显这个test类是由AppClassLoader加载而来的
那么AppClassLoader的父亲是谁呢?
输出:sun.misc.Launcher$ExtClassLoader@16d3586
输出是ExtClassLoader,可见ExtClassLoader是AppClassLoader的父亲
那么ExtClassLoader的父亲是谁呢?
输出:null
why?不是说除了Bootstrap CLassloder之外的加载器都有父亲吗,那为什么ExtClassLoader的父亲是null呢?
这里就要引出此处的”父亲”并不是指的继承关系上的父亲
getParent方法直接就返回了parent,那么这个parent是哪儿来的呢
是直接赋值的ClassLoader Parent,是一个类,也就是说这里的父子关系不是继承意义上的父子关系,而是逻辑上的
为什么为null呢?是因为ExtClassLoader的父亲是Bootstrap CLassloder,而Bootstrap CLassloder是C++开发的,并不存在于java代码中,固然为null了
所以这三个加载器由上至下的关系为
由双亲委派机制可以知道他们的执行顺序是由上至下,及Bootstrap CLassloder->Extention ClassLoader->AppClassLoader
双亲委派流程分析
写了一段示例代码帮助我们分析
让我们来看看java是如何实例化一个类的
首先来到loadclass方法,首先判断需要加载的类名是否还存在层级,此处的var.lastIndexof(46),46是.的ascall码,用来判断加载的类是否是xx.xxx的形式,如果不是直接进入第二个if,第二个if主要是判断if是否存在(猜测),但是这个if也不会进入,直接会进入super.loadClass.
来到了java.lang.ClassLoader的loadClass方法,首先会调用findLoadedClass判断test是否被加载器加载过了,此时类加载器是AppClassLoader
那么test此时肯定是没有被加载过的,此时c=null,404行判断parent是否为空(双亲委派机制触发核心,寻找AppClassLoader的父亲是否存在,存在就让父亲加载),此时很明显AppClassLoader的父亲是存在的,为ExtClassLoader
405行调用ExtClassLoader后依然来到了java.lang.ClassLoader的loadClass方法,和刚刚是同一个方法,因为AppClassLoader和ExtClassLoader都是继承自
URLClassLoader的,注意,这里说的继承关系是真正的类继承关系,URLClassLoader是AppClassLoader和ExtClassLoader的父类,但是AppClassLoader和ExtClassLoader还存在一层逻辑父子关系,在逻辑上Ext是App的父亲.此时this已经变为ExtClassLoader
但是ExtClassLoader此处的父亲为null,无法找到父亲了,直接调用407处的findBootStrapClassOrNull,传递到最终的类加载器Bootstrap CLassloder进行加载
又因为test类是属于AppClassLoader范畴的,在经过BootStrap和ExtClassLoader后发现都不是他们能加载的(ExtClassLoader的findclass方法无法找到对应类),于是就又回到了AppClassLoader,调用AppClassLoader的findClass方法寻找对应类,然后AppClassLoader来加载test类
这就是双亲委派的机制
反射(详细)
为什么会产生反射这个需求?
假设这样一个场景,程序执行过程中需要实例化所有具有toString方法的类,但是这个时候我们并不知道那些类有这个方法,那我们应该怎么办呢?new一个A对象然后去尝试调用?这显然是不太可取而且很麻烦的,这个时候就需要一个第三者监管机制,他好比上帝之眼,能在整个java运行过程中独立出来,置于程序外部去帮我们读取类的各种信息,帮助我们执行程序,这样的机制叫做反射
我画了个图,整个反射调用原理大概是这个样子的
结合上面的图我们来看下面这个例子:
fanshe.java
先用Class.forName(“demo”)加载,获得了demo的class对象democlass,然后通过这个democlass的getMethod获取到了demo类中的method方法,然后调用无参构造函数实例化obj(getConstructor()获取无参构造函数),然后使用invoke反射调用方法.
1 | import java.lang.reflect.InvocationTargetException; |
demo.java
1 | public class demo { |
输出:
1 | 无参构造方法调用 |