Fuzzing으로 Apache에서 Race Condition 찾기

Fuzzing
자동화 혹은 반자동화된 소프트웨어 테스트 기법이다. 컴퓨터 프로그램에 무작위의 데이터를 입력하고, 그 결과에 대해 모니터링 함으로써 버그를 찾는다.
Mutation-based: 샘플 입력을 변형하여 테스트 데이터를 만든다. 비트 플립이나 비트를 추가하는 등의 기법을 이용한다.
Generation-based: 파일의 형식이나 프로토콜을 이해하고 그것에 맞추어 적절한 입력을 생성한다.


Fuzzotron
TCP/UDP 기반의 네트워크 퍼징 툴이다.
radamsa라는 툴이 mutation-based 테스트 데이터를 만들어준다.


Thread Sanitizer
C/C++ 기반 data race를 찾아주는 도구이다.
Constant global에 대한 read operation이나 현재의 쓰레드를 벗어나지 않는 메모리 접근인 경우, 혹은 두 쓰레드가 접근하는데 하나가 read이고 다른 하나가 write라도 read가 항상 먼저 일어나는 경우를 제외하고의 모든 memory access가 기록된다.


실험 디자인
Apache 2.4.7의 소스코드를 받아서
CFLAGS="-fsanitize=thread -pthread -fPIC -g" ./configure --enable-threads --with-mpm=worker --enable-mods-shared=reallyall --with-included-apr --prefix=/LOCATION/
의 옵션으로 configure한다. --with-mpm=worker 옵션으로 한 자식 프로세스당 여러 thread를 가질 수 있도록 하고, thread sanitizer를 붙인다.
그 뒤 prefix위치에 빌드된 디렉토리에서 /conf/httpd.conf에서

SetHandler server-status
ExtendedStatus On

을 추가해준다. 이 설정은 서버상태의 모니터링을할 때 자세한 상태정보기능을 제공하도록 해준다.
그리고 /conf/extra/httpd-mpm.conf에서 worker-module의 thread 수를 적절히 조절한다.
이 세팅이 끝나면 /bin/ 에서

export TSAN_OPTIONS="io_sync=2 log_path=/tmp/tsan_log"
./httpd -DFOREGROUND
unset TSAN_OPTIONS

으로 Apache 서버를 실행한다. 이 상태가 되면 Tsan이 Apache에서 실행되는 memory access를 log_path에 기록하게 된다.
이제 fuzzing을 해야한다. input으로는 적절한 http request들을 넣어놓는다.

ERR_LOG_PATH=/tmp/tsan_log
rm -rf ./razzer
./fuzzotron --radamsa --directory input/ -h 127.0.0.1 -p 8080 -P tcp -o output -t 1 -e $ERR_LOG_PATH

로 fuzzing을 한다. 그러면 fuzzotron이 주어진 input을 변형하여 Apache 서버로 request를 계속 보낸다. 이러한 무작위의 request들에 대해서 Tsan이 data race를 기록하게 된다.


설명
Race bug란 같은 리소스에 2개 이상의 쓰레드가 접근하고 그중 쓰기 명령이 있어서 읽는 쓰레드가 예상하지 못한 값을 읽는 경우 생깁니다.
기본적으로 멀티 프로세스 싱글 쓰레드 모델을 사용하던 아파치는 사용자가 급격히 증가하면서 더 많은 요청을 더 빨리 처리할 수 있는 멀티쓰레드 모델을 도입했습니다.
아파치의 모듈 중 prefork말고 worker라는 모듈이 프로세스당 멀티쓰레드를 지원하는 모듈인데 이렇게 worker 모듈을 사용하게 되면 race bug가 생길 위험이 있습니다.
Race bug는 단순히 원치 않는 무작위의 결과를 내는 것 뿐만 아니라 공격자가 악의적으로 이용할 경우 권한 탈취 및 정보 유출 등의 심각한 결과를 초래할 수 있습니다.

CVE-2014-0226으로 알려진 아파치 서버의 race bug는 공격자에게 중요한 정보를 유출시킬 수 있는 버그입니다.
이 버그에 대해 간략히 설명드리겠습니다.
한 문자열 request에 대해 쓰레드1과 쓰레드2가 접근하는 상황이고 그 문자열이 비어있을 때를 가정합니다.
쓰레드1에서 그 문자열을 읽어서 사용자에게 return해야 합니다.
그 과정을 자세히 살펴보면 request에 저장된 문자열의 바이트 수를 먼저 센 뒤 그만큼 메모리를 할당하고 그 메모리에 request의 문자열을 복사하는 과정이 일어납니다.
빈문자열일 경우 null 문자인 \0만 복사해야 하므로 메모리 크기를 계산할 때 1바이트로 계산이 되고 return할 메모리에 1바이트가 할당됩니다.
그 다음에 복사가 일어나야 되는데 그 직전에 쓰레드2에서 request에 쓰기 명령이 일어납니다.
예를 들어 "GET"이라고 쓰기가 일어난다면 쓰레드1에서 정상적인 작동에서는 빈문자열의 맨 처음에 null 문자가 있기 때문에 null문자를 복사해서 리턴해야 했지만 지금은 맨 처음 1바이트가 G이므로 리턴하는 값은 null 문자가 아닌 G가 됩니다.
이 리턴 값을 받아서 다른 함수에서 그 리턴된 문자열을 복사해서 사용자에게 보내주는 곳이 있습니다.
거기서 함수는 \0가 있는 지점까지 복사해서 리턴하는데 이때 정상적으로 \0가 있는 문자열이 아니므로 계속해서 메모리를 읽어가며 복사합니다.
이 메모리에 중요한 정보가 있을 수 있고 이 때문에 중요한 정보 유출이 일어납니다.

하지만 race bug는 항상 일어나는 것이 아니고 특정한 상황에서 특정한 순서로 명령이 일어났을 때만 발생하기에 발견하고 고치기가 어렵습니다.
이에 fuzzing이라는 자동화 테스트 방법을 이용해서 race bug를 찾는 연구를 했습니다.
Fuzzing이란 많은 데이터를 임의로 보내서 각 입력 데이터에 대해 추적해가며 프로그램을 분석하는 방법입니다.
이 연구에서는 수 많은 다양한 요청들을 서버로 보내면서 각 요청에 대해 추적하며 race bug가 일어나는 지점을 기록하는 것을 목표로 했습니다.
알려진 race bug가 있던 2.4.7버전의 소스코드를 받아서 멀티쓰레드 환경에서 동작하도록 빌드하고 실행시켰습니다.
Fuzzing을 위해서 기본적인 샘플 입력을 몇 개 만들어 놓고 radamsa라는 툴을 이용하여 샘플 입력을 기반으로 무수히 많은 변형된 입력을 만들었습니다.
그 입력들을 fuzzotron이라는 fuzzing 툴로 아파치 서버에 전송을 했습니다.
각 입력들에 대해 아파치 서버가 그 요청을 처리할텐데 그 처리할 때 thread sanitizer라는 툴을 이용해서 각 쓰레드가 접근하는 모든 주소를 기록하여 같은 주소에 접근하면 기록하도록 합니다.
이 과정을 통해서 CVE-2014-0226을 포함하여 race bug를 찾을 수 있었습니다.




댓글

이 블로그의 인기 게시물

논문 정리 - MapReduce: Simplified Data Processing on Large Clusters

논문 정리 - The Google File System

kazoo: Using zookeeper api with python