前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java代码审计 -- 反射

Java代码审计 -- 反射

原创
作者头像
Gh0st1nTheShel
修改2022-01-23 14:33:09
5040
修改2022-01-23 14:33:09
举报
文章被收录于专栏:网络空间安全网络空间安全

欢迎关注我的微信公众号《壳中之魂》,查看更多网安文章

Java反射机制

Java 反射机制可以无视类方法、变量去访问权限修饰符(如protected、private 等),并且可以调用任何类的任意方法、访问并修改成员变量值。换而言之,在能够控制反射的类名、方法名和参数的前提下,如果我们发现一处 Java 反射调用漏洞,则攻击者几乎可以为所欲为

什么是反射

反射(Reflection)是Java的特征之一。C/C++语言中不存在反射,反射的存在使运行中的 Java 程序能够获取自身的信息,并且可以操作类或对象的内部属性。那么什么是反射呢?

Oracle 官方有着相关解释:

反射使Java代码能够发现有关已加载类的字段、方法和构造函数的信息,并在安全限制内使用反射的字段、方法和构造函数对其底层对应的对象进行操作

简单来说,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。同样,Java的反射机制也是如此,在运行状态中,通过 Java 的反射机制,我们能够判断一个对象所属的类;了解任意一个类的所有属性和方法;能够调用任意一个对象的任意方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制

反射的用途

反射的用途很广泛。在开发过程中使用Eclipse、IDEA等开发工具时,当我们输入一个对象或类并想调用它的属性或方法时,编译器会自动列出它的属性或方法,这是通过反射实现的;再如,JavaBean和JSP之间的调用也是通过反射实现的。反射最重要的用途是开发各种通用框架,如上文中提到的Spring框架以及ORM框架,都是通过反射机制来实现的。

面向不同的用户,反射机制的重要程度也大不相同。对于框架开发人员来说,反射虽小但作用非常大,它是各种容器实现的核心。对于一般的开发者来说,使用反射技术的频率相对较低。但总体来说,适当了解框架的底层机制对我们的编程思想也是大有裨益的。

反射的基本运用

由于大部分Java的应用框架采用了反射机制,因此掌握Java反射机制可以提高我们的代码审计能力。

Person类

代码语言:javascript
复制
public class Test01 {

}

class Person{
    private String name;
    private int id;
    private  int age;

    public Person() {
    }

    public Person(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }
}

由于一个类在内存中只有一个class对象,一个类被加载后,整个类都会被封装在Class对象中

验证,通过初始化两个int数组,通过查看hashcode来判定断是否为同一个class

代码语言:javascript
复制
int[] a = {10};
int[] b = {20};
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());

结果

代码语言:javascript
复制
23934342
23934342

使用int a是不能.getClass()的,原因是int是属于基本类型,而不属于类,如果要用则用Integer a

所有类型的Class

代码语言:javascript
复制
package g1ts.com;

import java.lang.annotation.ElementType;

public class Test02 {
    public static void main(String[] args) {
        Class c1 = Object.class;        //类
        Class c2 = Comparable.class;    //接口
        Class c3 = String[].class;      //一位数组
        Class c4 = int[][].class;       //二维数组
        Class c5 = Override.class;      //注解
        Class c6 = ElementType.class;   //枚举
        Class c7 = Integer.class;       //基本数据类型
        Class c8 = void.class;          //void
        Class c9 = Class.class;         //Class

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);
    }
}

结果

代码语言:javascript
复制
class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
class java.lang.Class

获取Class类

使用forName()方法

如果要使用Class类中的方法获取类对象,就需要使用forName() 方法,只要有类名称即可,使用更为方便,扩展性更强

代码语言:javascript
复制
public static void main(String[] args) throws ClassNotFoundException {
    //通过反射获取类的对象
    Class c1 = Class.forName("g1ts.com.Person");
    System.out.println(c1);
}

结果:

代码语言:javascript
复制
class g1ts.com.Person 

在使用JDBC连接到数据库的时候,使用

代码语言:javascript
复制
Class.forName("com.mysql.jdbc.Driver"); 

可以知道连接的数据库类型

详情:Java class.forname 详解 | 菜鸟教程 (runoob.com)

类名.class

任何数据类型都具备静态的属性,因此可以使用.class直接获取其对应的Class对象。这种方法相对简单,但要明确用到类中的静态成员

代码语言:javascript
复制
public static void main(String[] args) throws ClassNotFoundException {
    //通过反射获取类的对象
    Class name = Person.class;
    System.out.println(name);
}

结果:

代码语言:javascript
复制
class g1ts.com.Person 

使用getClass()方法

我们可以通过 Object 类中的 getClass() 方法来获取字节码对象。不过这种方法较为烦琐,必须要明确具体的类,然后创建对象

代码语言:javascript
复制
public static void main(String[] args) throws ClassNotFoundException {
    Person person = new Person();

    //通过反射获取类的对象
    Class name = person.getClass();
    System.out.println(name);
}

结果:

代码语言:javascript
复制
class g1ts.com.Person 

.getclass使用的对象为对象,.class使用的对象为类

使用getSystemClassLoader().loadClass()方法

getSystemClassLoader().loadClass()方法与forName()方法类似,只要有类名称即可,但是与forname()方法有些区别。

forname()的静态方法JVM会装载类,并且执行static()中的代码;而getSystemClassLoader().loadClass()不会执行static()中的代码。如上文中提到的使用JDBC,就是利用forName()方法,使JVM查找并加载指定的类到内存中,此时将"com.mysql.jdbc.Driver"当作参数传入,就是告知JVM去"com.mysql.jdbc"路径下查找Driver类,并将其加载到内存中

代码语言:javascript
复制
public static void main(String[] args) throws ClassNotFoundException {
    //通过反射获取类的对象
    Class name = ClassLoader.getSystemClassLoader().loadClass("g1ts.com.Person");
    System.out.println(name);
}

结果

代码语言:javascript
复制
class g1ts.com.Person 

Type属性

基本内置的包装类都有一个Type属性

代码语言:javascript
复制
public static void main(String[] args) throws ClassNotFoundException {
    //通过反射获取类的对象
    Class name = Integer.TYPE;
    System.out.println(name);
}

结果

代码语言:javascript
复制
int 

获得父类类型

假设我们设计了一个Student类继承Person类,通过Student类对象反射获得Person对象

代码语言:javascript
复制
public static void main(String[] args) throws ClassNotFoundException {
    //通过反射获取类的对象
    Class c1 = Class.forName("g1ts.com.Student");
    Class name = c1.getSuperclass();
    System.out.println(name);
}

结果

代码语言:javascript
复制
class g1ts.com.Person 

获取类方法

getDeclaredMethods方法

getDeclaredMethods方法返回类或接口声明的所有方法,包括public、 protected.、private和默认方法,但不包括继承的方法

代码语言:javascript
复制
import java.lang.reflect.Method;

public class demo{
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> name = Class.forName("java.lang.Runtime");
        Method[] declaredMethods = name.getDeclaredMethods();
        System.out.println("通过getDeclaredMethods方式获取的方法:");
        for(Method m:declaredMethods)
            System.out.print(m+"\n");
    }
}

结果:

代码语言:javascript
复制
通过getDeclaredMethods方式获取的方法:
public void java.lang.Runtime.exit(int)
public void java.lang.Runtime.runFinalization()
public static void java.lang.Runtime.runFinalizersOnExit(boolean)
public void java.lang.Runtime.load(java.lang.String)
public void java.lang.Runtime.loadLibrary(java.lang.String)
synchronized void java.lang.Runtime.loadLibrary0(java.lang.Class,java.lang.String)
public native void java.lang.Runtime.gc()
public static java.lang.Runtime java.lang.Runtime.getRuntime()
synchronized void java.lang.Runtime.load0(java.lang.Class,java.lang.String)
public native long java.lang.Runtime.freeMemory()
public native long java.lang.Runtime.maxMemory()
public void java.lang.Runtime.addShutdownHook(java.lang.Thread)
public native int java.lang.Runtime.availableProcessors()
public java.lang.Process java.lang.Runtime.exec(java.lang.String,java.lang.String[]) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String[]) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String[],java.lang.String[],java.io.File) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String[],java.lang.String[]) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String,java.lang.String[],java.io.File) throws java.io.IOException
public java.io.InputStream java.lang.Runtime.getLocalizedInputStream(java.io.InputStream)
public java.io.OutputStream java.lang.Runtime.getLocalizedOutputStream(java.io.OutputStream)
public void java.lang.Runtime.halt(int)
public boolean java.lang.Runtime.removeShutdownHook(java.lang.Thread)
private static native void java.lang.Runtime.runFinalization0()
public native long java.lang.Runtime.totalMemory()
public native void java.lang.Runtime.traceInstructions(boolean)
public native void java.lang.Runtime.traceMethodCalls(boolean)通过getDeclaredMethods方式获取的方法: public void java.lang.Runtime.exit(int) public void java.lang.Runtime.runFinalization() 

getMethods方法

getMethods返回某个类的的所有public方法,包括其继承类的public方法

代码语言:javascript
复制
import java.lang.reflect.Method;

public class demo{
    public static void main(String[] args) throws ClassNotFoundException {
        Runtime rt = Runtime.getRuntime();
        Class<?> name = rt.getClass();

        Method[] methods = name.getMethods();

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

结果:

代码语言:javascript
复制
getMethods获取的方法:
public void java.lang.Runtime.exit(int)
public void java.lang.Runtime.runFinalization()
public static void java.lang.Runtime.runFinalizersOnExit(boolean)
public void java.lang.Runtime.load(java.lang.String)
public void java.lang.Runtime.loadLibrary(java.lang.String)
public native void java.lang.Runtime.gc()
public static java.lang.Runtime java.lang.Runtime.getRuntime()
public native long java.lang.Runtime.freeMemory()
public native long java.lang.Runtime.maxMemory()
public void java.lang.Runtime.addShutdownHook(java.lang.Thread)
public native int java.lang.Runtime.availableProcessors()
public java.lang.Process java.lang.Runtime.exec(java.lang.String,java.lang.String[]) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String[]) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String[],java.lang.String[],java.io.File) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String[],java.lang.String[]) throws java.io.IOException
public java.lang.Process java.lang.Runtime.exec(java.lang.String,java.lang.String[],java.io.File) throws java.io.IOException
public java.io.InputStream java.lang.Runtime.getLocalizedInputStream(java.io.InputStream)
public java.io.OutputStream java.lang.Runtime.getLocalizedOutputStream(java.io.OutputStream)
public void java.lang.Runtime.halt(int)
public boolean java.lang.Runtime.removeShutdownHook(java.lang.Thread)
public native long java.lang.Runtime.totalMemory()
public native void java.lang.Runtime.traceInstructions(boolean)
public native void java.lang.Runtime.traceMethodCalls(boolean)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

getMethod方法

getMethod方法只能返回一个特定的方法,如Runtime类中的exec()方法,该方法的第一个参数为方法名称,后面的参数为方法的参数对应Class的对象

代码语言:javascript
复制
import java.lang.reflect.Method;

public class demo{
    public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException {
        Runtime rt = Runtime.getRuntime();
        Class<?> name = rt.getClass();

        Method method = name.getMethod("exec", String.class);

        System.out.println("getMethod获取的特定方法:");
        System.out.println(method);
    }
}

结果:

代码语言:javascript
复制
getMethod获取的特定方法:
public java.lang.Process java.lang.Runtime.exec(java.lang.String) throws java.io.IOException

getDeclaredMethod方法

getDeclaredMethod方法与getMethod类似,也只能返回一个特定的方法,该方法的第一个参数为方法名,第二个参数名是方法参数

代码语言:javascript
复制
import java.lang.reflect.Method;

public class demo{
    public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException {
        Runtime rt = Runtime.getRuntime();
        Class<?> name = rt.getClass();

        Method method = name.getDeclaredMethod("exec", String.class);

        System.out.println("getDeclaredMethod获取的特定方法:");
        System.out.println(method);
    }
}

结果:

代码语言:javascript
复制
getDeclaredMethod获取的特定方法:
public java.lang.Process java.lang.Runtime.exec(java.lang.String) throws java.io.IOException

Invoke方法

获得了方法后可以使用Invoke方法来执行获得的方法

首先先为Student类增加新的方法

代码语言:javascript
复制
public void doSomething(String str){
    System.out.println("正在做"+str);
}

测试代码

代码语言:javascript
复制
package g1ts.com;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test06 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        Class c = Class.forName("g1ts.com.Student");
        Student student = (Student) c.newInstance();
        Method method = c.getMethod("doSomething", String.class);

        method.invoke(student, "作业");

    }
}

结果

代码语言:javascript
复制
正在做作业 

但是无论是getMethod方法还是getDeclaredMethod方法都无法获取private方法

getMethod方法显示没有此方法

甚至getDeclaredMethod方法直接显示出了无法调用private方法

所以我们可以调用AccessibleObject上的setAccessible()方法来允许访问

代码语言:javascript
复制
package g1ts.com;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test06 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        Class c = Class.forName("g1ts.com.Student");
        Student student = (Student) c.newInstance();
        Method method = c.getDeclaredMethod("haveARest");
        method.setAccessible(true);

        method.invoke(student);

    }
}

结果

代码语言:javascript
复制
我要休息 

此处这样写只能用getDeclaredMethod而不能用getMethod

获取类成员变量

为了更直接地体现出获取类成员变量的方法,先创建一个Student类

代码语言:javascript
复制
package com.test;

public class Student {
    public String id;
    public String name;
    public String content;
    private int age;
    protected String address;

    public String getId(){
        return id;
    }
    public void setId(String id){
        this.id = id;
    }
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
    public String getContent()
    {
        return content;
    }
    public void setContent(String content){
        this.content = content;
    }
    public String getAddress(){
        return address;
    }
    public void setAddress(String address)
    {
        this.address = address;
    }
}

getFields方法

getFields能够获得某个类的所有的public字段,包括父类中的字段

代码语言:javascript
复制
package g1ts.com;

import java.lang.reflect.Field;

public class Test03{
    public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException {
        Student student = new Student();
        Class<?> name = student.getClass();
        Field[] getFields = name.getFields();

        System.out.println("通过getFields方式获取方法:");
        for(Field m:getFields)
            System.out.println(m);
    }
}

结果

代码语言:javascript
复制
通过getFields方式获取方法:
public java.lang.String g1ts.com.Student.id
public java.lang.String g1ts.com.Student.name
public java.lang.String g1ts.com.Student.content

getField方法

与getFields类似,getField方法能够获得某个类特定的public字段,包括父类中的字段,这里想获得Student类中的public类型变量content

代码语言:javascript
复制
package g1ts.com;

import java.lang.reflect.Field;

public class Test03{
    public static void main(String[] args) throws ClassNotFoundException,NoSuchFieldException {
        Student student = new Student();
        Class<?> name = student.getClass();
        Field getField = name.getField("content");

        System.out.println("通过getField方式获取方法:");
        System.out.println(getField);
    }
}

结果

代码语言:javascript
复制
通过getField方式获取方法:
public java.lang.String g1ts.com.Student.content

使用getDeclaredFields获取成员变量

getDeclaredFields方法能够获得类的成员变量数组,包括public、private和proteced,但是不包括父类的申明字段(属性,即设置了get方法和set方法的成员变量),即只能获得单纯只在子类声明的属性

代码语言:javascript
复制
package g1ts.com;

import java.lang.reflect.Field;

public class Test03{
    public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException {
        Student student = new Student();

        Class<?> name = student.getClass();

        Field[] getDeclaredFields = name.getDeclaredFields();
        System.out.println("通过getDeclaredFields方式获取方法:");
        for(Field m:getDeclaredFields)
            System.out.println(m);
    }
}

结果:

代码语言:javascript
复制
通过getDeclaredFields方式获取方法:
public java.lang.String g1ts.com.Student.id
public java.lang.String g1ts.com.Student.name
public java.lang.String g1ts.com.Student.content
private int g1ts.com.Student.age
protected java.lang.String g1ts.com.Student.address

getDeclaredField方法

该方法与getDeclaredFields的区别是只能获得类的单个成员变量,这里我们仅想获得Student类中的name变量

代码语言:javascript
复制
package g1ts.com;

import java.lang.reflect.Field;

public class Test03{
    public static void main(String[] args) throws ClassNotFoundException,NoSuchFieldException {
        Student student = new Student();

        Class<?> name = student.getClass();

        Field getDeclaredField = name.getDeclaredField("name");

        System.out.println("通过getDeclaredFields方式获取方法:");
        System.out.println(getDeclaredField);
    }
}

结果

代码语言:javascript
复制
通过getDeclaredFields方式获取方法:
public java.lang.String g1ts.com.Student.name

获取构造方法

在原来Person类的基础上进行修改

代码语言:javascript
复制
public Person() {
    System.out.println("无参构造方法");
}

public Person(String name, int id, int age) {
    this.name = name;
    this.id = id;
    this.age = age;
    System.out.println("有参构造方法");
}

private Person(boolean b){
    System.out.println("私有构造方法");
}

测试代码

代码语言:javascript
复制
package g1ts.com;

import java.lang.reflect.Constructor;

public class Test04 {
    public static void main(String[] args) {
        try {
            Class c1 = Class.forName("g1ts.com.Person");

            System.out.println("getConstructors返回所有public构造函数");
            Constructor[] constructors1 = c1.getConstructors();
            for (Constructor c : constructors1) {
                System.out.println(c);
            }

            System.out.println("getDeclaredConstructors返回所有构造函数");
            Constructor[] constructors2  = c1.getDeclaredConstructors();
            for (Constructor c : constructors2) {
                System.out.println(c);
            }

            System.out.println("getConstructor匹配和参数类型相符的public构造函数");
            Constructor constructors3 = c1.getConstructor(String.class, int.class, int.class);
            System.out.println(constructors3);

            System.out.println("getDeclaredConstructor匹配和参数类型相符的构造函数");
            Constructor constructors4 = c1.getDeclaredConstructor(boolean.class);
            System.out.println(constructors4);

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

结果

代码语言:javascript
复制
getConstructors返回所有public构造函数
public g1ts.com.Person(java.lang.String,int,int)
public g1ts.com.Person()
getDeclaredConstructors返回所有构造函数
private g1ts.com.Person(boolean)
public g1ts.com.Person(java.lang.String,int,int)
public g1ts.com.Person()
getConstructor匹配和参数类型相符的public构造函数
public g1ts.com.Person(java.lang.String,int,int)
getDeclaredConstructor匹配和参数类型相符的构造函数
private g1ts.com.Person(boolean)

通过类创建对象

newInstance

newInstance调用的是无参构造方法

代码语言:javascript
复制
package g1ts.com;

public class Test05 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class c = Class.forName("g1ts.com.Person");
        Person p = (Person) c.newInstance();

        System.out.println(p);
    }
}

结果

代码语言:javascript
复制
无参构造方法
g1ts.com.Person{name='null', id=0, age=0}

若果调用newInstance报错有可能有以下原因

1、使用的类没有无参构造函数

2、使用的类构造函数是私有的

getConstructor

getConstructor可以调用有参构造方法

代码语言:javascript
复制
package g1ts.com;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test05 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class c = Class.forName("g1ts.com.Person");
        Constructor con = c.getConstructor(String.class, int.class, int.class);
        Person p = (Person) con.newInstance("g1ts", 0, 22);

        System.out.println(p);
    }
}

结果

代码语言:javascript
复制
有参构造方法
g1ts.com.Person{name='g1ts', id=0, age=22}

参考资料:《Java代码审计(入门篇)》

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java反射机制
  • 什么是反射
  • 反射的用途
  • 反射的基本运用
    • 所有类型的Class
      • 获取Class类
        • 使用forName()方法
      • 获取类成员变量
        • 获取构造方法
          • 通过类创建对象
          相关产品与服务
          代码审计
          代码审计(Code Audit,CA)提供通过自动化分析工具和人工审查的组合审计方式,对程序源代码逐条进行检查、分析,发现其中的错误信息、安全隐患和规范性缺陷问题,以及由这些问题引发的安全漏洞,提供代码修订措施和建议。支持脚本类语言源码以及有内存控制类源码。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档