보안정보

전문화된 보안 관련 자료, 보안 트렌드를 엿볼 수 있는
차세대 통합보안관리 기업 이글루코퍼레이션 보안정보입니다.

glibc getaddrinfo 스택 기반 오버플로우

2016.05.04

12,215

보안관제사업부 보안분석팀 김지우 


1. 개요

 

리눅스 GNU C 라이브러리(glibc 2.9 이상)에서 원격코드 실행이 가능한 취약점(CVE-2015-7547)이 발견되었다. 해당 취약점은 2015년 7월 13일에 최초 확인되었으며 구글 온라인 시큐리티 연구팀에서 추가연구를 통해 원격코드 실행으로 이어질 수 있다는 것을 확인하였다.
※ GNU C 라이브러리(glibc) : 리눅스 계열 운영체제에서 C언어로 작성된 실행파일들이 동작하기 위해 공통적으로 사용하는 기능을 쉽게 이용할 수 있도록 묶어 놓은 소프트웨어 집합

 


2. 취약점 정보

 

■  취약점 영향
 · 원격코드 실행

 

■  영향을 받는 환경
 · GNU glibc 2.9 이후 버전을 사용하는 시스템

 

■  취약점 설명
 · glibc DNS 클라이언트 단의 resolver에서 발생하는 스택 버퍼 오버플로우이며, 이는 getaddinfo() 라이브러리 함수가 사용 되었을 때 내부적으로 send_dg (UDP 쿼리의 경우) 및 send_vc (TCP 쿼리의 경우) 함수에서 발생한다. 해당 취약점은 MITM과 같은 중간자 공격을 통해 공격자가 제어하는 DNS 서버로의 접속 시 특수하게 조작된 DNS 응답을 만들어 libresolv를 중단하거나 library를 실행하는 사용자의 권한으로 잠재적으로 코드를 실행할 수 있다.

 


3. 취약점 분석


취약점을 다루기에 앞서 본문에서 반복적으로 언급되는 포인터 변수에 대한 설명은 다음과 같다.

 

 포인트 변수

 설명

 ansp

 첫 번째 응답 버퍼의 주소를 가리킨다.

 anssizp

 첫 번째 응답 버퍼의 사이즈를 가리킨다.

 ansp2

 두 번째 응답 버퍼의 주소를 가리킨다.

 anssizp2

 두 번째 응답 버퍼의 사이즈를 가리킨다.

 thisansp

 현재 진행 중인 응답 버퍼의 주소를 가리킨다.

 thisanssizp

 현재 진행 중인 응답 버퍼의 사이즈를 가리킨다.

 thisresplenp

 현재 진행 중인 응답 패킷의 길이를 가리킨다.

 

 

취약점은 클라이언트가 DNS 서버로의 질의를 하면서 시작된다. DNS query를 전송하기 전 클라이언트는 DNS 응답수신을 위해 stack에서 2048 byte의 메모리를 할당받는데 이 stack 주소는 send_dg() 함수 안에 반복적으로 사용된다.

 

 


 

클라이언트의 요청을 받은 DNS 서버(공격자가 제어)는 첫 번째 응답으로 DNS 응답패킷을 나타내는 헤더와 함께 “00”으로 이루어진 2048 Bytes의 패킷을 클라이언트로 전송한다.

 

 

 

 

첫 번째 응답을 수신하면 포인터 변수(thisanssizp)는 첫 번째 응답 버퍼의 사이즈를 가리키는 주소를 얻고 첫 번째 응답이 수신되었음을 체크하기 위해 변수(recvresp1)를 “1”로 설정한 후 goto 문에 의해 wait 로 돌아가 두 번째 응답을 수신하기 위한 준비를 한다.

 

 

 

 

DNS 서버는 첫 번째 전송 패킷과 동일한 패킷을 두 번째 응답으로 클라이언트에 다시 전송한다.

 

 

 

 

클라이언트에선 goto 문에 의해 wait 단계로 다시 돌아온 후 조건 분기를 하게 되는데 이미 첫 번째 응답을 받았고 buf2는 non-NULL 상태이기에 else 조건을 실행한다. 이 과정에서 orig_anssizp(2048) - resplen(2048) 연산에 의해 두 번째 응답 버퍼의 사이즈(anssizp2)의 값이 “0” 으로 설정된다.

 

 변수

 설명

 orig_anssizp

 첫 번째 응답 버퍼의 사이즈를 저장한다.

 resplen

 첫 번째 응답 패킷의 길이를 저장한다.

 

 

 

 

 

두 번째 응답 버퍼의 주소(ansp2)와 사이즈(anssizp2), 응답 길이(resplen2)는 각각의 포인터 변수들(thisansp, thisanssizp, thisresplenp)에게 저장이 되고 조건 분기를 하게 되는데 이때 3가지 And 조건을 모두 만족하므로 malloc() 함수를 통해 MAXPACKET(65535 bytes)의 동적 메모리를 생성한다.
 이때 첫 번째 응답버퍼의 사이즈(anssizp)는 MAXPACKET(65535 bytes)으로 변경되는데 이는 버퍼 오버플로우가 발생하는 원인이 된다.

 

 

 


이후 응답 패킷의 길이를 확인하기 위해 recvfrom 함수를 사용하는데 이때 인수로 주어진 포인터 변수(*thisansp)는 새로 할당된 버퍼(new buffer)를 가리키지만 포인터 변수(*thisanssizp)는 두 번째 응답 버퍼의 사이즈(anssizp2)인 “0” 을 가리키므로 포인터 변수(*thisresplenp)의 값은 “0”이 설정된다. 포인터 변수(*thisresplenp)의 값은 그 다음 조건 분기에서 포인터 변수(thisresplenp)의 값이 “0” 이기 때문에 err_out 로 이동하게 되고 send_dg()함수는 종료된다.

 

 

 

 

여기까지가 최초 취약점이 발견된 시점(2015년 7월 13일)에 확인된 내용으로 단순 크래쉬 현상으로만 판단되었으나 이후 구글 온라인 시큐리티 연구팀에서 추가연구를 통해 클라이언트가 위 과정들로 발생한 에러로 인해 send_dg()가 종료되지만 __libc_res_nsend을 다시 순환하는 과정에서 이전에 사용된 버퍼가 재사용되고 이로 인해 버퍼 오버플로우가 발생함을 확인한다.

 

순환과정이 종료되기 전에 버퍼 오버플로우를 일으키기 위해서 DNS 서버는 세 번째 응답 패킷(2048byte 이상, 최대 65535 byte)을 전송한다.

 

 

 


이때 첫 번째 응답을 처리할 때처럼 포인터 변수(thisanssizp)는 첫 번째 응답 버퍼의 사이즈를 가리키는 주소를 얻게 되지만 이 주소가 가지고 있는 값은 처음과 같은 “2048”이 아닌 두 번째 응답 패킷을 처리하는 과정에서 할당된 MAXPACKET(65535 bytes)이다.

 

 

 


이와 같이 잘못된 사이즈를 참조한 상태에서 로직대로 흘러가다가 recvfrom() 함수를 처리하는 과정에서 스택을 덮어쓰게 되면서 버퍼 오버플로우가 발생한다.

 

 

 


이를 이용해 2048의 정상 범위를 제외하고 나머지 63487 bytes는 공격자가 제어할 수 있는 공격 코드로 사용될 수 있다.

 

 

4. 취약점 영향 조사


■ 로컬 점검
PoC 코드 다운로드 : https://github.com/fjserna/CVE-2015-7547/blob/master/CVE-2015-7547-poc.py
PoC 코드는 Python으로 구현되어 있으므로 먼저 Python을 설치하여야 한다.

 

 

 


CMD 창에서 PoC 코드 실행 시 53 포트(DNS 서버)가 활성화 된다.

 

 


이 후 점검하고자 하는 서버(또는 PC)의 DNS 설정을 변경한다. 필자는 VMWare에서 테스트한 관계로 VM 네트워크 설정을 변경하였다.

 

 

 


DSN 설정 완료 후 glibc 라이브러리를 사용하는 어플리케이션(ex. ssh)을 이용하여 임의의 사이트에 접속을 시도한다. 테스트 결과 아래 그림과 같이 “Segementation fault” 메시지가 발생할 경우 취약한 버전의 glibc 라이브러리를 사용하는 것으로 판단한다.

 

 

 


■ glibc 라이브러리에 의존하는 패키지 및 어플리케이션 확인 방법


[root@redhat]# lsof |grep libc | awk '{print $1}' | sort | uniq

 

 

 

 


5. 대응방안


■ 패치 버전 설치
취약한 glibc 버전을 사용하고 있는 경우, 운영체제 제조사 홈페이지를 방문하여 패치 방법을 확인한다.

 

 CentOS

 https://www.centos.org/forums/viewforum.php?f=17

 Debian

 https://security-tracker.debian.org/tracker/CVE-2015-7547

 Redhat

 https://access.redhat.com/security/cve/cve-2015-7547

 Ubuntu

 http://people.canonical.com/~ubuntu-security/cve/2015/CVE-2015-7547.html

 


OS별 패키지 버전 확인 명령어는 다음과 같다.

 

 OS 종류

 패키지 버전 확인

 CentOS / Redhat

 rpm -qa | grep glibc

 Ubuntu / Debian

 apt-cache show eglibc-source | grep Version

 


패키지 업데이트 방법은 다음과 같으며 패키지 업데이트 후 반드시 시스템을 재부팅하여야 한다.

 

 OS 종류

 패키지 업데이트 방법

 CentOS / Redhat

 yum install glibc

 Ubuntu / Debian

 apt-get clean && apt-get update && apt-get upgrade

 


■ Snort Rule을 이용한 공격탐지
취약점이 발생하기 위해선 2차례에 걸쳐 조작된 UDP 패킷을 전송해야 하므로 다음과 같이 UDP 패킷 중 DNS 응답 패킷의 사이즈가 2048 byte를 초과할 경우 탐지하도록 Snort Rule을 설정한다.


alert udp  any 53 ->any any (msg:”glibc getaddrinfo stack-based buffer overflow”; dsize:>2048;sid:10000007 ;)

 

 

 


7. 참고자료


[1] [PATCH] CVE-2015-7547 --- glibc getaddrinfo() stack-based buffer overflow -
https://sourceware.org/ml/libc-alpha/2016-02/msg00416.html

[2] GitHub CVE-2015-7547
https://github.com/fjserna/CVE-2015-7547

[3] glibc의 getaddrinfo() 함수에서 발생하는 스택 버퍼 오버플로우 취약점
https://access.redhat.com/ko/node/2171151

[4] 구글이 드러낸 glibc 취약점, 보안 위기다? 아니다?
http://www.boannews.com/media/view.asp?idx=49650&kind=4

[5] glibc - getaddrinfo Stack-Based Buffer Overflow
https://www.exploit-db.com/exploits/39454/

[6] CVE-2015-7547: glibc getaddrinfo stack-based buffer overflow(Google online Security Blog)
https://googleonlinesecurity.blogspot.kr/2016/02/cve-2015-7547-glibc-getaddrinfo-stack.html