前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Java安全--Java反射机制(1)

Java安全--Java反射机制(1)

作者头像
OneTS安全团队
发布2025-02-07 16:29:43
发布2025-02-07 16:29:43
8000
代码可运行
举报
文章被收录于专栏:OneTS安全团队OneTS安全团队
运行总次数:0
代码可运行

声明

本文属于OneTS安全团队成员mes9s0的原创文章,转载请声明出处!本文章仅用于学习交流使用,因利用此文信息而造成的任何直接或间接的后果及损失,均由使用者本人负责,OneTS安全团队及文章作者不为此承担任何责任。

对于Java反序列化漏洞来说,Java反射机制必须理解,本文专门用来说明究竟什么才是Java反射。

什么是Java反射

Java 反射机制允许运行中的Java程序获取自身的信息, 操作类和对象的内部属性。

Java 反射机制是指在程序运行时,对于任何一个类,都能知道这个类的所有属性和方法对于任何一个实例对象 , 都能调用该对象的任何一个属性和方法

Java中这种 " 动态获取信息 "" 动态调用属性方法 " 的机制被称为 Java 反射机制。

实例对象可以通过反射机制获取它的类 , 类可以通过反射机制获取它的所有方法和属性 . 获取的属性可以设值 , 获取的方法可以调用。

在静态语言中 , 一般对象的类型都是在编译期就确定下来的 . 而通过 Java 反射机制 , 可以动态的创建对象并调用其方法和属性 。

Java反射的功能

正是因为 PHP 中存在多种动态特性 , 使得开发人员能通过很少的代码来实现非常多的功能。

比较典型的例子就是一句话木马 , 通过一行 <?php @eval($_POST[cmd]);能实现目录管理 , 命令执行 , 数据库连接 , 文件上传下载等多种多样的功能 。

但是 Java 本身是一门静态语言 , 无法像 PHP 那么灵活多变 。但是通过 Java 反射机制 , 可以为自身提供一些动态特性。

当我们在通过 IDE 写代码时 , 敲击点号" . " , 会出现当前对象或类所包含的属性和方法 。这里用到的就是 Java 反射机制。

而且 , 反射最重要的用途是开发各种通用框架 . 很多框架都是通过XML文件来进行配置的( 例如 struts.xml , spring-*.xml 等 ) , 即所谓的框架核心配置文件 。

为了确保框架的通用性 , 程序运行时需要根据配置文件中对应的内容加载不同的类或对象 , 调用不同的方法 , 这也依赖于 Java 反射机制 。

综上所述 , Java 反射机制的功能可分为如下几点 :

1、在程序运行时查找一个对象所属的类

2、在程序运行时查找任意一个类的成员变量和方法

3、在程序运行时构造任意一个类的对象

4、在程序运行时调用任意一个对象的方法

查找一个对象所属的类

如何获取一个类( java.lang.Class )呢?

总的而言有三种方法:

▪obj.getClass()

▪Class.forName(className)

▪className.class

具体的使用方法如下所示:

代码语言:javascript
代码运行次数:0
复制
//查找对象所属的类

public class getClass {
    public getClass(String name){
        System.out.println(name);
    }

    public static void main(String[] args) throws ClassNotFoundException {

        getClass gc1 = new getClass("mes9s0");
        //已知上下文中存在某个类的实例对象名称
        //可以调用obj.getClass()获取实例对象所属的类
        System.out.println("通过obj.getClass()获得所属的类:" + gc1.getClass());

        //已知某个类的名称
        //可以调用Class.getClass("className")来获取类
        System.out.println("通过Class.forName('className')获取类:" + Class.forName("getClass"));

        //当已经加载了某个类
        //可以通过className.class属性来获取类
        System.out.println("通过className.class属性获取类:"+getClass.class);
    }
}

针对不同的情况 , 可以用不同的方法来获取类 。

需要注意 : forName( ) 函数有两个重载 , 如下所示 :

Class.forName( String className )

Class.forName( String className , Boolean initialize , ClassLoader loader )

1、String className : 类名

2、Boolean initialize : 是否进行类初始化

3、ClassLoader loader : 加载器( 告诉 Java 虚拟机如何加载获取的类 , Java 默认根据类名( 即类的绝对路径 , 例如 java.lang.Runtime( ) )来加载类,Runtime类在Java安全从零到一(3)中讲过 )

其中 , 第一种方法是对第二种方法的封装 , 存在以下对应关系 :

Class.forName( String className ) == Class.forName( String className , True , currentLoader )

那么这个类初始化是指什么呢 ?

代码语言:javascript
代码运行次数:0
复制
//类初始化

public class test1 {
    {
        System.out.println("构造代码块");
    }

    static {
        System.out.println("静态代码块");
    }

    public test1() {
        System.out.println("构造函数");
    }

    public void test1test() {
        System.out.println("普通代码");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> cls = Class.forName("test1");//类初始化,初始化时会加载静态代码块
        System.out.printf(String.valueOf(cls));
    }
}

这个部分在我的Java安全从零到一(2)中讲过,可以详细翻看。

结果表明 , 在 Java 类初始化时 , 会执行静态代码块中的内容 。

那也就是说 , 如果我们能控制一个类 , 那么就可以通过在类中添加包含恶意代码的静态代码块。当类初始化时 , 默认会自动执行恶意代码. 如下所示 :

1、假设存在如下代码 :

代码语言:javascript
代码运行次数:0
复制
public class vul {
    public vul(String string) throws ClassNotFoundException {
        Class.forName(string);
    }

    public static void main(String[] args) throws ClassNotFoundException {
        vul v = new vul("test2");
        //实例化 vul 类,调用构造方法 vul, vul()方法中通过 Class.forName(className) 方法来获取类,获取类时默认进行类初始化,调用静态代码块 static{}
    }
}

此时 , 如果我们能控制 test2类 , 那就能执行任意代码 .

2、构造恶意的 test2 类:

代码语言:javascript
代码运行次数:0
复制
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;


public class test2 {
    static {
        try {
            //执行系统命令
            Process p = java.lang.Runtime.getRuntime().exec("id");
            //获取p的标准输入流作为输入字节流
            InputStream is = p.getInputStream();
            //字节流转化为字符流
            InputStreamReader isr = new InputStreamReader(is);
            //将字符流存入缓冲区
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            //逐一读取字符流中缓冲区的每一行
            while ((line = br.readLine()) != null) {
                System.out.printf(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这个恶意类的内容是在该类静态代码块中 , 通过 java.lang.Runtime.getRuntime( ).exec( ) 执行系统命令 , 并将返回字节流转换为字符流 , 存入缓冲区后逐行读取并输出 。

3、当调用 vul 类时 , 会自动执行恶意代码 。

查找任意一个类的成员变量和方法

如何获取某一个类的所有方法呢?

总的来说有三种方法 :

▪className.getMethod(functionName , [parameterTypes.class])

▪className.getMethods()

▪className.getDeclaredMethods()

代码语言:javascript
代码运行次数:0
复制
//查找一个类的方法

import java.lang.reflect.Method;

public class getMethod {
    class methodClass {
        public int add(int a, int b) {
            return a + b;
        }
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Class<?> cls = methodClass.class;//通过className.class获取类

        //获取类方法的三种方式
        Method method = cls.getMethod("add", int.class, int.class);
        Method[] methods = cls.getMethods();//获取某个类的public方法
        Method[] declareMethods = cls.getDeclaredMethods();//获取某个类的公共,保护,默认方法,不包括继承

        //输出结果
        //className.getMethod
        System.out.println("getMethod获取方法" + method);

        System.out.println("\ngetMethods获取的方法:");
        for (Method m : methods) {
            System.out.println(m);
        }

        System.out.println("\ngetDeclaredMethods获取的方法");
        for (Method m : declareMethods) {
            System.out.println(m);
        }
    }
}

getMethod( ) : 返回类中一个特定的方法。其中第一个参数为方法名称 , 后面的参数为方法的参数对应 Class 的对象。

getMethods( ) : 返回某个类的所有公用(public)方法 , 包括其继承类的公用方法。

getDeclaredMethods( ) : 返回某个类或接口声明的所有方法 , 包括公共、保护、默认(包)访问和私有方法 , 但不包括其继承类的方法。

补充:$是内部类的意思

构造任意一个类的对象

上文提到了可以通过三种方式来获取类 , 那么如果获取一个实例对象呢 ?

通过 className.newInstance() 构建一个实例对象。

我们都知道在类实例化时会调用构造函数 , 而构造函数又分为 " 有参构造函数 " 和 " 无参构造函数 " 。

然而 className.newInstance() 没有参数 , 只能调用无参构造函数(注意,该方法已经被弃用,新的在代码中) . 如果我们想要调用有参构造函数 , 就必须依赖于 Class 类的 getConstructor() 方法 。

通过 Class 类的 getConstructor() 方法 , 可以获取 Constructor 类的一个实例 , Constructor 类也存在一个 newInstance() 方法 , 不过该方法可以携带参数 . 用该方法来创建实例对象可以调用有参构造函数 。

代码语言:javascript
代码运行次数:0
复制
//构造任意一个类的对象

import java.lang.reflect.InvocationTargetException;

public class newInstance {

    //无参构造函数
    public newInstance(){
        System.out.println("这是无参构造函数");
    }

    //有参构造函数
    public newInstance(String str){
        System.out.println(str);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> cls = Class.forName("newInstance");

        System.out.println("通过className.newInstance()创建实例对象,默认调用无参构造函数,但是该方法已被弃用");
        newInstance obj1 = (newInstance)cls.newInstance();

        System.out.println("通过className.getConstrutor().newInstance()创建实例对象,可以添加参数调用有参构造函数");
        newInstance obj2 = (newInstance)cls.getConstructor(String.class).newInstance("这里有参构造函数");

        System.out.println("此处为新的无参构造函数调用");
        newInstance obj3 = (newInstance)cls.getDeclaredConstructor().newInstance();
    }
}

▪className.newInstance()

▪className.getConstructor( parameterType ).newInstance( parameterName )

▪className.getDeclaredConstructor().newInstance()

因此 , 我们可以通过 newInstance() 方法来构造任何一个类的对象。并且可以选择是调用其无参构造方法 , 还是有参的构造方法 。

调用任意一个实例对象的方法

有了实例对象 , 如何调用调用该对象的方法呢 ?

一般来说 , 可以通过 objectName.functionName() 这种格式来调用实例方法,举个例子 。

代码语言:javascript
代码运行次数:0
复制
/调用任意一个实例对象的方法

import java.lang.reflect.InvocationTargetException;

public class invoke {
    public String prt(String name) {
        return name;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        String name = "mes9s0";
        Class<?> cls = Class.forName("invoke");
        invoke ivk = (invoke) cls.getDeclaredConstructor().newInstance();
        //通过objectName.functionName()来调用实例方法
        String str1 = ivk.prt(name);
        System.out.println(str1);
    }
}

但在很多情况下 , 并不知道类名, 也就无法 new 出实例对象 , 更别提调用实例对象的方法了 。

当遇到这种情况时 , 就需要使用 Java 反射来调用实例对象的方法了 。

以下就是思路了:

➡不知道类怎么办 ?

我们可以通过 obj.getClass() , Class.forName(className) , className.class 来获取类.

➡不知道类有哪些方法怎么办 ?

可以通过 className.getMethod(functionName , [parameterTypes.class]) , className.getMethods() , className.getDeclaredMethods() 来获取类的方法.

➡不能 new 出实例对象怎么办 ?

我们可以通过 className.newInstance() , className.getConstructor().newInstance() 来构造实例对象 .

➡那如何调用实例对象的方法呢 ?

通过 invoke() 方法来调用任何一个实例对象的方法 !

看看定义,它是Method对象调用的:

我们把上面的代码改成invoke获得的

代码语言:javascript
代码运行次数:0
复制
/调用任意一个实例对象的方法

import java.lang.reflect.InvocationTargetException;

public class invoke {
    public String prt(String name) {
        return name;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        String name = "mes9s0";
        Class<?> cls = Class.forName("invoke");
        invoke ivk = (invoke) cls.getDeclaredConstructor().newInstance();
//        String str1 = ivk.prt(name);
//        System.out.println(str1);
        Object ret = cls.getMethod("prt", String.class).invoke(ivk, name);
        System.out.println(ret);
    }
}

Method.invoke(obj , args[])

如上文所说的 , 通过Java反射机制来获取类 , 获取类的方法 , 构造实力对象 , 最终调用实例方法。

注 : 官方文档中提到了一些比较有意思的东西 , 需要注意 。

如果要调用的方法是静态的 , 则忽略 obj 参数。这个点其实比较好理解 , 我们知道Java中调用静态方法是无需创建实例对象的 , 所以这里可以省略 obj 参数 。

**如果要调用的方法的形参个数为 " 0 " , 那么 args[] 数组的长度可以为 " 0 " 或者 " null " 。

这个点其实也没啥说的 , args[] 数组本就是要调用方法的参数 , 既然目标方法没有参数 , 这里自然也就不用写 .

看了这么久,记得关注哦~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 OneTS安全团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档