java安全学习-第一天(反射)
0x01、前言
文章开头,我先引用p牛文章里的一句话
反射是⼤多数语言里都必不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。
对于我这种先入手php,再学习java的,会感到一脸茫然
为什么?java web执行rce的条件为什么那么怪啊?
先获取runtime类,再获取exec方法,最后传个参数才能执行。中间还有一大堆奇奇怪怪的东西
以下是一个标准案例
import java.lang.reflect.Method;public class reflection{public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException{Class name = Class.forName("java.lang.Runtime");System.out.println(name); //class java.lang.RuntimeMethod method = name.getMethod("exec",String.class);System.out.println(method); //public java.lang.Process java.lang.Runtime.exec(java.lang.String) throws java.io.IOException}}
反正我第一次看是一头雾水
相比之下,php就显得灵活的多。能直接调用system(“whoami”)
当然,既然下定决心学java了,就得适应这种操蛋的语法
话不多说,我们开始吧
0x02、初探
如何获取一个类?
很简单,有一下三种方式
1、Class.forName()
用法
Class name = Class.forName(className)// 等于Class name = Class.forName(className, true, currentLoader)
默认情况下, forName 的第一个参数是类名;第二个参数表示是否初始化;第三个参数就是ClassLoader 。
Java默认的 ClassLoader 就是根据类名来加载类, 这个类名是类完整路径,如 java.lang.Runtime 。
第二个true的意思是,是否要载入类的同时,将类初始化。默认都是要初始化的
什么是java类的初始化?
我来举个简单的例子
假设我写了一个名为rat的类
public class rat {static{System.out.println("static优先");}{System.out.println("{}其次");}public rat(){System.out.println("rat最后");}}
那么当它被初始化时,执行优先级顺序是static>{}>自定义方法
关于类中私有方法的执行
首先,我们要了解java中的一个知识点
class.newInstance()
调用一个类中的构造函数(说白了就是初始化一个类)
如何调用一个方法?
getMethod(“exec”, String.class) 来获取 Runtime.exec 方法
但很可惜,这里的exec是私有方法,无法被直接调用
我们只能通过 Runtime.getRuntime() 来获取到 Runtime 对象,再用Runtime对象执行exec
而invoke呢?(来自p牛的文章)
invoke 的作用是执行一个方法,它的第一个参数是:
如果这个方法是一个普通方法,那么第一个参数是类对象
如果这个方法是一个静态方法,那么第一个参数是类
执行exec的exp
Class clazz = Class.forName("java.lang.Runtime"); //获取runtime类,但无法使用构造函数Method execMethod = clazz.getMethod("exec", String.class); //先获取exec方法Method getRuntimeMethod = clazz.getMethod("getRuntime"); //获取getruntime方法Object runtime = getRuntimeMethod.invoke(clazz); //执行getruntime方法(重新获取runtime类,并使用其构造函数)execMethod.invoke(runtime, "calc.exe"); //执行exec方法
或者方便点,你也可以那么写
public class rat{public static void main(String[] args){try{Runtime.getRuntime().exec("open -a Calculator");}catch(Exception e){e.printStackTrace();}}}

你是不是还是有些疑惑,为什么运用了getRuntime就能执行私有模式?
首先,Runtime类的构造函数是私有类型,你用Class.forName()调用后,也并没有真正拿到权限
这里设计到了一个知识点,叫做单例模式
例如数据库的类,你只有在第一次初始化时会直接调用数据库连接的方法(私有),而不是每次都要重复去连接
public class MYSQLDB{private static MYSQL DB = new MYSQLDB()public static MYSQL getMYSQLDB(){return DB;}private MYSQLDB(){//...}}
假设你想第二次使用MYSQLDB这个类中的私有方法,那你是不是只能通过getMYSQLDB方法?
同样getRuntime的作用也是如此,Runtime类的构造函数是私有的,你得用getRuntime重新获取它,才能真正拿到权限
特意去翻了一下Runtime类的源码,果真如此
public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}...}
其他的小疑惑
那以上类在初始化的执行顺序呢?明明看着没有构造函数啊!
给你看一个小案例,这里我们准备两个文件,一个当作单例,一个是引用单例
public class test_dan{public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException{Class name = Class.forName("danli");System.out.println(name);}}
public class danli{private static danli DB = new danli();public static danli getdanli(){System.out.println("public执行");return DB;}private danli(){System.out.println("private执行");//...}}
我们执行第一个文件,看看是什么结果

明明是构造函数是私有,但为什么会执行呢?
原因是第一行的时候,程序默认把danli这个类初始化了一次,导致了danli()方法的执行,并没有真正拿到这个类
第一次我们外部加载进入时,因为初始构造函数是private,无法执行,所以程序默认从上到下执行。
而执行第一行是默认初始化danli类并赋值给DB,所以会调用private danli()
现在我们再把文件内容换一下
public class danli{private static danli DB = new danli();public static danli getdanli(){return DB;}private danli(){System.out.println("private执行");//...}public danli fuck(){System.out.println("入侵成功");return null;}}
import java.lang.reflect.Method;public class test_dan{public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException{try{Class clazz = Class.forName("danli");Method fuckMethod = clazz.getMethod("fuck");Method getdanliMethod = clazz.getMethod("getdanli");Object danli = getdanliMethod.invoke(clazz);fuckMethod.invoke(clazz);}catch(Exception e){e.printStackTrace();}}}

与runtime同理,如果直接载入类调用fuck方法,是无法调用成功的,因为你没有成功载入此类

0x03、小结
Class.forName加载类
getMethod获取方法
invoke执行方法
单例模式下,无法获取类的真正权限,只是单纯的把构造函数执行了一遍;得通过getxxx方法重新获取类,以执行其它方法
说实话,就这么点简单的基础,学的也是磕磕绊绊的,中途还向pic前辈问过n+弱智问题,翻阅了p牛文档、源码以及无数blog才勉强弄懂了一点
用tiger的话说就是”战战兢兢,如履薄冰”
