Java安全学习笔记(一)

本文详细讲解了Java反射的基本概念、重要方法以及在实例化无参构造和私有方法中的应用,特别关注RMI中codebase的远程代码执行漏洞利用案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

反射

Java中对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。(P神的定义)

反射中几位重要的方法
  • 获取类的方法:forname
  • 实例化类对象的方法:newInstance
  • 获取函数的方法:getMethod
  • 执行函数的方法:invoke
获取类的三种方式
  • obj.getClass() 如果上下文中存在某个类的实例obj,那么我们可以直接通过obj.getClass()来获取它的类。

    Runtime runtime = Runtime.getRuntime();
            System.out.println(runtime.getClass());
    // class java.lang.Runtime
    
  • Test.class 如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接 拿它的 class 属性即可。这个⽅法其实不属于反射。

  • Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取

    Class clazz = Class.forName("java.lang.Runtime");
            System.out.println(clazz);
    // class java.lang.Runtime
    
使用newInstance不成功的一些原因
  1. 你使用的类没有无参构造函数
  2. 你使用的类构造函数是私有的

案例

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "whoami");

会报错image-20211102162734720

原因是Runtime类的构造方法是私有的

可以这样

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");

getMethod的作用是通过反射获取一个类的特定的公有方法。

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

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

上述命令执行的payload可以分解为

Class clazz = Class.forName("java.lang.Runtime");
Method exec = clazz.getMethod("exec", String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
exec.invoke(runtime, "calc.exe");

到这里P神提出了两个疑问

  • 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类 呢?
  • 如果一个方法或构造方法是私有方法,我们是否能执行它呢?

第一个问题的解决方式,需要用到一个新的放射方法getConstructor

和 getMethod 类似, getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。 获取到构造函数后,我们使用 newInstance 来执行。

ProcessBuilder有两个构造函数

  • public ProcessBuilder(List command)
  • public ProcessBuilder(String… command)

用第一种形式

主要传入List.class

Class clazz = Class.forName("java.lang.ProcessBuilder");
            ((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();

利用反射的方式

Class clazz = Class.forName("java.lang.ProcessBuilder");
            clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));

第二种形式

主要传入String[].class

Class clazz = Class.forName("java.lang.ProcessBuilder");
        ((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{
   
   {
   
   "calc.exe"}})).start();

反射的方式

Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{
   
   {
   
   "calc.exe"}}));

第二个问题:如果一个方法或构造方法是私有方法,我们是否能执行它呢?

解决方式是getDeclared系列的反射,它与普通的getMethodgetConstructor区别是:

  • getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
  • getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私 有的方法,但从父类里继承来的就不包含了

实现代码

Class clazz = Class.forName("java.lang.Runtime");
        Constructor exec = clazz.getDeclaredConstructor();
        exec.setAccessible(true);
        clazz.getMethod("exec", String.class).invoke(exec.newInstance(), "calc.exe");

这里使用了一个方法 setAccessible ,这个是必须的。我们在获取到一个私有方法后,必须用 setAccessible 修改它的作用域,否则仍然不能调用。

RMI

RMI全称是Remote Method Invocation,远程方法调用。

RMI Server

package org.vulhub.RMI;

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class RmiServer {
   
   

   
    // interface继承自interface使用extends
    // interface 代表这个类没有字段 全是方法
     // ⼀个继承了 java.rmi.Remote 的接⼝ 其中定义了我们要调用的类
    public interface IRemoteHelloWorld extends Remote {
   
   
        // 这一句是什么意思?
        public String hello() throws RemoteException;
    }
    // extends 继承关键字
    // 使用 implements 关键字可以变相的使java具有多继承的特性
    // throw和throws就是异常相关的关键字
    public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
   
   

        protected RemoteHelloWorld() throws RemoteException {
   
   
            super();
        }
        // 定义我们要远程调⽤的函数
        public String hello() throws RemoteException {
   
   
            System.out.println("call from");
            return "Hello world";
        }
    }
    // ⼀个主类,⽤来创建Registry
    private void start() throws Exception {
   
   
        RemoteHelloWorld h = new RemoteHelloWorld();
        LocateRegistry.createRegistry(1099);
        Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
    }
    public static void main(String[] args) throws Exception {
   
   
        new RmiServer().start();
    }
}

一个RMI Server分为三部分

  1. 一个继承了java.rmi.Remote的接口,其中定义我们要远程调用的函数,比如这里的hello()
  2. 一个实现了此接口的类
  3. 一个主类,用来创建Register,并将上面的类实例化后绑定到一个地址,这就是我们所谓的Server了。

RMI Client

package org.vulhub.Train;
import org.vulhub.RMI.RMIServer;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class <
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值