面试题答案
一键面试对输入流的验证
- 字节流长度检查:在接收输入流时,首先检查其长度是否在合理范围内。若输入流过长,可能是恶意构造的攻击数据。例如,通过
ByteArrayInputStream
获取字节数组长度进行判断:
ByteArrayInputStream bais = new ByteArrayInputStream(inputBytes);
if (inputBytes.length > MAX_ALLOWED_LENGTH) {
throw new IllegalArgumentException("Input stream length exceeds limit");
}
- 数据类型初步判断:根据预期的输入类型,对输入流的数据进行初步判断。如对于期望是JSON格式的输入流,先检查是否以
{
或[
开头,以}
或]
结尾。对于XML格式,检查是否有正确的XML声明头。 - 校验和验证:若传输的数据带有校验和(如CRC、MD5等),在反序列化前对输入流数据计算校验和并与传输的校验和对比。例如使用
java.security.MessageDigest
计算MD5:
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(inputBytes);
if (!Arrays.equals(digest, receivedChecksum)) {
throw new SecurityException("Checksum verification failed");
}
黑名单与白名单机制的设计与实现
- 黑名单机制
- 类黑名单:维护一个禁止反序列化的类的列表。在反序列化过程中,通过
ObjectInputStream
的resolveClass
方法进行检查。
- 类黑名单:维护一个禁止反序列化的类的列表。在反序列化过程中,通过
private static final List<String> BLACKLISTED_CLASSES = Arrays.asList(
"java.lang.ProcessBuilder", "javax.management.BadAttributeValueExpException"
);
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (BLACKLISTED_CLASSES.contains(desc.getName())) {
throw new InvalidClassException("Deserialization of this class is prohibited", desc.getName());
}
return super.resolveClass(desc);
}
- 方法黑名单:对于一些可能被恶意利用的方法(如
readObject
方法中可能存在危险操作),可以通过字节码分析(如使用ASM库)来检查反序列化对象的类中是否包含这些方法。
- 白名单机制
- 类白名单:相比黑名单,白名单更为严格,只允许特定的类进行反序列化。同样在
resolveClass
方法中实现:
- 类白名单:相比黑名单,白名单更为严格,只允许特定的类进行反序列化。同样在
private static final List<String> WHITELISTED_CLASSES = Arrays.asList(
"com.example.WhitelistedClass1", "com.example.WhitelistedClass2"
);
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!WHITELISTED_CLASSES.contains(desc.getName())) {
throw new InvalidClassException("Deserialization of this class is not allowed", desc.getName());
}
return super.resolveClass(desc);
}
- 字段白名单:对于类中的字段,也可以设置白名单。在反序列化过程中,通过反射获取类的字段,并检查是否在白名单内。例如:
private static final List<String> WHITELISTED_FIELDS = Arrays.asList("field1", "field2");
ObjectInputStream ois = new ObjectInputStream(inputStream) {
@Override
protected void readObjectOverride(Object obj) throws IOException, ClassNotFoundException {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (!WHITELISTED_FIELDS.contains(field.getName())) {
throw new SecurityException("Field " + field.getName() + " is not allowed");
}
}
super.readObjectOverride(obj);
}
};
与现有的Java安全体系相结合
- 安全管理器(SecurityManager):可以利用Java的
SecurityManager
来限制反序列化过程中的系统资源访问。例如,在反序列化可能执行系统命令的类(如ProcessBuilder
)时,SecurityManager
可以抛出安全异常。
System.setSecurityManager(new SecurityManager() {
@Override
public void checkExec(String cmd) {
throw new SecurityException("Executing external commands is not allowed during deserialization");
}
});
- Java安全策略(Policy):通过配置Java安全策略文件(如
java.policy
),可以进一步限制反序列化代码的权限。例如,只授予特定代码源反序列化某些类的权限。在策略文件中可以这样配置:
grant codeBase "file:/path/to/your/secure/code/-" {
permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect";
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
};
- 使用Java安全提供者(Security Provider):一些安全提供者提供了额外的加密和验证功能,可以用于加强反序列化数据的安全性。例如,Bouncy Castle安全提供者可以用于更复杂的校验和计算、数字签名验证等,以确保反序列化数据的完整性和来源可靠性。
总结
设计通用的Java反序列化防御框架需要综合运用输入流验证、黑名单与白名单机制,并紧密结合现有的Java安全体系。通过这些措施,可以有效地抵御各种Java反序列化攻击。