개요

많은 사람들이 퍼징(Fuzzing)이라는 단어를 들어는 보셨겠지만, 정확히 이게 무엇인지 궁금해하셨을 겁니다. 혹시 여러분은 특정 파라미터 변수를 적절히 수정하기 위해 고민해보신 적이 있나요? 입력 값을 argument로 전달할 때 보통의 적절한 값으로 전달하는 그런 작업 말입니다. 만약 이런 경험이 있으시다면, 여러분도 모르는 사이에 이미 퍼징을 접해보신 것이라 할 수 있습니다.


IEEE에서 발간하는 표준 소프트웨어공학 전문용어 사전에 따르면 퍼징은 다음과 같이 정의됩니다. 

"부정확한 입력값 또는 부하가 심한 환경적 조건에서 시스템 또는 컴포넌트가 올바르게 작동할 수 있는 정도"

“The degree to which a system or component can function correctly in the presence of invalid inputs or stressful environmental conditions.”


퍼징(또는 퍼즈 테스팅)은 기본적으로 소프트웨어 테스팅상에서의 다양한 문제를 점검하기 위한 기술입니다. 예를들어 코딩 오류, XSS 같은 보안 취약점, 버퍼 오버 플로우, 서비스 거부(DoS) 등을 유발하기 위한 악의적인 용도로, 예상치 못한 무작위 데이터를 해당 프로그램의 입력값(input)으로 주입하는 것이며 이를 통해 실제적으로 프로그램의 작동을 중단(Crash)시키거나 예상치 못한 행동을 나타나게 합니다.


퍼징의 기원

퍼징은 1988년 위스콘신 대학교의 Barton Miller 교수에 의해 처음 시도된 것으로 알려져 있습니다. 그는 “운영체제의 유틸리티 프로그램 안전성 - Fuzz 생성기” 라는 프로젝트를 진행한 적이 있습니다. 이 곳에서 처음으로 “Fuzz”라는 용어가 사용되었으며, UNIX에서 “fuzzer”라는 명령어를 입력함으로써 특정 프로그램에 무작위한 bit의 stream을 전달하도록 하였습니다. 


누군가가 직접 Miller교수에게 “fuzz라는 단어를 어디에서 착안하여 사용하게 되셨나요?”라고 묻자 Miller는 다음과 같이 이메일로 회신해주었습니다.


“The original work was inspired by being logged on to a modem during a storm with lots of line noise. And the line noise was generating junk characters that seemingly were causing programs to crash. The noise suggested the term ‘fuzz’”.


폭풍우가 몰아치던 날, 모뎀을 통해 네트워크 작업을 하고 있었습니다. 비바람 때문인지 회선에서 불필요한 잡음이 섞여서 들려왔습니다. 그러던 중 제가 사용하던 프로그램에서 쓸데없는 문자열들이 의도치 않게 삽입되었고, 결국 오류가 발생하여 종료가 되는 현상을 겪게 되었습니다. 그때 들려오던 ‘파지지직’하던 잡음 소리에서 영감을 받아서, fuzz라고 이름을 붙였습니다.


퍼징을 하는 이유

어떤 제품에 대하여 테스트 케이스가 정의되었다면, 이는 곧 ‘어떻게 작동하도록 설계되어있는지’, 또는 ‘어떻게하면 안되는지’에 대한 명세가 정해졌다는 뜻입니다. 그러나 이러한 기준이 있다하더라도, 프로그래머 또는 테스터는 사람이므로 생각해보지 못한 초월 영역이 존재할 수 있습니다. 이러한 빈틈을 메우는 것이 바로 퍼징이며, 그러한 정의되지 않은 영역을 테스트하는 일종의 탐험가 역할을 합니다. 퍼징(Fuzzing)의 주요 목적은 제품/프로그램의 ‘올바른’기능을 테스트하는 것이 아니라, ‘정의되지 않은’ 영역을 검증하고 확인하는 작업입니다.


퍼징기법의 분류

퍼징을 분류할 때 그 공격 기법이나 방식, 타겟 등등에 따라 다양한 종류로 구분할 수 있습니다. 타겟의 경우만 해도 파일 포맷, 네트워크 프로토콜, 커맨드라인 파라미터, 환경변수, 웹 어플리케이션 등등 굉장히 다양합니다. 이러한 다양한 분류 중에서 가장 기본이 되는 방법은, 테스트 케이스를 어떻게 생성하는지에 따라 분류하는 것입니다. 즉, 퍼징을 위한 input data를 어떻게 조절하는지에 따라 크게 두가지 방식인 Mutation, Generation으로 나뉘어집니다.


Mutation vs. Generation (Dumb vs. Intelligent)

Mutation (Dumb Fuzzers): 뮤테이션 기반 퍼징(Dumb Fuzzer)

먼저 설명드릴 방식은, Mutation(변이) 기법으로, 주어진 입력 값을 마구잡이식으로 이리저리 바꿔가면서 새로운 입력값을 만들어내는 것입니다. 이러한 방식은 데이터의 형식이나 구조에 대한 명확한 이해가 어려울 때 사용하는것이다보니, 때로는 너무나 노가다 같은 느낌이 강해서 ‘dumb(멍청한) 퍼징'이라는 이름으로 불리기도 합니다. 아래의 예를 보시면, 데이터의 특정 영역의 값들을 임의로 바꿔치기(replacing)하거나, 추가(appending)하는 것을 보실 수 있습니다.


Mutation기법 중 하나인 Bit Flipping은 특정 비트를 일정한 주기 또는 무작위로 바꿔버리는 것입니다.


Mutation 기반의 대표적인 예로는 ZZUF가 있으며, 비트 플로핑을 사용하여 아래와 같이 fuzz.txt 라는 입력 파일의 내용을 변조합니다.


Generation (Intelligent Fuzzer):

Dumb Fuzzer와는 대조적으로, 파일의 포맷(형식)이나 프로토콜(규약)을 이해하고 그것에 맞추어 적절한 input을 생성하는 방식이 바로 Generation 기반 퍼징이며, 명세에 따라 밑바닥부터 철저히 구현해야 하므로 때로는 Intelligent(지능적인) Fuzzer라고 불리기도 합니다.


Peach Fuzzer를 예를 들어보겠습니다. 이는 대표적인 Smart 퍼저이며, 조금 더 정확하게는 Mutation 방식과 Generation 방식을 둘다 사용할 수 있습니다. 우선 이를 사용하기 위해서는 해당 데이터를 정확하게 이해하기 위해 데이터구조, 타입 등의 정의된 XML 파일을 일명 PeachPit 형태로 작성하여 참고할 수 있도록 해야 합니다.


Peach는 해당 파일을 통해 모델링을 수행하고, 기타 여러 동작을 수행하게 됩니다. 아래는 HTTP 프로토콜을 위한 PeachPIT 파일의 예시입니다. peachpit에는 최소 한개 이상의 data model이 포함되어 있고, 이를 통해 새로운 다양한 데이터를 새롭게 생성하여 퍼징을 수행합니다.


비교 :

뮤테이션 방식이 제너레이션 방식에 비해 쉽습니다. 왜냐하면 프로토콜을 굳이 이해할 필요가 없기 때문입니다. 그러나 유효한 입력값의 조합을 잘 구성하고, 코드 커버리지 측면에서 보면 제너레이션 방식이 실제적으로 더 유용하다고 볼 수 있습니다. 물론 철두철미하게 하기 위해서는 많은 시간이 소요될수 있다는 단점이 있습니다. 즉, 어떤 방식이 더 우월하다기보다는, 각 방식에 장단점이 있으므로 상황에 따라 적절한 것을 택해야 할 것입니다.


퍼징의 장점 :

퍼징은 테스팅을 무작위로 수행하는 기법입니다. 즉, 정의된 것만 테스트하는 접근방식으로는 해결할 수 없는 지점을 공략하여 버그를 찾아내는 것입니다. 이러한 이슈들을 발견하는 것은 매우 가치있는 작업이며 이를 수행하기 전에 별도로 고려해야할 점도 적은데도 불구하고 상당한 성과를 얻을 수 있습니다. 굉장히 쉽고 빠르게 수행할 수 있으며 해당 작업을 여러번 반복하는 것도 가능하다는 것이 큰 장점입니다.


퍼징의 한계 :

퍼징이 매우 효과적인 버그탐지법임에도 불구하고, 그 한계 또한 존재합니다. 이는 대부분 블랙박스 테스팅을 할 때 유용합니다. 또한 퍼징을 위해 해당 프로토콜의 스펙이나 파일의 포맷을 파악하는 것은 쉽지않은 작업이 될 수 있고, 랜덤으로 작업하는 것 역시 경계값을 탐색하기가 어렵다는 단점이 있습니다.


참고 : http://resources.infosecinstitute.com/fuzzing-mutation-vs-generation/

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