기본 콘텐츠로 건너뛰기

C-lang-vulnerabilities

C 언어 공통 취약점

해당 글은 CERN Computer Security의 Common vulnerabilities guide for C programmers 글을 참고하여 작성하였습니다.

C언어에서 발생하는 대부분의 취약점은 버퍼 오버플로우와 문자열 처리 미흡과 관련되어 있다. 이는 segmentation fault를 유발하고 입력 값을 조작할 경우 임의 코드 실행으로 이어질 수 있다. 이에 대부분의 에러와 조치 방안을 살펴보자고 한다.

gets

stdio gets() 함수는 버퍼 길이를 검증하지 않아 사용 시 항상 취약성을 야기한다.

Vulnerable Code

#include<stdio.h>
int main() {
    char username[8];
    int allow = 0;
    printf("Enter your username, please: ");
    gets(username); //악의적인 값 삽입
    if(grantAccess(username)) {
        allow = 1;
    }
    if(allow !=0) { //username을 오버플로우하여 덮어씀
        privilegeAction();
    }
    return 0;
}

Mitigation

fgets()함수 사용 및 동적 메모리 할당

#include <stdio.h>
#include <stdlib.h>
#define LENGTH 8
int main () {
    char* username, *nlptr;
    int allow = 0;

    username = malloc(LENGTH * sizeof(*username));
    if (!username)
        return EXIT_FAILURE;
    printf("Enter your username, please: ");
    fgets(username,LENGTH, stdin);
    // fgets는 LENGTH-1이나 개행 문자 다음에 멈춘다.
    nlptr = strchr(username, '\n');
    if (nlptr) *nlptr = '\0';

    if (grantAccess(username)) {
        allow = 1;
    }
    if (allow != 0) {
        priviledgedAction();
    }

    free(username);
    return 0;
}

strcpy

strcpy() 내장 함수는 버퍼 길이를 검사하지 않아 목적지에 인접할 메모리영역을 덮어 쓸 수 있다. strcat()strcmp() 함수 또한 취약한다.

Vulnerable Code

char str1[10];
char str2[]="abcdefghijklmn";
strcpy(str1,str2);

Mitigation

strlcpy()함수를 사용하는 것이 가장 좋은 방법이다. BSD 시스템이 아닌 경우, 간단히 구현하여 조치할 수 있다.

#include <stdio.h>

#ifndef strlcpy
#define strlcpy(dst,src,sz) snprintf((dst), (sz), "%s", (src))
#endif

enum { BUFFER_SIZE = 10 };

int main() {
    char dst[BUFFER_SIZE];
    char src[] = "abcdefghijk";

    int buffer_length = strlcpy(dst, src, BUFFER_SIZE);

    if (buffer_length >= BUFFER_SIZE) {
        printf("String too long: %d (%d expected)\n",
                buffer_length, BUFFER_SIZE-1);
    }

    printf("String copied: %s\n", dst);

    return 0;
}

또 다른 방법은 strncpy()함수를 사용하는 것이지만 ‘\0’ 종료를 보장하지 않는다.

enum { BUFFER_SIZE = 10 };
char str1[BUFFER_SIZE];
char str2[]="abcdefghijklmn";

strncpy(str1,str2, BUFFER_SIZE); /* 복사할 문자 수 제한 */
//버퍼의 모든 문자가 '\0'으로 설정되게 하고 만약 소스 버퍼가 제한 길이보다 길면 복사본에 '\0'로 덮어쓴다.

if (str1[BUFFER_SIZE-1] != '\0') {
    /* buffer was truncated, handle error? */
}

sprintf

이전 함수들과 마찬가지로 sprinf()함수는 버퍼 경계를 검사하지 않아 오버플로우에 취약하다.

Vulnerable Code

#include <stdio.h>
#include <stdlib.h>

enum { BUFFER_SIZE = 10 };

int main() {
    char buffer[BUFFER_SIZE];
    int check = 0;

    sprintf(buffer, "%s", "This string is too long!");

    printf("check: %d", check); /* This will not print 0! */

    return EXIT_SUCCESS;
}

Mitigation

snprintf()함수를 사용하면 오버플로우 방지 및 최소 크기의 버퍼를 반환하는 장점이 있다.

#include <stdio.h>
#include <stdlib.h>

enum { BUFFER_SIZE = 10 };

int main() {
    char buffer[BUFFER_SIZE];

    int length = snprintf(buffer, BUFFER_SIZE, "%s%s", "long-name", "suffix");

    if (length >= BUFFER_SIZE) {
        /* handle string truncation! */
    }

    return EXIT_SUCCESS;
}

printf and friends

다른 취약점 분류로 string formatting attacks을 들 수 있다. 이는 정보 유출이나 메모리 덮어쓰기의 문제를 야기할 수 있다. 해당 에러는 printf(), fprintf(), sprintf(), snprintf 등 포멧 스트링을 인자로 가지는 모든 함수에서 발생한다.

Vulnerable Code

#FormatString.c
#include <stdio.h>

int main(int argc, char **argv) {
    char *secret = "This is a secret!\n";

    printf(argv[1]);

    return 0;
}

위 코드를 -mpreferred-stack-boundary=2 옵션을 설정하여 컴파일하면 흥미로운 결과를 확인할 수 있다.(64 비트의 경우 약간 다르게 동작하지만, 여전히 취약하다.)

$ gcc -mpreferred-stack-boundary=2 FormatString.c -o FormatString
$ ./FormatString %s
This is a secret!
$

-mpreferred-stack-boundary=2 옵션은 정보 누출의 원인이 될 수 없으며 설정하지 않아도 코드가 더 안전 해지지 않는다.

Mitigation

항상 포멧 스트링을 삽입하여 사용하며, 절대 사용자에게 곧바로 입력 받지 않는다.

File opening

파일을 열 때, 많은 문제가 야기될 수 있으므로 반드시 주의가 필요하다. (자세한 내용은 Kupsch and Miller가 작성한 튜토리얼 문서를 참고하자.) 파일 처리 시 공격 받을 수 있는 여러 가지 경우 중에서 두 개의 간단한 예를 살펴보자.

파일을 생성하기 전에 파일이 존재하는 지 확인하는 것이 좋다. 그러나 공격자는 파일을 사용하는 순간 중요 파일에 대한 심볼릭 링크를 생성할 수 있다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define MY_TMP_FILE "/tmp/file.tmp"

int main(int argc, char* argv[])
{
    FILE * f;
    if (!access(MY_TMP_FILE, F_OK)) {
        printf("File exists!\n");
        return EXIT_FAILURE;
    }
    /* 이 시점에 공격자는 /etc/passwd에 대한 심볼릭 링크를 /tmp/file.tmp에 생성한다.
    tmpFile = fopen(MY_TMP_FILE, "w");

    if (tmpFile == NULL) {
        return EXIT_FAILURE;
    }

    fputs("Some text...\n", tmpFile);

    fclose(tmpFile);
    /* 공격자는 성공적으로 /etc/passwd를 덮어쓴다. (root권한으로 구동된다는 가정) */

    return EXIT_SUCCESS;
}

Mitigation

파일을 직접 접근하여 레이스 컨디션을 피하고 파일이 존재할 경우 덮어쓰지 않는다.

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

#define MY_TMP_FILE "/tmp/file.tmp"

enum { FILE_MODE = 0600 };

int main(int argc, char* argv[])
{
    int fd;
    FILE* f;

    /* 심볼릭 링크 제거 */
    unlink(MY_TMP_FILE);
    /* 이때, 심볼릭 링크를 복원한 경우 실패한다. - fopen(path, "w")을 보완한 형태 */
    fd = open(MY_TMP_FILE, O_WRONLY|O_CREAT|O_EXCL, FILE_MODE);
    if (fd == -1) {
        perror("Failed to open the file");
        return EXIT_FAILURE;
    }
    /* 파일 기술자 대신에 파일 포인터를 사용 */
    f = fdopen(fd, "w");
    if (f == NULL) {
        perror("Failed to associate file descriptor with a stream");
        return EXIT_FAILURE;
    }
    fprintf(f, "Hello, world\n");
    fclose(f);
    /* fclose()로 fd를 닫는다. */
    return EXIT_SUCCESS;
}

O_WRONLY|O_CREAT|O_EXCL
O_WRONLY : 쓰기 전용으로 열기
O_CREAT : 해당 파일이 없으면 생성
O_EXCL : O_CREAT를 사용할 때, O_EXCL를 함께 사용하면, 이미 파일이 있을 때에는 open() 되지 않아 이전 파일을 보존
출처: falinux open 파일 열기

출처: Common vulnerabilities guide for C programmers

이 블로그의 인기 게시물

데일 카네기 인간관계론 정리

Remove-Server-Header

응답 메시지 내 서버 버전 정보 제거 1. Apache 1) 조치 방법 “/etc/htpd/conf/httpd.conf” 파일 안에서 1. ServerTokens OS → ServerTokens Prod 2. ServerSignature On → ServerSignature Off 로 변경한 후 아파치를 재시작하면 헤더 값의 아파치 버전 정보 및 OS 정보를 제거할 수 있다. 2) 참고 URL http://zetawiki.com/wiki/CentOS_ 아파치_보안권장설정_ServerTokens_Prod,_ServerSignature_Off 2. IIS 1) 조치 방법 IIS 6.0 urlscan_setup 실행. 설치. \windows\system32\inetsrv\urlscan\urlscan.ini 파일을 열어 다음 수정(RemoveServerHeader=0 을 RemoveServerHeader=1 로 변경) 서비스에서 IIS Admin Service 재시작. IIS 7.0 IIS 관리자를 열고 관리하려는 수준으로 이동합니다. 기능 보기에서 HTTP 응답 헤더를 두 번 클릭합니다. HTTP 응답 헤더 페이지에서 제거할 헤더를 선택합니다. 작업 창에서 제거를 클릭하고 예를 클릭합니다. 2) 참고 URL IIS 6.0 : http://gonnie.tistory.com/entry/iis6- 응답헤더-감추기 IIS 7.0 : https://technet.microsoft.com/ko-kr/library/cc733102(v=ws.10).aspx 3. jetty 1) 조치 방법 “jetty.xml” 파일에서 jetty.send.server.version=false 설정 2) 참고 URL http://attenuated-perspicacity.blogspot.kr/2009/09/jetty-61x-hardening.html 4. Nginx ...

American Fuzzy Lop And Address Sanitizer

American Fuzzy Lop And Address Sanitizer Address Sanitizer(ASAN) 단지 gcc/clang에 -fsanitize=address 옵션을 추가하는 것으로 간단히 사용할 수 있지만 그 효과는 충분하다. Example Out Of Bounds Read #include <stdio.h> int main() { int a[ 2 ] = { 3 , 1 }; int i = 2 ; printf ( "%i\n" , a[i]); } 예제 파일을 OutOfBoundsRead.c 로 생성하고 ASAN 옵션을 지정하여 clang 으로 컴파일하자. clang -g -fsanitize = address -fno -omit -frame -pointer OutOfBoundsRead . c -o OutOfBoundsRead 생성된 OutBoundsRead 파일을 실행하면 다음과 같은 결과를 볼 수 있다. ================================================================= ==3678==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc0ba87428 at pc 0x47b7db bp 0x7ffc0ba87390 sp 0x7ffc0ba87388 READ of size 4 at 0x7ffc0ba87428 thread T0 ==3678==WARNING: Trying to symbolize code, but external symbolizer is not initialized! #0 0x47b7da (/root/ASAN/OutOfBoundsRead+0x47b7da) #1 0x7faba0260f44 (/lib/x86_64-linux-gnu/libc.so.6+0x21f44) #2 0x...