06-反序列化缺陷
2025/1/1大约 4 分钟安全开发安全开发应用安全
反序列化缺陷 - 腾讯安全代码审计实战系列06
序列化是把对象转换成一种数据格式, JSON, XML等文本格式或二进制字节流格式, 便于保存在内存, 文件, 数据库中或者在网络通信中进行传输。 反序列化是序列化的逆过程, 即由保存的文本格式或字节流格式还原成对象。 当反序列化的输入可以被用户控制, 恶意用户便可以构造恶意的字节流, 经反序列化之后得到精心构造的恶意对象。
其危害如下:
- 拒绝服务攻击
- 访问控制攻击
- 远程命令执行攻击
修复建议
- 不要对来自不可信来源的数据进行反序列化。
- 对所有反序列化的数据进行加密签名,并确保在进行反序列化之前对数据进行验证。
- 为反序列化的接口添加认证与授权机制,确保只有授权的用户可以访问。
- 确保反序列化服务只在本地环境运行或适当配置防火墙规则以阻止未授权的远程访问。
- 定期更新和升级所有使用的第三方库,以利用最新的安全修补程序防止已知漏洞的利用。
示例代码
Java代码示例:
//bad:直接从请求中获取输入流进行反序列化,存在高风险
public void unsafeDeserialization(HttpServletRequest request) {
try {
InputStream in = request.getInputStream();
ObjectInputStream ois = new ObjectInputStream(in);
Object obj = ois.readObject(); // 反序列化操作
ois.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
//good:通过验证数据签名来增强安全性
public void safeDeserialization(HttpServletRequest request) {
try {
InputStream in = request.getInputStream();
if (!isSignatureValid(in)) { // 检查数据签名是否有效
throw new SecurityException("Invalid data signature");
}
// 使用白名单过滤可反序列化的类
ObjectInputStream ois = new SecureObjectInputStream(in, Arrays.asList(AllowedClass1.class, AllowedClass2.class));
Object obj = ois.readObject();
ois.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
PHP代码示例:
<?php
//bad:直接对来自用户输入的数据进行反序列化
function unsafeDeserialization($userData) {
$obj = unserialize($userData); // 反序列化操作,危险
// 使用$obj做进一步处理
}
//good:检查数据签名后才进行反序列化
function safeDeserialization($userData, $signature) {
if (!checkSignature($userData, $signature)) { // 检查数据签名是否匹配
throw new Exception("Invalid data signature");
}
$obj = unserialize($userData);
// 使用$obj做进一步处理
}
//good:限制反序列化的类
function safeDeserializationWithClassLimit($userData) {
$allowedClasses = ['AllowedClass1', 'AllowedClass2']; // 允许反序列化的类列表
$obj = unserialize($userData, ["allowed_classes" => $allowedClasses]);
// 使用$obj做进一步处理
}
// 辅助函数:模拟检查签名
function checkSignature($data, $signature) {
return md5($data) === $signature; // 假设使用md5进行签名处理
}
?>
Python代码示例:
import pickle
from cryptography.fernet import Fernet
#bad:直接反序列化未验证的数据
def unsafe_deserialization(data):
try:
obj = pickle.loads(data) # 反序列化操作
except pickle.PickleError as e:
print("Failed to deserialize:", e)
#good:对数据进行验证后进行安全的反序列化
def safe_deserialization(data, key):
fernet = Fernet(key)
try:
# 验证并解密数据
decrypted_data = fernet.decrypt(data)
# 只允许反序列化特定的安全类型
safe_globals = {'__builtins__': None}
obj = pickle.loads(decrypted_data, fix_imports=True, globals=safe_globals)
print("Successfully deserialized object:", obj)
except Exception as e:
print("Failed to deserialize safely:", e)
#good:使用白名单进行类过滤的反序列化
def class_filter():
safe_classes = {'allowed_class': AllowedClass}
try:
with open('data.pkl', 'rb') as file:
obj = pickle.load(file, fix_imports=True, globals={'__builtins__': None, 'allowed_class': AllowedClass})
except Exception as e:
print("Deserialization error:", e)
WebGoat-main/src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserializationTask.java
/*
InsecureDeserializationTask.java:52 token是污点来源
InsecureDeserializationTask.java:58 污点从token传递至b64token
InsecureDeserializationTask.java:60 污点从b64token传递至ois
InsecureDeserializationTask.java:63 反序列化类型风险触发,由入参ois导致
*/
@RestController
@AssignmentHints({
"insecure-deserialization.hints.1",
"insecure-deserialization.hints.2",
"insecure-deserialization.hints.3"
})
public class InsecureDeserializationTask implements 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();
}
}