버퍼 오버플로
버퍼 오버플로(buffer overflow)란 메모리를 다루는 데에 오류가 발생하여 잘못된 동작을 하는 프로그램 취약점이다. 컴퓨터 보안과 프로그래밍에서는 프로세스가 데이터를 버퍼에 저장할 때 프로그래머가 지정한 곳 바깥에 저장하는 것을 의미한다. 버퍼 오버런(buffer overrun)이라고도 한다.
개요
벗어난 데이터는 인접 메모리를 덮어쓰게 되며 이때 다른 데이터가 포함되어 있을 수도 있는데, 손상을 받을 수 있는 데이터는 프로그램 변수와 프로그램 흐름 제어 데이터도 포함된다. 이로 인해 잘못된 프로그램 거동이 나타날 수 있으며, 메모리 접근 오류, 잘못된 결과, 프로그램 종료, 또는 시스템 보안 누설이 발생할 수 있다. 버퍼 오버플로가 코드를 실행시키도록 설계되거나 프로그램 작동을 변경시키도록 설계된 입력에 의해 촉발될 수 있다. 따라서 이는 많은 소프트웨어 취약점의 근간이 되며 악의적으로 이용될 수 있다. 경계 검사로 버퍼 오버플로를 방지할 수 있다. 버퍼 오버플로는 보통 데이터를 저장하는 과정에서 그 데이터를 저장할 메모리 위치가 유효한지를 검사하지 않아 발생한다. 이러한 경우 데이터가 담긴 위치 근처에 있는 값이 손상되고 그 손상이 프로그램 실행에 영향을 미칠 수도 있다. 특히 악의적인 공격으로 인해 프로그램에 취약점이 발생할 수도 있다. 흔히 버퍼 오버플로와 관련되는 프로그래밍 언어는 C와 C++로, 어떤 영역의 메모리에서도 내장된 데이터 접근 또는 덮어쓰기 보호 기능을 제공하지 않으며 어떤 배열에 기록되는 데이터가 그 배열의 범위 안에 포함되는지 자동으로 검사하지 않는다.[1]
역사
버퍼 오버플로는 1972년 컴퓨터 보안 기술 기획 연구에서 기법을 소개했을 때부터 이해됐던 개념이다. "이 기능을 수행하는 코드는 출처와 목적지 주소를 적절히 검사하지 않음으로, 사용자가 모니터의 일부를 덮어쓰도록 사용해 코드를 모니터에 삽입하여 제어권을 획득하는 데 사용될 수 있다."라고 설명했다. 1980년대 개인용 컴퓨터의 확산으로 이 기법에 대해 아는 사람의 수가 증가했다. 초기 개인용 컴퓨터들은 버퍼 보호에 부적절했기 때문에 많은 프로그래머가 버퍼 오버플로에 대해 알게 되는 계기가 되었다. 1988년 최초로 문서에 남은 적대적 오버플로가 사용되었다. 모리스 웜(Morris Worm)이 인터넷에서 자신을 알리기 위해 사용했던 몇 가지 공격 중 하나로 핑거(finger)라는 유닉스 서버를 사용했다.
1995년에 토마스 로파틱이 독립적으로 버퍼 오버플로를 재발견하였고 자신의 발견을 버그트랙(Bugtraq) 보안 메일링 리스트에 공개하였다. 1996년 엘리아스 레비는 프랙지에 "재미와 소득을 위해 스택 때리기"라는 제목의 기사에서 스택 기반 버퍼 오버플로 취약점을 공략하는 단계별 소개를 공개하였다. 이후 최소한 두 가지의 대규모 인터넷 웜이 버퍼 오버플로를 이용하여 많은 수의 시스템에 영향을 주었다. 2001년 코드 레드 윔은 마이크로소프트의 인터넷 정보 서비스 5.0의 버퍼 오버플로를 이용했고, 2003년 SQL 슬래머 웜은 마이크로소프트 SQL 서버 2000을 실행시키는 컴퓨터에 영향을 주었다. 2003년 허가된 엑스박스 게임 안에 존재했던 버퍼 오버플로가 이용되어 자작 게임을 포함한 허가 받지 않은 소프트웨어도 콘솔에서 모드칩이라고 알려진 하드웨어 변경 없이 이용할 수 있게 되었다.[1]
특징
공격 기법
- 스택 버퍼 오버플로(Stack Buffer Overflow)
- 스택 버퍼 오버플로는 주로 SetUID가 설정된(루트 권한으로 실행되는) 프로그램들을 타깃으로 하며 입력값에 수용 가능한 버퍼보다 큰 값을 입력하여 임의의 공격 코드를 루트 권한으로 실행시킨다. 스택 버퍼 오버플로는 프로그램에서 특정 함수를 실행시키면 스택에 이전 함수로 돌아가기 위한 주소가 기록되는데, 버퍼에 큰 값을 넣게 되면 이 스택 영역을 침범하게 된다. 리턴 주소가 들어갈 곳에 셸 코드나 다른 프로그램의 주소를 넣으면 실행된다.
- 힙 버퍼 오버플로(Heap Buffer Overflow)
- 힙 버퍼 오버플로란 힙 데이터 영역에서 일어나는 버퍼 오버플로이다. 스택 오버플로만큼 흔히 사용하는 공격은 아니고 동적 메모리 할당 연결(malloc 상위 수준 데이터)을 덮어씀으로써 프로그램 함수 포인터를 조작한다.[2]
- 리턴 투 라이브러리(Return to library)
- 리턴 투 라이브러리란 버퍼 오버플로 취약점이 존재하는 프로그램을 공격할 때 해당 프로그램의 스택 영역에 실행 권한이 존재하지 않아 프로그램이 사용하는 GNU C 라이브러리(glibc) 내에 존재하는 함수를 호출하는 기법이다.[3]
대안
- 카나리(canary)
- 카나리(canary) 또는 카나리스(Canaries)는 버퍼 오버플로를 감시하기 위해 스택의 버퍼와 제어 데이터 사이에 위치한 값들이다. 버퍼가 오버플로 하면 오염될 첫 데이터는 보통 카나리일 것이고, 카나리 값의 검증은 실패하여 오버플로에 대한 경고가 발생하고 처리될 수 있다. 카나리 기법은 공격의 전체 계층을 막을 수 있다는 장점을 제공한다. 몇몇 연구에 따르면 이 기법의 성능에 대한 영향은 무시해도 될 정도라고 한다. 카나리는 총 3가지 종류가 있다.[4]
- 터미네이터 카나리(Terminator Canary) : 터미네이터 카나리는 문자열 끝 문자를 이용하여 카나리를 구성하는 방법으로 카나리 값으로 널(NULL), CR, LF, EOF 가 있다. 공격자는 공격 시, 종료 문자로 구성된 카나리 값에 접근할 수 없게 된다.
- 랜덤 카나리(Random Canary) : 프로그램을 실행할 때마다 임의의 카나리 값을 삽입한다. 이에 따라 공격자는 카나리 값을 예측하지 못해 공격에 어려움이 발생한다.
- 랜덤 XOR 카나리 : 랜덤 XOR 카나리는 제어 데이터의 모든 또는 부분을 사용해서 XOR을 섞은 방식으로 카나리나 제어 데이터가 오염될 때까지 카나리 값이 달라져 공격자가 예측할 수 없게 한다.[4]
- 경계 검사
- 경계 검사는 할당된 메모리의 각 블록을 위한 런타임 경계 정보를 추가하고 런타임 시에 관련된 모든 포인터를 검사하는 컴파일러 기법이다. C와 C++에서 경계 검사는 포인터 계산 시간 또는 역참조 시간에 수행될 수 있다. 이 접근법의 구현은 메모리의 할당된 각 블록을 서술하는 중앙 저장소, 또는 포인터와 추가적엔 데이터를 모두 포함하는 팻 포인터를 사용한다.[4]
- 태깅
- 태깅은 메모리에서 데이터의 타입을 태그하기 위한 컴파일러 또는 하드웨어 기반 기법이다. 메모리의 특정한 영역을 실행 불가능으로 마킹함으로 데이터를 저장하기 위해 할당된 메모리가 실행 가능한 코드를 포함하는 것으로부터 효과적으로 보호할 수 있다. 더욱이 메모리의 특정한 영역들은 버퍼 오버플로를 방어하기 위해 할당 불가능하게 마킹할 수 있다. 역사적으로 태깅은 고 수준 프로그래밍 언어들을 구현하는 데 사용되어 왔다. 운영체제로부터의 적절한 지원하에, 태깅은 버퍼 오버플로를 탐지하는 용도로도 사용될 수 있다.
- 스택가드(Stackgurad)
- 스택가드란 카나리(canary)라고 불리는 무결성 체크용 값을 복귀 주소와 변수 사이에 삽입하여 스택에 변조가 발생했는지 확인한다. 버퍼 오버플로가 발생할 시 카나리 값이 변하게 되는데, 이 경우 복귀 주소를 호출하지 않는다. gcc 등 각종 컴파일러가 이 기법에 반영되어 업데이트되었다.
- 스택쉴드(Stack Shield)
- 스택쉴드란 함수 시작 시 복귀 주소를 글로벌 RET(Global RET)라는 특수 스택에 저장해 함수 종료 시 저장된 값과 스택의 RET 값을 비교해 값이 다를 경우 오버플로로 간주하고 프로그램을 실행을 중단한다.
- 주소 공간 레이아웃 무작위화(Address Space Layout Randomization, ASLR)
- 주소 공간 레이아웃 무작위화는 메모리 보호 기법으로 메모리 공격을 방어하기 위해 주소 공간 배치를 난수화 한다. 실행 시마다 메모리 주소를 변경 시켜 버퍼 오버플로를 통한 특정 주소 호출을 차단한다. 리눅스에서 다음과 같이 설정할 수 있다.
echo 2 > /proc/sys/kernel/randomize_va_space
- 실행 불가능한 영역
- 실행 불가능한 영역이란 솔라리스(Solaris)2.7버전에서 /etc/system 파일에 noexec_user_stack, noexec_user_stack_log를 1로 set하여 활성화해 스택과 힙을 실행 불가능한 영역으로 만든다.
- 안전한 개발
- 프로그램 개발 시 입력값에 대한 크기 검증을 적절히 수행하거나 취약하지 않은 함수만을 사용한다면 버퍼 오버플로를 어느 정도 방지할 수 있다.
- 버퍼 오버플로에 취약한 언어들: C, C++
- 버퍼 오버플로에 취약한 함수들 : strcat(), strcpy(), gets(), scanf(), sscanf(), vscanf(), vsscanf(), sprintf(), vsprintf(), gethostbyname()
- 대신 사용이 권장되는 함수들 : strncat(), strncpy(), fgets(), fscanf(), vfscanf(),snprintf(), vsnprintf()[5]
각주
- ↑ 1.0 1.1 위키백과 - https://ko.wikipedia.org/wiki/%EB%B2%84%ED%8D%BC_%EC%98%A4%EB%B2%84%ED%94%8C%EB%A1%9C
- ↑ 아이들링, 〈버퍼 오버플로우(Buffer Overflow) 취약점〉, 《티스토리》, 2018-03-24
- ↑ 캐스피, 〈[https://kaspyx.tistory.com/99 Buffer Overflow - RTL(Return to library) 기법, 《티스토리》, 2017-03-01
- ↑ 4.0 4.1 4.2 위키백과 - https://ko.wikipedia.org/wiki/%EB%B2%84%ED%8D%BC_%EC%98%A4%EB%B2%84%ED%94%8C%EB%A1%9C_%EB%B3%B4%ED%98%B8
- ↑ 공대위키 - http://itwiki.kr/w/%EB%B2%84%ED%8D%BC_%EC%98%A4%EB%B2%84%ED%94%8C%EB%A1%9C%EC%9A%B0
참고자료
- 위키백과 - https://ko.wikipedia.org/wiki/%EB%B2%84%ED%8D%BC_%EC%98%A4%EB%B2%84%ED%94%8C%EB%A1%9C
- 아이들링, 〈버퍼 오버플로우(Buffer Overflow) 취약점〉, 《티스토리》, 2018-03-24
- 캐스피, 〈[https://kaspyx.tistory.com/99 Buffer Overflow - RTL(Return to library) 기법, 《티스토리》, 2017-03-01
- 공대위키 - http://itwiki.kr/w/%EB%B2%84%ED%8D%BC_%EC%98%A4%EB%B2%84%ED%94%8C%EB%A1%9C%EC%9A%B0
- 위키백과 - https://ko.wikipedia.org/wiki/%EB%B2%84%ED%8D%BC_%EC%98%A4%EB%B2%84%ED%94%8C%EB%A1%9C_%EB%B3%B4%ED%98%B8
같이 보기