USENIX Security 2012에서 발표되었던 Fuzzing with Code Fragments을 살펴보자.

Christian Holler, Mozilla Corp
Christian Holler, Mozilla Corp

저자 Christian Holler는 Firefox 브라우저로 유명한 Mozilla Corporation의 보안 담당자이다. 그의 주특기는 Grammar-based fuzzing이다. 즉, Javascript인터프리터를 테스트할 때 프로그램의 문법구문(Syntax)을 만족시키는 동시에 정의되지 않은 행위(undefined behavior)를 유발하는 인풋 데이터를 찾아내는 일을 한다. 그는 2011년에 "Grammar-Based Interpreter Fuzz Testing" 이라는 논문으로 독일의 Saarland University에서 석사학위를 받았다. 해당 내용은 '인터프리터'라고 되어있지만 사실상 Javascript 엔진을 타게팅하여 작성되었고, 구체적으로 파이어폭스나 구글 크롬같은 웹 브라우저의 엔진을 대상으로 퍼즈테스팅을 수행하여 취약점을 찾아내었다. 그는 Firefox에서만 105개의 보안 취약점을 찾아내었으며 버그바운티에서 포상금 $53,000(약 6천만원)을 획득하였다. 이러한 공로로 그는 Firefox에 입사하게 된다.


그는 세계적인 보안 컨퍼런스인 USENIX Security 2012에서 그의 학위논문을 요약한 "Fuzzing with Code Fragments"을 발표하였다. 해당 프로젝트의 이름을 일명 LangFuzz라고 명명하였다.

해당 논문을 대략적으로 요약해보자.


Abstract 

Fuzz 테스팅은 취약점을 노출시키기 위해 소프트웨어 시스템에 대해 랜덤한 데이터 input을 자동으로 주입하는 기법이다. 이 기법이 효과적으로 작동하기 위해서는, consistency check에 통과할수 있을 정도의 무난한(common) 인풋 데이터가 주어져야 한다. 예를들어, 자바스크립트의 인터프리터는 프로그램의 semantic 유효성을 검증하여 허용여부를 결정한다. 그러나 예외적인 결과(인터프리터 crash 등)를 유발시키기 위해서는 필연적으로 uncommon한 fuzzed input을 사용할 수밖에 없다. 이러한 모순적인 상황에 대한 해결책으로 제시한 LangFuzz는, 프로그램의 문법 검증은 만족시키면서 동시에 invalid behavior를 유발하는 code fragments를 얻게 해준다. 따라서 LangFuzz는 보안테스팅에서 유용할 것이다. 실제로 모질라의 JavaScript interpreter에 적용하여 운영해보니 3개월만에 105개의 새로운 취약점이 발견되었다.(버그 바운티 top ranker의 반열에 듬) PHP interpreter에도 적용해보니 crash 를 유발하는 18개의 새로운 결함을 찾을 수 있었다.


본문 내용 요약

소프트웨어에 있어서 보안 문제는 매우 위협적이고, 이를 대응하기 위한 비용도 상당히 많이 소요된다. 2008년 CSI의 연례 보고서에 따르면, Computer 이용 범죄 및 보안에 관한 조사에서 평균 289,000 달러의 손실이 수반된다고 판시하였다. 이를 막기위해 취하는 조치가 바로 ‘보안 테스팅’이다. 소프트웨어 취약점을 조기에 발견하기 위해 사용하는 여러가지 기술이 있으며, 퍼징(Fuzzing)도 그 중 하나다. 임의의 데이터 입력값을 자동으로 생성하여 충돌 또는 예기치 않은 동작을 발생시킴으로써 잠재적인 소프트웨어 취약점을 찾아내는 기법이다. 웹 브라우저에서, 자바스크립트 인터프리터에 보안 문제가 자주 발생한다. 예를들어 모질라 파이어폭스의 보안패치 대부분은 이와 관련된 것이다. 때문에 자바스크립트 인터프리터에 대한 퍼즈 테스팅을 수행하려는 많은 시도가 있었다. 문제는, 해당 인터프리터는 자바스크립트 언어에 대한 Syntactic 체크를 수행하므로 퍼징 입력값들이 그 문법기준을 준수해야 한다는 것이다. 만약 충족되지 않으면 인터프리터가 해당 입력을 lexical / syntactic 분석만으로 기각시키기 때문에 code transformation, in-time compilation, actual execution에 도달할수가 없다.


이 문제를 해결하기 위해서 퍼징 프레임워크에 원하는 데이터에 대한 구조를 모델링하는 방법을 사용한다. 자바스크립트 인터프리터의 경우, built-in 자바스크립트 문법을 이용하여 퍼즈 테스팅을 한다. 이 방식을 사용하는 퍼징 연구는 비교적 적지만 대표적으로 다음과 같은 것들이 있었다.

  • Godefroid, Patrice, Adam Kiezun, and Michael Y. Levin. "Grammar-based whitebox fuzzing." ACM Sigplan Notices. Vol. 43. No. 6. ACM, 2008.
  • Ruderman, Jesse. "Introducing jsfunfuzz." URL http://www. squarefree. com/2007/08/02/introducing-jsfunfuzz (2007).
  • Yang, Xuejun, et al. "Finding and understanding bugs in C compilers." ACM SIGPLAN Notices. Vol. 46. No. 6. ACM, 2011.

(참고 : [논문리뷰] jsfunfuzz 소개)

그중 Javascript에 초점을 맞추자면, 가장 인기 있는 방법은 jsfunfuzz이며, 실제로 이를 사용하여 Mozilla JavaScript 엔진에서 천개 이상의 취약점을 발견하였다. jsfunfuzz는 관련된 과거의 취약점 등에 대한 기반지식을 활용하여 하드코딩으로 특정 인터프리터에 대해 적용할 수 있기 때문에 효과적이다. 하지만 한가지 생각해야할 문제가 있다. 각 프로젝트별 배경지식을 활용하는 것에 상관없이 포괄적으로 퍼지 테스트를 적용할 수 있는 일반적인(generic) 해법은 없을까?


본 논문에서는 LangFuzz라는 기법을 제안한다. 이는 context-free한 문법에 기반한 black box 퍼지 테스트를 한다. 특히, 특정한 테스트 타겟에 한정되지 않고, Java Script 문법이 주어지면 그에 응당하는, PHP 문법이면 PHP 등, 해당 Target에 대해 적합하게 수행하기 위해 LangFuzz는 주어진 코드를 통해 code fragments를 학습함으로써 문법을 스스로 터득하도록 한다. 또한, 새 프로그램을 생성할 때 Random한 input을 사용하기보다는, 이전에 문제를 유발했던 전례가 있었던 입력값들은 추후에도 버그를 일으킬 가능성이 높다고 가정하에 해당 코드 fragments들을 재조합하여 적절한 테스트 셋을 생성한다. 



LangFuzz 작동원리

프로그래밍 언어의 문법을 사용한다. 먼저, 샘플 코드로부터 code fragments를 얻고, test suite로부터 test cases를 얻고, 후에 테스트 케이스를 이리저리 바꿔가면서 이러한 조각들을 재조합해본다. 결과 코드는 실행시 interpreter를 통과한다.
프로그래밍 언어의 문법을 사용한다. 먼저, 샘플 코드로부터 code fragments를 얻고, test suite로부터 test cases를 얻고, 후에 테스트 케이스를 이리저리 바꿔가면서 이러한 조각들을 재조합해본다. 결과 코드는 실행시 interpreter를 통과한다.

위의 그림은 LangFuzz의 전체적인 작동원리를 도식화하였다. 인풋으로는 코드를 이해하기 위한 프로그래밍 언어 문법에 대한 정보, 샘플 코드, code mutation에 사용할 test suite 이렇게 3개가 필요하다. 이때 테스트 케이스들은 대부분 과거 버그를 유발시켰던 전례가 있는 code fragments를 포함한다. test suite는 mutation basis 뿐만 아니라 sample code로도 활용 가능하다. LangFuzz는 이상의 정보들을 토대로 Code Mutation과 Code Generation 을 수행하여 계속해서 새로운 테스트 케이스들을 만들고, 인터프리터에 넣는다.

(*구체적인 구현에 관련한 내용은 논문의 Part3. How LangFuzz works 와 Part4. The LangFuzz Implementation을 참고할 것. 단, 소스코드는 github등에 공개되어있지 않은 private한 프로그램임.)

실제로 LangFuzz에 의해 생성된 테스트 케이스. 8번 라인을 실행할 때 인터프리터에서 crash가 발생한다.(Reported as Mozilla bug 610223)
실제로 LangFuzz에 의해 생성된 테스트 케이스. 8번 라인을 실행할 때 인터프리터에서 crash가 발생한다.(Reported as Mozilla bug 610223)

위의 코드를 보자. RegExp. $1(Line 8)은 정규식에 매치된 리스트의 첫번째 원소에 대한 포인터이다. 이들이 적재되어 있는 메모리 영역은 새로운 input(Line 7)에 의해 수정될 수 있다. 공격자는 해당 포인터를 이용하여 임의로 메모리 내용에 접근할 수도 있다. 테스트 케이스의 Line 7과 8은 LangFuzz에 의해 새롭게 생성된 것이고, 1~6은 기존의 테스트 케이스에 존재하던 나머지 코드조각이다. 이를가지고 실행 테스트하면, 실제 crash가 발생하였고, 이는 결국 모질라 Javascript 엔진에서 보안 취약점을 발견하게 된 것이다. 해당 버그는 610223로 리포트되어 처리되었다.


LangFuzz는 프로그래밍 언어 문법과 project-specific한 이슈와 관련한 코드 코각을 재사용하는 두가지 방법을 앙상블함으로써, 보안테스팅에서 매우 유용하게 사용할 수 있는 도구가 될 것이다. 모질라의 javascript engine에서 이를 사용해서 3달만에 103개 취약점을 찾았다. 대부분 메모리 관련 문제로 인한 이 버그들은 실제로 매우 심각하였으며 버그 바운티에서 50달러의 가치를 지닌 것들이었다. 



성능검증 및 평가

그렇다면 이와 비슷한 기능을 하는 다른 프로그램과 비교했을 때 얼마나 월등한 성능일까? 현재 State of the Art(최신 동향)으로 평가받는 jsfunfuzz와 비교해보았더니 위와 같은 성과를 보였다.



발전된 주제로의 확장

또한, Javascript가 아닌 다른 언어를 테스트하기 위해 PHP에 적용해 보았다. 생성 된 모든 입력은 semantically 정확하며 각각의 인터프리터에 의해 실행될 수 있는 형태였고, 이를 통해 18개의 취약점을 찾을 수 있었다. 확인된 결과에 따르면, 동적이면서 약한 타입형(weakly typed) 언어에서 더 좋은 성능을 나타내는 것으로 보인다. 하지만 아직 다른 언어에 대한 연구가 더 필요한 실정이며, 이를 Further works로 남겨둔다.


결론

Fuzz 테스팅은 유용하지만 관련한 배경지식과 해당 프로그래밍 언어에 대한 특유의 요소들을 인지하고 있어야만 원활한 활용이 가능하다는 한계가 있다. LangFuzz는 임의의 프로그래밍 언어에 대해 그에 알맞은 문법을 적용하고, 새로운 프로젝트에서 적절한 테스트 셋을 제공함으로써 mutating과 extending이 쉽도록 도와주는 접근 방법이다. 평가 결과 소프트웨어의 보안 취약점을 효과적으로 찾아낼 수 있다는 것이 확인되었으며, 실제 버그바운티 등에서도 매우 월등한 성적을 거두었다. 이는 경제적으로도 충분한 가치가 있는 유의미한 결과이다. 특히, 본 논문에서 제시한 방식은 복잡한 입력(사용자 정의 등)을 다루는 프로세서(컴파일러/인터프리터)를 자동적으로 테스팅하는 매우 효과적이면서도 간단한 기법이다.


참고 :

https://www.usenix.org/conference/usenixsecurity12/technical-sessions/presentation/holler

https://issta2016.cispa.saarland/interview-with-christian-holler/

https://issta2016.cispa.saarland/wp-content/uploads/2016/07/ISSTA16-holler.pdf

CPUU님의 창작활동을 응원하고 싶으세요?