不过在挖洞的过程中也发现了,现在起始php相对来说越来越少了,java的相对来说用的比较多,所以最近也是在补补java安全相关的知识。
Java安全可以从反序列??化漏??洞??开始说起,反序列??化漏??洞????又可以从反射开始说起。所以这篇文章主要整理一下java反射相关的基础内容。
什么是反射?
反射可以用于判断任意对象所属的类,获得Class对象,构造任意一个对象以及调用一个对象。注:这篇文章所整理的基本反射功能和实现都在java.lang.reflect包里。
在反射里有几个非常重要的方法:
获取类的方法: forName实??化类对象的方法: newInstance获取函数的方法: getMethod执??函数的方法: invoke
通常来说我们有如下三种??方式获取??个“类”,也就是 java.lang.Class对象:
obj.getClass()如果上下文中存在某个类的实??obj,那么我们可以直接通过 obj.getClass() 来获取它的类Test.class 如果你已经加载了某个类,只是想获取到它的java.lang.Class对象,那么就直接拿它的 class 属性即可。这个??方法其实??属于反射。Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使??用forName来获取
使用Class对象的newInstance()方法来创建对象,或者通过Class对象获取指定的Constructor对象,再通过Constructor对象的newInstance来创建实例。如下一个简单的代码例子:
StringBuilderhello=newStringBuilder(“hello”);//直接通过`obj.getClass()`来获取Class<?>k=hello.getClass();System.out.println(k);//通过class获取Class<?>c=String.class;Objectstr=c.newInstance();System.out.println(str);//通过Class对象获取指定的Constructor对象,在通过Constructor对象的newInstance来创建实例Constructor<?>constructor=c.getConstructor(String.class);Objectobj=constructor.newInstance(“hello”);System.out.println(obj);
class.newInstance()的作用就是调用这个类的无参构造函数,但是在实际使用中可能会碰到一些问题,会发现使用 newInstance 总是不成功,这里一般有两种原因:
使用的类没有无参构造函数使用的类构造函数是私有的
这里通过java.lang.Runtime来进行理解,用这个类构造命令执行Payload, 先看错误的代码:
Classclazz2=Class.forName(“java.lang.Runtime”);clazz2.getMethod(“exec”,String.class).invoke(clazz2.newInstance(),”id”);
如果执行就会发现如下错误:
Exceptioninthread”main”java.lang.IllegalAccessException:ClassMaincannotaccessamemberofclassjava.lang.Runtimewithmodifiers”private”atsun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)atjava.lang.Class.newInstance(Class.java:436)atMain.main(Main.java:46)
这个错误的也很明显,告诉我们Runtime 类的构造方法是私有的。如果做过java开发的朋友可能更加清楚,这是经常会使用的单例模式,如下是一个简单的单例代码:
publicclassMyLoader{publicintage=23;privatestaticfinalMyLoaderinstance=newMyLoader();privateMyLoader(){}publicstaticMyLoadergetInstance(){returninstance;}publicintgetAge(){returnage;}}
通常就是会暴露一个 getInstance 。而Runtime类就是单例模式,我们只能通过 Runtime.getRuntime() 来获取到 Runtime 对象。
在上面的代码中我们用到了getMethod, 上面也说了,getMethod获取函数的??方法, 这里看一下这个方法说明:
public Method getMethod(String name, Class<?>… parameterTypes)
name: method的名称parameterTypes: method的参数类型的列表
这里我们要知道,java是支持类的的重载的,所以是不能仅仅通过一个函数名来确定一个函数,还需要传给它你需要获取的函数的参数类型列表即parameterTypes
上面代码中我们是想要获取Runtime的exec方法,从源码中可以看到,Runtime的exec方法有6个重载
这里我们使用最简单的那个重载,即只有一个参数类型为String,那么我使用getMethod获取Runtime.exec方法的代码为:
getMethod(“exec”, String.class)
获取到方法之后就是该执行方法,这里用到的是invoke, 这里看一下这个方法说明:
public Object invoke(Object obj, Object… args)
obj:实例化后的对象args:用于方法调用的参数
这样对于我们最开始报错的那个代码就可以更改为如下:
Stringline=””;Classclazz=Class.forName(“java.lang.Runtime”);Processprocess=(Process)clazz.getMethod(“exec”,String.class).invoke(clazz.getMethod(“getRuntime”).invoke(clazz),”id”);BufferedReaderinput=newBufferedReader(newInputStreamReader(process.getInputStream()));while((line=input.readLine())!=null){System.out.println(line);}input.close();
这段代码就可以获取我们执行命令的结果,其实核心的部分是:
Classclazz=Class.forName(“java.lang.Runtime”);clazz.getMethod(“exec”,String.class).invoke(clazz.getMethod(“getRuntime”).invoke(clazz),”id”);
看着可能有点迷糊,分开写之后看着就清楚了:
Classclazz=Class.forName(“java.lang.Runtime”);MethodexecMethod=clazz.getMethod(“exec”,String.class);MethodgetRuntimeMethod=clazz.getMethod(“getRuntime”);Objectruntime=getRuntimeMethod.invoke(clazz);execMethod.invoke(runtime,”id”);