java安全-反射机制

前言:

        java反射机制作用主要是在java执行过程中允许我们去构造任意类的的对象,我们可以对修改任意类的成员变量值,并调用任意对象的属性和方法。有点类似shellcode里的ROP链。

        简单来说java反射机制就是通过获取Class对象然后使用java.lang.reflect里提供的方法操作Class对象,Class与java.lang.reflect最终实现了java反射

        通过反射机制我们可以调用任意类的任意方法,且可以修改成员变量内容,常来做webshell的免杀和java反序列化漏洞执行命令等。

代码实现:

正射:

        首先我们先了解下正常的调用方式:

        我们创建main.java和people.java,代码如下:

         main函数如下:

import java.lang.reflect.*;

public class Main{
    public static void main(String[] args) throws Exception {
        people peo = new people();
        peo.num_people(); 
    
    }
}

        people函数如下:

public class people{
    private String boy = "66";
    int girl = 10;
    public void num_people(){
        System.out.println("boy is " + this.boy + " girl is " + this.girl);
    }
}

        正常我们的调用方式我们首先要new一个对象,然后才能去调用里面的方法,而且方法必须为public属性才可以调用。下面我们来了解下反射是如果实现上面的功能。

反射:

获取class对象三种方法:

1.使用.class方法

       Class clazz = String.class;

2.使用getclass方法

       String str = new String("Hello");
       Class clazz = str.getClass();

3.使用Class.forName 静态方法

        Class clazz = Class.forName("java.lang.String");

获取和修改字段和方法:

getField(String name)
getFields()
可以获取子类和父类的所有public变量
getDeclaredField(String name)
getDeclaredFields()
只能获取子类所有的public、protected和private类型的变量,不能获取父类的变量
field.get(Obj)
field.get(null)
获取Obj对象的field字段
获取static修饰的静态字段field的值
field.set(Obj,field_value)
field.set(null,field_value)
设置Obj对象的field字段值为field_value
设置static修饰的静态字段field的值为field_value
setAccessible(boolean flag)
当为private和protected时将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
getMethods()
getMethod(String name, Class... parameterTypes)
根据方法名和参数类型获取对应的方法,只能获取public类型方法,可以获取父类和子类方法
getDeclaredMethods()
getDeclaredMethod(String name, Class... parameterTypes)
获取子类所有的方法,包括public、protected和private,但是不能获取父类的方法
invoke(Object obj, Object[] args)
执行通过反射获取的方法,obj代表要执行方法的对象,args代表方法的参数

构造函数获取和使用:

getConstructors()
getConstructor(Class... parameterTypes)
获取类所有的构造函数,只能获取public类型的字段,不能获取父类的构造函数。
getDeclaredConstructors()
getDeclaredConstructor (Class... parameterTypes)
获取类所有的构造函数,包括public、protected和private。不能获取父类的构造函数。
newInstance(Object ... initargs)
newInstance函数是实现“虚拟构造器”的一种方法,使用该方法创建类,接受可变的参数个数,构造函数实际有几个传输,这里就传递几个参数值。

通过类名反射调用方法:

people.java:

public class people{
    private String boy = "66";
    int girl = 10;
    private final String fin_boy = "99";
    private void num_people(){
        System.out.println("boy is " + this.boy + " girl is " + this.girl + " fin_boy is " + this.fin_boy);
    }
}

main.java

import java.lang.reflect.*;

public class Main{
    public static void main(String[] args) throws Exception {
        test();
    }

    public static void test() throws Exception{
        String class_name = "people";
        String function_name = "num_people";
        Class clazz = Class.forName(class_name);
        Method method = Class.forName(class_name).getDeclaredMethod(function_name);
        method.setAccessible(true);
        method.invoke(clazz.newInstance());
    }
}

这里使用forName获取了people的类,反射调用num_people方法,最后使用invoke发射执行方法

反射获取并修改参数值

people.java

public class people {
    private String boy = "66";
    int girl = 10;
    private final String fin_boy = "99";
    private final StringBuilder fin_boy2 = new StringBuilder("99");
    private static final StringBuilder fin_boy3 = new StringBuilder("88");

    public void num_people(){
        System.out.println("boy is " + this.boy + " girl is " + this.girl + " fin_boy is " + this.fin_boy + "fin_boy2 is " + this.finboy2 + "fin_boy3 is " + this.finboy3);
    }
}

main.java

import java.lang.reflect.*;

public class Main{
    public static void main(String[] args) throws Exception {
        test();
    }
    
    public static void test() throws Exception{
        people peo = new people();
        Class peoclass = peo.getClass();    

        Field field = peoclass.getDeclaredField("boy");
        field.setAccessible(true);  
        String boy = (String) field.get(peo);
        peo.num_people();

        field.set(peo,"change"); 
        peo.num_people();

        field = peoclass.getDeclaredField("fin_boy2");
        field.setAccessible(true);
        field.set(peo,new StringBuilder("fin_boy2 change"));
        peo.num_people();
    }
}

        这里boy是private,所以可以使用getDeclaredField获取boy变量,使用set对值进行修改,但是fin_boy是final String类型,并且其值为直接赋值,所以可以看到打印第三行修改失败。只有为间接赋值的时候才可以进行修改,如fin_boy2可以虽然为final,但是可以进行修改。

        当类型既有final又有static修饰的字段,这个时候直接修改会报错,要采用反射修改final修饰符的值,然后就可以进行正常的修改。

        直接赋值:直接赋值是指在创建字段时就对字段进行赋值,并且值为JAVA的8种基础数据类型或者String类型,类似:

private final int number = 5;
private final String sex = "boy";

     除此以外均为间接赋值,均可以进行修改。

反射调用方法

people.java

public class people{

    public void num_people5(String number){
        System.out.println("fin_boy5 is " + number);
    }

    public  void num_people6(String strnumber, int intnumber){
        System.out.println("fin_boy6  is " + strnumber + " int is " + intnumber);
    }

    private static  void num_people7(String strnumber, int intnumber){
        System.out.println("fin_boy7  is " + strnumber + " int is " + intnumber);
    }
}

main.java

import java.lang.reflect.*;

public class Main{
    public static void main(String[] args) throws Exception {
        test_reflect();
    }
    
    public static void test_reflect() throws Exception{
        people peo = new people();
        Class peoclass = peo.getClass();
        Method method = peoclass.getMethod("num_people5", String.class);
        method.invoke(peo,"change");

        method = peoclass.getMethod("num_people6", new Class<?>[]{String.class, int.class});
        method.setAccessible(true);
        method.invoke(peo,new Object[]{"999",100});

        method = peoclass.getDeclaredMethod("num_people7", new Class<?>[]{String.class, int.class});
        method.setAccessible(true);
        method.invoke(null,new Object[]{"888",111});

    }
}

 反射调用方法首先需要获取class对象,然后根据方法属性选择使用getDeclaredMethod还是getMethod,如果参数较多,采用new Class的方式传入,然后使用invoke执行方法,此处需要注意,如果函数为static静态函数,则在使用invoke的时候传入null进行反射调用。

反射调用构造函数

people.java

public class people{

    private int int_test8;
    private String str_test8;
    public people(){
        this.int_test8 = 11;
        this.str_test8 = "cheange";
    }
    public people(int int_test8, String str_test8){
        this.int_test8 = int_test8;
        this.str_test8 = str_test8;
    }
    public void num_people8(){
        System.out.println("fin_boy7  is " + this.str_test8 + " int is " + this.int_test8);
    }
}

main.java

import java.lang.reflect.*;

public class Main{
    public static void main(String[] args) throws Exception {
        con_reflect();
    }
    
    public static void con_reflect() throws Exception{
        Class peoclass = people.class;
        Constructor constructor = peoclass.getConstructor();
        people peo = (people) constructor.newInstance();
        peo.num_people8();

        constructor = peoclass.getConstructor(int.class,String.class);
        constructor.setAccessible(true);
        peo = (people) constructor.newInstance(20,"mychange");
        peo.num_people8();
    }
}

        反射调用构造函数首先获得class对象,然后调用getConstructor函数获得构造函数,然后使用newInstance构造函数,并调用

反射java.lang.Runtime:

Runtime.getRuntime().exec(cmd2);

反射首先要获取Runtime类,及java.lang.Runtime,然后依次获取方法getRuntime和exec,然后实例化getRuntime并最终调用exec

//获取Runtime类
Class clazz = Class.forName("java.lang.Runtime");

//获取getRuntime方法
Method getRuntimeMethod = clazz.getMethod("getRuntime");
//获取exec方法
Method execMethod = clazz.getMethod("exec", String.class);

//执行getRuntime方法来获取Runtime实例
Object runtime = getRuntimeMethod.invoke(clazz);
//通过runtime示例执行命令
execMethod.invoke(runtime, "calc.exe");

注意:

其中关于静态变量和非静态变量的反射修改需要注意,静态变量可以直接进行修改,但是非静态变量需要实例化后才能修改,但是这就带来一个问题,只有当前实例化的内容中被修改,如果重新实例化则之前反射修改的失效:

例如如下情况,如果我们想要修改AGPL因为是静态变量可以直接修改,但是下面的则不能直接修改

反射修改代码如下:

Class clazz = Class.forName("com.itextpdf.text.Version");

Field field = clazz.getDeclaredField("AGPL");
field.setAccessible(true);

System.out.println("field.get:" + field.get(clazz));
System.out.println("Version.AGPL:" + Version.AGPL);
field.set(clazz,"aaaaaa");
System.out.println("field.get change:" + field.get(clazz));
System.out.println("Version.AGPL change:" + Version.AGPL);

可以看到修改完成后其值已经永久被修改,只要不被销毁: 

但是当我们直接修改另外非静态变量,如果不实例化会报错:

Class clazz = Class.forName("com.itextpdf.text.Version");

Field field = clazz.getDeclaredField("iText");
field.setAccessible(true);

System.out.println("field.get:" + field.get(clazz));
field.set(clazz,"aaaaaa");
System.out.println("field.get change:" + field.get(clazz));

可以看到会报类型错误,这时候需要实例化: 

 这个时候修改代码如下,通过两次调用,实例化两次:

Class clazz = Class.forName("com.itextpdf.text.Version");
Object object = clazz.newInstance();

Field field = clazz.getDeclaredField("iText");
field.setAccessible(true);

System.out.println("field.get:" + field.get(object));
field.set(object,"aaaaaa");
System.out.println("field.get change:" + field.get(object));

String string =  Version.getInstance().getVersion();
System.out.print(string);

运行后可以发现仅能对当前实例化的内容进行更改,所以在反射变量的时候需要注意,如果要反射修改非静态变量需要使用当前实例化对象才可以: 

总结:

        当我们需要反射调用函数的时候参照如下方法:

  1. 首先需要获得要反射类的class对象,获取class对象方式有三种,可以根据实际情况进行选择
  2. 当我们需要修改参数值的时候,可以采用field来获取参数值或对参数值进行修改。
  3. 当需要反射调用方法的时候,可以使用method来进行反射调用。
  4. 当需要调用构造函数的时候,可以使用Constructor进行反射调用
  5. 另外就是要注意static和final类型的函数和参数修改方法。

        反射在webshell免杀和反序列化漏洞中使用会比较频繁,但是反射本身并不难,只要掌握了主要的几个函数基本就可以了,在实战中经常采用反射的方式调用危险函数,进而绕过免杀检测和进行调用链的构造。后续我会对反射在安全中的实战进行讲解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值