五、漏洞修复
上面我们看到了利用常用开源组件的多个类构建的一系列攻击链,如果仅用黑名单限制某些攻击链上类的反序列化是不够的,会有源源不断的新的攻击链被挖掘出来。所以为了让代码更受控、更安全,最好能梳理清楚业务上需要反序列化的类列表,进行白名单校验。
控制反序列化源: 反序列化的数据源如果是可以轻易地被外部用户控制,就一定要做白名单校验。如果数据源在正常业务不能被外部控制,但是也不能完全排除攻击者通过其它手段攻破进来篡改了相关依赖的数据源后发动组合攻击,最好也做白名单防护。
白名单校验: 涉及到使用 ObjectInputStream
进行反序列化时,重写 resolveClass
方法增加白名单校验。业务代码使用重写 SecureObjectInpuStream
类进行反序列化。
注意: 自定义白名单校验的时候,需要考虑到对象中还包含哪些类型的属性,包装类也要考虑在内,否则就会被校验卡住,导致反序列化失败。
5.1 反序列化校验-二进制
方式一:自定义白名单校验
SecureObjectInputStream.java
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
/**
* 白名单校验
*/
public class SecureObjectInputStream extends ObjectInputStream {
public SecureObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
// 白名单校验
if (!desc.getName().equals(“com.demo.user.Person”) && !desc.getName().startsWith(“java.lang”)) {
throw new ClassNotFoundException(desc.getName() + " not found.");
}
return super.resolveClass(desc);
}
}
使用示例:
Test.java
import com.alibaba.fastjson.JSON;
import org.apache.commons.io.serialization.ValidatingObjectInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Test {
/**
* 序列化
*/
public static void main1(String[] args) {
Person obj = new Person(“ACGkaka”, 20);
try {
FileOutputStream fileOut = new FileOutputStream(“D:\object.bin”);
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(obj);
out.close();
fileOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 反序列化
*/
public static void main(String[] args) throws Exception {
ValidatingObjectInputStream vois = new ValidatingObjectInputStream(new FileInputStream(“D:\object.bin”));
vois.accept(Person.class);
vois.accept(“java.lang.*”);
Object object = vois.readObject();
System.out.printf(JSON.toJSONString(object));
}
}
Person.java
package com.demo.user;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
@Data
@AllArgsConstructor
public class Person implements Serializable {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
}
正常反序列化,执行结果:
利用攻击链1,执行结果:
利用攻击链2,执行结果:
方式二:使用工具包中的校验【推荐】
在 commons-io
中有带有 ValidatingObjectInputStream
工具类,专门用于过滤反序列化校验。
使用示例:
import com.alibaba.fastjson.JSON;
import org.apache.commons.io.serialization.ValidatingObjectInputStream;
import java.io.FileInputStream;
public static void main(String[] args) throws Exception {
ValidatingObjectInputStream vois = new ValidatingObjectInputStream(new FileInputStream(“D:\object.bin”));
// 可以多次添加校验内容,只要满足其中一个就会正常反序列化。
vois.accept(Person.class);
vois.accept(“java.lang.*”);
Object object = vois.readObject();
System.out.printf(JSON.toJSONString(object));
}
正常反序列化,执行结果:
利用攻击链1,执行结果:
利用攻击链2,执行结果:
可以看到,两种方式都在保证正常序列化的情况下,完整了漏洞的白名单校验,漏洞修复。
5.2 反序列化校验-FastJSON
漏洞位置:
import com.alibaba.fastjson.JSONObject;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
@Component
public class MyMessageListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
String body = message.toString();
// 漏洞位置
JSONObject jsonObject = JSONObject.parseObject(body);
…
}
}
漏洞分析:
1.2.24及以前版本就跟白纸一样随便调用方法,在1.2.25开始加入了黑白名单机制。
我们继续用1.2.24的payload(这里用TemplatesImpl的payload)去打,会发现报错autotype不支持:
究其原因,是因为在com.alibaba.fastjson.parser.ParserConfig 加入了CheckAutoType方法:
com.alibaba.fastjson.parser.ParserConfig !public Class<?> checkAutoType(String typeName, Class<?> expectClass)
在其中有个autotypesupport属性,如果为false,那么就会检测json中@type的值 开头是否与黑名单中的值一样,若一样就直接返回一个异常,然后加载白名单中的类
if (!autoTypeSupport) {
\黑名单检测,classname是传入类的全名,denyList是黑名单
for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}
黑名单长这样:
若autotypesupport开启,则会先白名单加载,后黑名单检测
if (autoTypeSupport || expectClass != null) {
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
return TypeUtils.loadClass(typeName, defaultClassLoader);
}
}
for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
后面的许多更新都是对checkAutotype以及本身某些逻辑缺陷导致的漏洞进行修复,以及黑名单的不断增加。
漏洞修复:
如何开启autotypesupport?只需在json被解析前加入如下代码即可:
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
5.3 反序列化校验-RedisSerializer
漏洞位置:
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Slf4j
@Component
public class MyMessageListener implements MessageListener {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public void onMessage(Message message, byte[] pattern) {
byte[] body = message.getBody();
// 漏洞位置
String value = (String) redisTemplate.getValueSerializer().deserialize(body);
…
}
}
漏洞修复:
Redis 的序列化和反序列化一般自定义可以分为两种:
RedisStringSerializer
:对 String 类型数据进行序列化和反序列化;RedisObjectSerializer
:对其他 Object 类型数据进行序列化和反序列化。
我们可以根据具体情况来进行修复:
- 如果是 Redis 的 String 类型传输,有两种情况:
- 普通 String 传输,比如传一个编码(NO_1001)。针对这种,我们可以直接对类型进行判断(
if (String.class.equals(redisTemplate.getStringSerializer().getTargetType())
)。 - JSON 类型的 String 传输,需要先进行普通字符串的校验,然后配合上面的 FastJSON 反序列化校验。
- 如果是 Redis 的 Object 类型,有两种处理方式:
- 根据当前传输对象的类型 T.class,再单独创建一个 RedisSerializer 的序列化和反序列化实现,然后在具体需要反序列化的场景中对类型进行判断(
if (T.class.equals(redisTemplate.getValueSerializer().getTargetType()))
)。 - 将对象转为 JSON 格式进行传输,然后配合 if + FastJSON 的格式进行校验。
下面是普通 String 类型传输的校验示例:
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Slf4j
@Component
public class MyMessageListener implements MessageListener {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public void onMessage(Message message, byte[] pattern) {
byte[] body = message.getBody();
// 增加反序列化名单校验
Class<?> targetType = redisTemplate.getStringSerializer().getTargetType();
if (!String.class.equals(targetType)) {
log.error(“类型错误,反序列化对象失败,targetType: {}”, targetType);
throw new RuntimeException(“类型错误,反序列化对象失败”);
}
String value = redisTemplate.getValueSerializer().deserialize(body);
// 如果还需要解析JSON,则配合FastJSON的校验即可。
…
}
}
整理完毕,完结撒花~ 🌻
参考地址:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!
王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。
对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!
【完整版领取方式在文末!!】
93道网络安全面试题
内容实在太多,不一一截图了
黑客学习资源推荐
最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
😝朋友们如果有需要的话,可以联系领取~
1️⃣零基础入门
① 学习路线
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
② 路线对应学习视频
同时每个成长路线对应的板块都有配套的视频提供:
2️⃣视频配套工具&国内外网安书籍、文档
① 工具
② 视频
③ 书籍
资源较为敏感,未展示全面,需要的最下面获取
② 简历模板
因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
9b7e13b39771b3a6e4397753dab12e.png#pic_center)
资源较为敏感,未展示全面,需要的最下面获取
② 简历模板
因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-WS9DiS7o-1712761692533)]