EASY7

ghost(CVE-2015-0235) 본문

보안 공부/취약점 실습

ghost(CVE-2015-0235)

E.asiest 2019. 9. 10. 23:04

ghost 취약점

GNU C Library (glibc) 의 호스트네임을 조회하는 gethostbyname()의 패밀리함수에서 입력값 검증이 제대로 이루어지지 않아 Overflow 를 일으키는 버그.

 

 

관련 CVE정보

CVE-2015-0235

 

 

취약한 소프트웨어 버전

취약한 버전 : On Fedora 19 (glibc-2.17): [user@...ora-19 ~]$ ./GHOST vulnerable

패치된 버전 : On Fedora 20 (glibc-2.18): [user@...ora-20 ~]$ ./GHOST not vulnerable

 

보안 조치 방법

 

 

 

netdb.h 라이브러리의 gethostbyname 함수대한 이해

함수를 호출하면 소켓 라이브러리가 DNS서버에 조회하여 IP주소나 도메인 명을 찾는 기능을 제공한다.

정보를 hostent구조체에 담아서 반환해준다.

(참고 : http://forum.falinux.com/zbxe/index.php?mid=C_LIB&document_srl=518686)

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netdb.h>

 

int   main( void)

{

    struct hostent *host_entry;

    int             ndx;

 

    host_entry = gethostbyname( "easy7.tistory.com");

 

    if ( !host_entry)

    {

        printf"gethostbyname() 실행 실패/n");

        exit( 1);

    }

    for ( ndx = 0NULL != host_entry->h_addr_list[ndx]; ndx++){

        printf("ndx : %d\n", ndx);

     printf("%x \n", host_entry->h_addr_list[ndx]);

     printf("\n");

     printf"%s\n", inet_ntoa( *(struct in_addr*)host_entry->h_addr_list[ndx]));

    

    }

    return 0;

}

 

 

h_addr_list 배열은 데이터 하나만 들어있다

(0x5257b818) 이건... h_addr_list의 값이 아닌 메모리 주소인 것 같다.

더보기

*빅엔디언을 십진수로 변경해보기(h_addr_list의 값이 아님 ㅠㅠ)

16진수를 2진수로 변환해보겠다.

5 -> 0101

2 -> 0010

5 -> 0101

7 -> 0111

b -> 1011

8 -> 1000

1 -> 0001

8 -> 1000

 

2진수들을 이으면

0101 0010 , 0101 0111, 1011 1000, 0001 1000 (https://m.blog.naver.com/PostView.nhn?blogId=rna2823&logNo=220602373183&proxyReferer=https%3A%2F%2Fwww.google.com%2F)

 

2진수를 10진수로 변경

82,87,184,24 (땡!)

*빅엔디언을 십진수로 변경해보기(h_addr_list의 값이 아님 ㅠㅠ)

16진수를 2진수로 변환해보겠다.

5 -> 0101

2 -> 0010

5 -> 0101

7 -> 0111

b -> 1011

8 -> 1000

1 -> 0001

8 -> 1000

 

2진수들을 이으면

0101 0010 , 0101 0111, 1011 1000, 0001 1000 (https://m.blog.naver.com/PostView.nhn?blogId=rna2823&logNo=220602373183&proxyReferer=https%3A%2F%2Fwww.google.com%2F)

 

2진수를 10진수로 변경

82,87,184,24 (땡!)

 

* 네트워크 바이트 순서(big endian)과 십진수 표현 방식(Dotted-decimal notation)간의 상호 변환 함수.

inet_ntoa() :  네트워크 바이트 순서-> 십진수 표현 방식

inet_addr() :  십진수 표현방식 -> 네트워크 바이트 순서

 

빅엔디언과 리를엔디언

 

 

* hostent 구조체(참고 : https://sjh836.tistory.com/50)

struct hostent {

 char *h_name; //공식 도메인 이름

 char **h_aliases; //공식 이외 도메인 이름들

 int h_addrtype; //주소정보 체계(IPv4: AF_INET, IPv6: AF_INET6)

 int h_length; //IP주소의 크기를 담는다. (IPv4는 4)

 char **h_addr_list; //도메인 이름에 대한 IP주소가 정수 형태로 반환될 때 이 멤버 변수를 이용 }
}

 

 

* gethostbyname_r()함수

struct hostent *gethostbyname_r( const char * name, struct hostent * result, char * buffer, int bufflen, int * h_errnop );

  • name : 인터넷 호스트 네임 ex) www.naver.com
  • result : hostent struct 의 포인터, 결과가 담긴다
  • buffer : 연산중 저장하는 name을 저장하는 임시 데이터(?)
  • bufflen : buffer에 의해 포인트되는 구역의 길이
  • h_errnop : 에러가 날 때 에러 넘버를 저장하는 포인터

 

실습 ghost 취약점 해보기

1. 취약한 버전인지 확인하기.

아래 C로 작성된 파일 실행하기

#include <netdb.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <errno.h>

 

#define CANARY "in_the_coal_mine"

 

struct {

  char buffer[1024];

  char canary[sizeof(CANARY)];

} temp = { "buffer", CANARY };

 

int main(void) {

  struct hostent resbuf;

  struct hostent *result;

  int herrno;

  int retval;

 

  /*** strlen (name) = size_needed - sizeof (*host_addr) - sizeof (*h_addr_ptrs) - 1; ***/

  size_t len = sizeof(temp.buffer) - 16*sizeof(unsigned char) - 2*sizeof(char *) - 1; //1024-16-16-1=991 사이즈 오차

  char name[sizeof(temp.buffer)];

  memset(name, '0', len); //name 문자열 0~990까지 0으로 대체

  name[len] = '\0'; //name[991]에 문자의 끝을 나타내는 \0 대입

 

printf("before: %s \n", temp.canary); //이전 temp.canary 값 "in_the_coal_mine"

//name을 temp.buffer에 대입하여 오버플로우 시킴.

  retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);

 

  if (strcmp(temp.canary, CANARY) != 0) {

    puts("vulnerable");

    exit(EXIT_SUCCESS);

  }

  if (retval == ERANGE) {

    puts("not vulnerable");

    exit(EXIT_SUCCESS);

  }

  puts("should not happen");

  exit(EXIT_FAILURE);

}

 

# gcc ghost.c -o ghost

결과  :  패치가 됐는지 익스플로잇 되지 않았다.

취약한 버전 : On Fedora 19 (glibc-2.17): [user@...ora-19 ~]$ ./GHOST vulnerable

패치된 버전 : On Fedora 20 (glibc-2.18): [user@...ora-20 ~]$ ./GHOST not vulnerable

 

glibc 버전이 2.28-8인것을 볼 수 있다.

 

 

취약한 패밀리함수 __nss_hostname_digits_dots....

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

int

__nss_hostname_digits_dots (const char *name, struct hostent *resbuf,

    char **buffer, size_t *buffer_size,

    size_t buflen, struct hostent **result,

    enum nss_status *status, int af, int *h_errnop)

{

    if (isdigit (name[0]) || isxdigit (name[0]) || name[0== ':')

    {

        const char *cp;

        char *hostname;

        typedef unsigned char host_addr_t[16];

        host_addr_t *host_addr;

        typedef char *host_addr_list_t[2];

        host_addr_list_t *h_addr_ptrs;

        char **h_alias_ptr;

        size_t size_needed;

 

        size_needed = (sizeof (*host_addr)+ sizeof (*h_addr_ptrs) + strlen (name) + 1);

 

        if (buffer_size == NULL)

        {

            if (buflen < size_needed)

            {

                goto done;

            }

        }

        else if (buffer_size != NULL && *buffer_size < size_needed)

        {

            char *new_buf;

            *buffer_size = size_needed;

            new_buf = (char *realloc (*buffer, *buffer_size);

 

            if (new_buf == NULL)

            {

                goto done;

            }

            *buffer = new_buf;

        }

 

        host_addr = (host_addr_t **buffer;

        h_addr_ptrs = (host_addr_list_t *) ((char *) host_addr + sizeof (*host_addr));

        h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs));

        hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);

 

        if (isdigit (name[0]))

        {

            for (cp = name;; ++cp)

            {

                if (*cp == '\0')

                {

                    int ok;

 

                    if (*--cp == '.')

                        break;

 

                    if (af == AF_INET)

                        ok = __inet_aton (name, (struct in_addr *) host_addr);

                    else

                    {

                        assert (af == AF_INET6);

                        ok = inet_pton (af, name, host_addr) > 0;

                    }

                    if (! ok)

                    {

 

                        goto done;

                    }

 

                    resbuf->h_name = strcpy (hostname, name);

 

                    goto done;

                    }

    

                    if (!isdigit (*cp) && *cp != '.')

                        break;

                    }

                }

 

 

Colored by Color Scripter

cs

 

 

 

참고자료

실습 자료

https://m.blog.naver.com/PostView.nhn?blogId=renucs&logNo=220284384395&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

Ghost 취약점 (CVE-2015-0235)

근래에 굵직굵직한 오픈소스 취약점들이 많이 터지는 것 같습니다. 작년부터 Heart Bleed, Bash Shell ...

blog.naver.com

 

hostent구조체 개념

https://sjh836.tistory.com/50

 

hostent 구조체, 도메인 관련 API

1. hostent 구조체 struct hostent { char *h_name; //공식 도메인 이름 char **h_aliases; //공식 이외 도메인 이름들 int h_addrtype; //주소정보 체계(IPv4: AF_INET, IPv6: AF_INET6) int h_length; //IP주소..

sjh836.tistory.com

gethostbyname 함수에 대한 자료

http://forum.falinux.com/zbxe/index.php?mid=C_LIB&document_srl=518686)

 

 

Comments