이 포스트는 Everything old is new again: binary security of webassembly 논문의 내용을 정리한 내용이다. 더 자세한 내용을 알고 싶으면 위의 논문을 참고하길 바란다.
managed memory
: managed memory
는 VM에 의해 직접적으로 관리되는 storage로, local variables, global variables, 그리고 return addresses가 저장되어 VM에 의해 관리된다.
unmanaged memory
: 이는 linear memory
라고도 불리우며, wasm
파일과 JS
파일에서 읽고 쓰이는 데이터가 연속적으로 저장되는 버퍼이다. 또한, string
, array
, list
와 같은 non-scalar
데이터들은 unmanaged memory
에 저장된다. unmanaged memory
는 offset
이 정해져있므며, memory.grow
instruction에 의해서 offset
이 늘어나 동적으로 memory를 확장할 수 있다. WebAssembly
는 C나 C++와는 다르게, 메모리 전체에 접근할 수 있는 것이 아니라 현재 알려진 offset
을 통하여 현재 할당된 메모리 영역에만 접근할 수 있다.
위의 이미지를 보면, WebAssembly
파일에는 DEP
나 SSP
와 같은 일반적인 mitigation들이 적용되어 있지 않다. 따라서, modern compiler에 의해서 C/C++에서는 이제 거의 수행할 수 없는 buffer overflow
나 stack overflow
와 같은 공격들을 WebAssembly
에서는 수행할 수 있다. 다만, return address
는 managed memory
에 저장되어 VM에 의해 직접적으로 관리되기 때문에, ROP
와 같은 공격들은 수행할 수 없고, unmanaged memory
에 대하여 overflow류 공격들을 수행하여 data corruption
을 발생시켜서 indirect call의 redirection을 발생시킨다던지, eval()
, exec()
과 같은 위험한 API 함수에 들어가는 argument의 내용을 변경시키는 등의 공격을 수행할 수 있다. 더 자세한 예시는 밑에서 설명하도록 하겠다.
위 공격은 CVE-2018-14550
취약점이 존재하는 libpng
C 라이브러리를 이용해 공격을 수행하는 예시이다. libpng
라이브러리는 PNM 파일을 PNG 파일로 변경시켜주는 라이브러리인데, 이를 수행할 때 buffer overflow
가 발생하여 exploit할 수 있는 CVE-2018-144550
취약점이 존재한다. 하지만, modern compiler 들에서는 stack canary
를 제공하여 이러한 취약점을 방어할 수 있는데, WebAssembly
파일은 stack canary
가 제공되지 않아 이러한 취약점을 통해 공격을 수행할 수 있다. 만약, buffer overflow
를 통하여 위의 C++ 코드의 <img>
태그 부분을 <script>
태그로 바꿔서 이를 실행시킬 수 있다면, XSS
공격을 수행할 수 있을 것이다. 이는 WebAssembly
에서 string literal
은 unmanaged memory
에 저장된다는 사실과 여기에는 stack canary
가 적용되어 있지 않다는 사실을 이용한 공격이다.
이 공격의 이해를 위해서는, 이전 포스트를 참고하면 좋을 것 같다.
간단히 설명하자면, WebAssembly
에서 indirect call
을 수행할 때, call_indirect
instruction을 수행하는데, 이 instruction은 unmanaged stack
의 value를 pop하여 이 값을 table section
의 index로 활용한다. 그리고, 그 index에 해당하는 함수를 불러올 때 type-correctness를 보장하기 위하여 VM은 정적으로 type-checking
을 수행하는데, WebAssembly
에는 i32
, i64
, f32
, f64
의 4가지 primitive type만이 존재하여 redirection 공격의 폭을 넓힐 수 있다.
위 사진은 이러한 사실을 이용하여 공격을 수행할 수 있는 코드의 예시인데, C++ 코드에서는 log_happy
, log_unhappy
함수의 signature와 exec
함수의 signature가 다른데, WebAssembly
에서는 이러한 세 함수의 함수 signature가 모두 같다. 따라서, log_happy
나 log_unhappy
함수에 대한 indirect call
이 수행되기 전에, call_indirect
instruction 실행 전에 unmanaged stack
의 맨 위 값을 table section
에서 exec
함수의 포인터가 저장되어 있는 index
로 바꾸면, 정적 type-checking
을 통과하여 정상적으로 실행된다.
마지막 공격은 unmanaged memory
에 저장되는 string literal
을 변경하여 수행할 수 있는 공격인데, 위 C++ 코드에서는 fopen
의 인자들을 constant string 값으로 주고 있는데, C/C++를 컴파일하면 이러한 string literal
을 .rodata
섹션에 저장되어 이를 바꿀 수 없다. 하지만, WebAssembly
에서는 이러한 값이 unmanaged memory
에 저장되어 이러한 값을 바꿀 수 있다. 따라서, "file.txt"
값을 변경하여 fopen
을 통해 여는 파일을 변경할 수 있고, "a"
를 변경하여 mode를 변경할 수도 있다. 또한, fprintf
의 인자로 넘겨지는 "Append constant text."
를 변경하여 파일에 쓰려고 하는 string을 변경할 수도 있을 것이다.
WebAssembly
에는 DEP
, SSP
와 같은 일반적인 방어 기법이 적용되지 않고, primitive type의 개수도 제한적으로 존재하여 binary security
에 치명적인 결함이 존재한다. 그리고, 이러한 사실을 이용하여 공격을 수행하는 예시들을 위에서 설명하였다.