Java反序列化漏洞—基础

前言

为了更接近目标(红队),需要先将常规打点的基础打好。虽说PHP很流行,但市场需求、语言特性和培训机构,工作中大多以Java为主,因此漏洞也是Java最多,所以学习JAVA漏洞非常有必要。


反序列化学习

最近目标:掌握Fastjson漏洞。


序列化过程

PHP

    class test{ 											//类    
     $flag = "flag{111}";			  
     }
    $test = new test;										//实例化
    $data_ser_result = serialize($test);					//序列化
	$data_un_result  = unserialize($data_ser_result);		//反序列化
 ?>

序列化的过程可以看成将类进行打成压缩包,而反序列化就是解压
实例是占内存的,而类只是个抽象的,不占内存

可以发现PHP序列化和反序列化非常简单,只用调用一个行数即可,序列化后输出结果也只是一条字符串。

O:4:"test":1:{s:4:"flag";s:9:"flag{111}";}         			//上面代码序列化的结果

class test{ 											    //反序列化结果  
     $flag = "flag{111}";			  
     }

JAVA

Java和php序列化过程一样,只不过复杂一些。

package test;

import java.io.*;

public class Serialize{

    public static void main(String args[]) throws Exception {
        String obj = "ls";										 //对象
        
		//将序列化对象写入a.ser
        FileOutputStream fos = new FileOutputStream("aa.ser");    
        ObjectOutputStream os = new ObjectOutputStream(fos);	 
        os.writeObject(obj);										//序列化
        os.close();
         
        // 从文件a.ser中读取数据
        FileInputStream fis = new FileInputStream("aa.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);


        // 通过反序列化恢复对象obj
        String obj2 = (String)ois.readObject();						//反序列化
        System.out.println(obj2);
        ois.close();
    }
}

上面的代码是将对象obj序列化后写入aa.ser文件,然后才从aa.ser文件读取并反序列化。
序列化结果
在这里插入图片描述
反序列化结果
在这里插入图片描述
WinHex打开aa.ser文件发现可以看到对象的信息。写到文件的便是该对象序列化后的二进制数据
在这里插入图片描述

JAVA小小的进阶

上面序列化的只是一个对象,但在现实中不可能就传输一个对象,因此将对象变成一个类,可以发现没有什么区别,只是多了一步,将类实例化。

package test;
import java.io.*;

class User implements Serializable {

    public String name;
    public int age;
    public String  getUserInfo(){
        return "name: "+this.name +" age: "+this.age;
    }
}

public class Serialize{
	
    	public static void main(String[] args) throws Exception {

            User user = new User();
            user.name = "啦啦啦啦啦啦啦";
            user.age = 1;

            try {

                FileOutputStream fos = new FileOutputStream("user.txt");
                ObjectOutputStream os = new ObjectOutputStream(fos);
                os.writeObject(user);

                os.close();
                fos.close();

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

            FileInputStream fis = new FileInputStream("user.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            User user_out = (User) ois.readObject();

            ois.close();
            fis.close();
            System.out.println(user_out.getUserInfo());
        }
}

在这里插入图片描述

写入恶意代码

User类注释,重写ObjectInputStream的readObject方法,将它放在自定义ShellClass类中。

package test;
import java.io.*;

/*class User implements Serializable {
   public String name;
   public int age;
   public String  getUserInfo(){
        return "name: "+this.name +" age: "+this.age;
    }

}*/

class ShellClass implements Serializable{
	 public String name;
	 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
	        in.defaultReadObject();
	        Runtime.getRuntime().exec("calc.exe");
	    }
}

public class Serialize{
    	public static void main(String[] args) throws Exception {

            //User user = new User();
            //user.name = "啦啦啦啦啦啦啦";
           // user.age = 1;
    		ShellClass shell = new ShellClass();
    		
            try {

                FileOutputStream fos = new FileOutputStream("user.txt");
                ObjectOutputStream os = new ObjectOutputStream(fos);
                
                os.writeObject(shell);
                os.close();
                fos.close();
                
            } catch (IOException e) {
                e.printStackTrace();
            }

            FileInputStream fis = new FileInputStream("user.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
           
            ShellClass a = (ShellClass)ois.readObject();
            ois.close();
            fis.close();
        }    
}

其实就是写一个readObject()方法,将原有的ObjectInputStream的readObject()方法重新调用,并在后面跟上自己的恶意代码


JAVA反射

java反射机制

Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制。

正射

Student student = new Student();
student.doHomework("数学");

反射

 Class clazz = Class.forName("reflection.Student");
 Method method = clazz.getMethod("doHomework", String.class);
 Constructor constructor = clazz.getConstructor();
 Object object = constructor.newInstance();
 method.invoke(object, "语文");

可以发现上面正射和反射看起来好像没什么区别,但对于编译的电脑来说却区别很大
一个是我想知道这个类在哪、做什么,然后我才开始运行代码,实例化后调用方法,另一个是我先运行代码,再获取有这个类在哪、做什么的。

具体参考
谈谈Java反射:从入门到实践,再到原理
为什么需要Java反射?
大白话说Java反射:入门、使用、原理

反射的使用

User类

package test;
import java.lang.reflect.*;

 class User {
    public String name;

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

获取Class类对象

获取反射中的Class对象有三种方法

Class class1 = Class.forName("test.User");

Class class1 = User.class;
 
User user = new User();
Class class1 = user.getClass();

利用类对象新建对象

public static void main(String[] args) throws Exception {
		Class classa = Class.forName("test.User");
		Constructor constructor=classa.getConstructor(String.class);
		User student1 = (User) constructor.newInstance("a");
		System.out.println(student1);
	}

获取类成员变量

public static void main(String[] args) throws Exception {
		Class user = Class.forName("test.User");
        Field name= user.getField("name");
        System.out.println(name);
	}

获取类的方法

public static void main(String[] args) throws Exception {
		Class user = Class.forName("test.User");
		 Method[] methods = user.getMethods();
	        for (int i = 0; i < methods.length; i++) {
	            System.out.println(methods[i]);
	        }
	}

通过反射构造恶意代码

在上面有写过一个恶意代码

java.lang.Runtime.getRuntime().exec("calc.exe");

我们将它用反射方法写出来

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

为什么使用反射呢

代码乱写的,了解个意思就好,有错勿喷

不使用反射

interface Apple{
xxxxxx
}
class Red implements Apple{
	public void shopping(){
		System.out.println("red");
	}
}

class Black implements Apple{
	public void shopping(){
		System.out.println("black");
	}
}

class Stop{
	public string color(String apple_color){
		if(apple_color=="red"){
			result = new Red();
		}else(apple_color=="black"){
			result = new Black();
		}
		retrun result;
	}
}

class Buy{
	public static void main(String[] a){
		Apple s = Stop.color("red");
		s.shopping();
	}
}

使用反射

interface Apple{
xxxxxx
}
class Red implements Apple{
	public void shopping(){
		System.out.println("red");
	}
}

class Black implements Apple{
	public void shopping(){
		System.out.println("black");
	}
}

class Stop{
	public string color(String apple_color){
		Apple result = (Apple)Class.forName(apple_color).newInstance();
		retrun result;
	}
}

class Buy{
	public static void main(String[] a){
		Apple s = Stop.color("Apple.red");
		s.shopping();
	}
}

总结

可以发现上面实现的功能差不多,只不过写法不同,那这样也看不出为啥要使用反射。
仔细看Buy这个类中传参给Stop时,使用反射的多加了一个Apple类名,这里就有问题了,可以发现这个类是可以修改的,再加些方法,那我是不是可以调用任意类,去运行系统命令了。
有的人会说用反射效率差,对性能要求高,用于字段和方法接入时要远慢于直接代码,那
在这里插入图片描述
我就是一个安服仔!!!
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值