34

Java 反射 (待补充)

 3 years ago
source link: https://www.wjlshare.xyz/2020/10/25/java-反射-待补充/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0x00 前言

​ 最近打算学习Java代码审计,所以需要对Java语言中的一些特性有所熟悉,在各类的框架中Java反射经常被使用,在jsp一句话里面我们就可以利用反射获取Runtime中的exec方法来进行恶意代码执行,所以来学习一波

0x01 为什么会有反射?

在说之前我们先来了解一下JVM 和 java类加载的过程。

java之所以可以跨平台运行主要是因为java的JVM,JVM相当于是java的虚拟机,这个虚拟机用来运行我们的java代码。

类的加载

众所周知我们在运行java文件的时候都会经过一次编译,将我们的 .java 文件转换成我们的 .class 文件,然后通过我们的类加载器将 .class 的二进制数据加载到我们的内存中,这里我们主要来看方法区和堆区。

JRv2u2u.png!mobile

当 .class 文件被读入到内存中的时候,会将其放在运行时数据区中的方法区,同时在我们的堆区创建一个 java.lang.Class 对象,用来封装我们方法区中的数据结构同时向我们java程序员提供了访问方法区中数据结构中的接口。

所以当我们在运行代码的时候,其实就是将我们的代码给了JVM虚拟机,然后JVM虚拟机来负责运行,运行结束之后就结束了,程序也就停止了。

那么现在我们来回归正题,为什么会出现反射。

首先我们需要知晓 反射是为了增加java语言的动态性,如果正常的加载我们都需要通过new来对类进行实例化,然后才可以使用对象中的方法。

这是我们不使用反射时调用对象中方法的代码

page pg = new page("test");
 pg.getName();

这么看起来是不是也蛮方便的?但是在某些情况下却不是这样的。

因为我们上面这样的加载方式有一个前提,那么就是必须要将我们的类进行实例化,举个例子:程序员A在写代码的时候需要用到程序员B的类,但是这时候程序员B的类还没有写好,那程序员A岂不是只能傻傻等着了吗?

所以这时候我们就需要引入我们的java反射,之前也说过Java反射实际上就是动态的加载我们的类。

反射就是在运行的时候才知道要加载哪些类,然后可以在运行的过程中获取类的完整构造并且调用对应类中方法

0x02 反射

​ 接下来我们来看一下反射的例子

package com;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Page {

    private String name;

    public Page(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
}

class reftest{
    public static void main(String[] args) throws Exception{
        Class clas = Class.forName("com.Page");
        Constructor constructor = clas.getDeclaredConstructor(String.class);
        // 利用构造器进行一个初始化
        constructor.setAccessible(true);
        Object object =  constructor.newInstance("Test");
        Method getname = clas.getMethod("getName");
        System.out.println(getname.invoke(object));
    }
}

上面是一个例子,我们通过反射来调用Page类中的setName方法

反射方法使用主要的步骤如下:

  1. 通过路径获取类的对象实例

Class clas = Class.forName("com.Page");

  1. 根据Class对象获取对应的Constructor对象也就是我们的Constructor构造器

Constructor constructor = clas.getDeclaredConstructor(String.class);

  1. 使用Constructor 的 newInstance 来获取被实例化的类

Object object = constructor.newInstance("Test");

  1. 利用Method来获取类的方法进行方法的调用

Method getname = clas.getMethod("getName");

getname.invoke(object)

接下来我们来一块块的说明一下

获取类对象加载要被实例化的对象

通常有如下三种方法:

Class.forName("要被实例化对象的路径")
.class
.getClass()

我们通常是使用第一种方法

获取类的构造器对象

方法 说明 getConstructor(Class…parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
| getConstructors() | 获得该类的所有公有构造方法 |
| getDeclaredConstructor(Class…parameterTypes) 获得该类中与参数类型匹配的构造方法(主要私有) getDeclaredConstructors() 获得该类所有构造方法

Constructor constructor = clas.getDeclaredConstructor(String.class);

通过构造器获取被实例化的对象

constructor.newInstance("Test");

通过上面几步我们就可以获取到对应类的对象了,接下来我们可以利用反射来对对象中的函数或者属性进行一个访问

利用反射获取类的方法

方法 说明 getMethod(String name, Class…parameterTypes) | 获得该类某个公有的方法 |
| getMethods() | 获得该类所有公有的方法 |
| getDeclaredMethod(String name, Class…parameterTypes) 获得该类某个方法(主要私有) getDeclaredMethods() 获得该类所有方法

调用方法:

Method method = clas.getMethod("getName");

method.invoke(object)

调用有参数的函数

Method method = clas.getMethod("setName",String.class);

method.invoke(object,"test")

利用反射获取类的属性

方法 说明 getField(String name) 获得某个公有的属性对象 getFields() 获得所有公有的属性对象 getDeclaredField(String name) 获得某个属性对 getDeclaredFields() 获得所有属性对象

获取属性

Field field = clas.getDeclaredField("name");
// 如果目标属性是private 我们需要将setAccessible设为true 否则无法正常访问
field.setAccessible(true);
System.out.println((String) field.get(object));

修改属性

Field field = clas.getDeclaredField("name");
field.setAccessible(true);
// 将name属性值修改为change
field.set(object,"change");
System.out.println((String) field.get(object));

0x03 利用反射执行命令

由于Runtime类中含有exec方法所以我们可以利用反射来调用Runtime中的exec来进行命令执行

public class Loder {
    public static void main(String[] args) throws Exception {
        // 通过名字来获取对应的对象
        Class clas = Class.forName("java.lang.Runtime");
        // 我们这里利用构造来进行实例的初始化,首先来获取我们的构造函数
        Constructor constructor = clas.getDeclaredConstructor();
        // 由于Runtime的类的构造函数是私有的所以我们要设置状态来获取我们的私有状态的构造函数
        constructor.setAccessible(true);
        // 将我们的对象转化成我们的实例
        Object object = constructor.newInstance();
        // 获取我们类中的所有方法
        Method method = clas.getMethod("exec", String.class);
        // 来调用我们获取到的方法
        method.invoke(object,"open -a Calculator");
    }
}

由于为这里使用的是mac所以 打开计算机的命令为open -a Calculator,win操作系统只需修改为calc即可

成功弹出计算机

QFn6J3i.png!mobile

0x04 思考

​ 在学习反射的过程中,想到了一个问题,既然对象的private是为了防止外部查看或篡改类的属性,但是反射的存在岂不是导致private就没有什么作用了吗?我们只需要通过setAttribute(true)就可以对参数进行修改查看

直到看到了如下的解答

Q:private修饰的方法可以通过反射访问,那么private的意义是什么?

A:Java的private修饰符不是为了绝对安全设计的,而是对用户常规使用Java的一种约束。就好比饭店厨房门口挂着“闲人免进”的牌子,但是你还是能够通过其他方法进去。

0x05 参考链接

https://m0nit0r.top/2020/06/07/java-deserialize-learn2/

https://blog.csdn.net/qq_40406704/article/details/98060936

https://juejin.im/post/6844903862575300622#heading-3

https://blog.csdn.net/u010164936/article/details/87922082


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK