java安全学习-第一天(反射)

0x01、前言

文章开头,我先引用p牛文章里的一句话

反射是⼤多数语言里都必不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。

对于我这种先入手php,再学习java的,会感到一脸茫然

为什么?java web执行rce的条件为什么那么怪啊?

先获取runtime类,再获取exec方法,最后传个参数才能执行。中间还有一大堆奇奇怪怪的东西

以下是一个标准案例

  1. import java.lang.reflect.Method;
  2. public class reflection{
  3. public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException{
  4. Class name = Class.forName("java.lang.Runtime");
  5. System.out.println(name); //class java.lang.Runtime
  6. Method method = name.getMethod("exec",String.class);
  7. System.out.println(method); //public java.lang.Process java.lang.Runtime.exec(java.lang.String) throws java.io.IOException
  8. }
  9. }

反正我第一次看是一头雾水

相比之下,php就显得灵活的多。能直接调用system(“whoami”)

当然,既然下定决心学java了,就得适应这种操蛋的语法

话不多说,我们开始吧

0x02、初探

如何获取一个类?

很简单,有一下三种方式

1、Class.forName()

用法

  1. Class name = Class.forName(className)
  2. // 等于
  3. Class name = Class.forName(className, true, currentLoader)

默认情况下, forName 的第一个参数是类名;第二个参数表示是否初始化;第三个参数就是ClassLoader 。

Java默认的 ClassLoader 就是根据类名来加载类, 这个类名是类完整路径,如 java.lang.Runtime 。

第二个true的意思是,是否要载入类的同时,将类初始化。默认都是要初始化的

什么是java类的初始化?

我来举个简单的例子

假设我写了一个名为rat的类

  1. public class rat {
  2. static{
  3. System.out.println("static优先");
  4. }
  5. {
  6. System.out.println("{}其次");
  7. }
  8. public rat(){
  9. System.out.println("rat最后");
  10. }
  11. }

那么当它被初始化时,执行优先级顺序是static>{}>自定义方法

关于类中私有方法的执行

首先,我们要了解java中的一个知识点

class.newInstance()

调用一个类中的构造函数(说白了就是初始化一个类)

如何调用一个方法?

getMethod(“exec”, String.class) 来获取 Runtime.exec 方法

但很可惜,这里的exec是私有方法,无法被直接调用

我们只能通过 Runtime.getRuntime() 来获取到 Runtime 对象,再用Runtime对象执行exec

而invoke呢?(来自p牛的文章)

invoke 的作用是执行一个方法,它的第一个参数是:

如果这个方法是一个普通方法,那么第一个参数是类对象

如果这个方法是一个静态方法,那么第一个参数是类

执行exec的exp

  1. Class clazz = Class.forName("java.lang.Runtime"); //获取runtime类,但无法使用构造函数
  2. Method execMethod = clazz.getMethod("exec", String.class); //先获取exec方法
  3. Method getRuntimeMethod = clazz.getMethod("getRuntime"); //获取getruntime方法
  4. Object runtime = getRuntimeMethod.invoke(clazz); //执行getruntime方法(重新获取runtime类,并使用其构造函数)
  5. execMethod.invoke(runtime, "calc.exe"); //执行exec方法

或者方便点,你也可以那么写

  1. public class rat{
  2. public static void main(String[] args){
  3. try{
  4. Runtime.getRuntime().exec("open -a Calculator");
  5. }
  6. catch(Exception e){
  7. e.printStackTrace();
  8. }
  9. }
  10. }

1.png

你是不是还是有些疑惑,为什么运用了getRuntime就能执行私有模式?

首先,Runtime类的构造函数是私有类型,你用Class.forName()调用后,也并没有真正拿到权限

这里设计到了一个知识点,叫做单例模式

例如数据库的类,你只有在第一次初始化时会直接调用数据库连接的方法(私有),而不是每次都要重复去连接

  1. public class MYSQLDB{
  2. private static MYSQL DB = new MYSQLDB()
  3. public static MYSQL getMYSQLDB(){
  4. return DB;
  5. }
  6. private MYSQLDB(){
  7. //...
  8. }
  9. }

假设你想第二次使用MYSQLDB这个类中的私有方法,那你是不是只能通过getMYSQLDB方法?

同样getRuntime的作用也是如此,Runtime类的构造函数是私有的,你得用getRuntime重新获取它,才能真正拿到权限

特意去翻了一下Runtime类的源码,果真如此

  1. public class Runtime {
  2. private static Runtime currentRuntime = new Runtime();
  3. /**
  4. * Returns the runtime object associated with the current Java application.
  5. * Most of the methods of class <code>Runtime</code> are instance
  6. * methods and must be invoked with respect to the current runtime object.
  7. *
  8. * @return the <code>Runtime</code> object associated with the current
  9. * Java application.
  10. */
  11. public static Runtime getRuntime() {
  12. return currentRuntime;
  13. }
  14. /** Don't let anyone else instantiate this class */
  15. private Runtime() {}
  16. ...
  17. }

其他的小疑惑

那以上类在初始化的执行顺序呢?明明看着没有构造函数啊!

给你看一个小案例,这里我们准备两个文件,一个当作单例,一个是引用单例

  1. public class test_dan{
  2. public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException{
  3. Class name = Class.forName("danli");
  4. System.out.println(name);
  5. }
  6. }
  1. public class danli{
  2. private static danli DB = new danli();
  3. public static danli getdanli(){
  4. System.out.println("public执行");
  5. return DB;
  6. }
  7. private danli(){
  8. System.out.println("private执行");
  9. //...
  10. }
  11. }

我们执行第一个文件,看看是什么结果

2.png

明明是构造函数是私有,但为什么会执行呢?

原因是第一行的时候,程序默认把danli这个类初始化了一次,导致了danli()方法的执行,并没有真正拿到这个类

第一次我们外部加载进入时,因为初始构造函数是private,无法执行,所以程序默认从上到下执行。

而执行第一行是默认初始化danli类并赋值给DB,所以会调用private danli()

现在我们再把文件内容换一下

  1. public class danli{
  2. private static danli DB = new danli();
  3. public static danli getdanli(){
  4. return DB;
  5. }
  6. private danli(){
  7. System.out.println("private执行");
  8. //...
  9. }
  10. public danli fuck(){
  11. System.out.println("入侵成功");
  12. return null;
  13. }
  14. }
  1. import java.lang.reflect.Method;
  2. public class test_dan{
  3. public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException{
  4. try{
  5. Class clazz = Class.forName("danli");
  6. Method fuckMethod = clazz.getMethod("fuck");
  7. Method getdanliMethod = clazz.getMethod("getdanli");
  8. Object danli = getdanliMethod.invoke(clazz);
  9. fuckMethod.invoke(clazz);
  10. }
  11. catch(Exception e){
  12. e.printStackTrace();
  13. }
  14. }
  15. }

3.png

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

4.png

0x03、小结

Class.forName加载类

getMethod获取方法

invoke执行方法

单例模式下,无法获取类的真正权限,只是单纯的把构造函数执行了一遍;得通过getxxx方法重新获取类,以执行其它方法

说实话,就这么点简单的基础,学的也是磕磕绊绊的,中途还向pic前辈问过n+弱智问题,翻阅了p牛文档、源码以及无数blog才勉强弄懂了一点

用tiger的话说就是”战战兢兢,如履薄冰”