C언어 시큐어 코딩 #1 입력 데이터 검증 및 표현

Posted by ceyx
2017. 5. 23. 11:59 C 언어/Coding

시큐어 코딩은 SW 개발과정에서 개발자 실수, 논리적 오류 등으로 인해 SW에 내포될 수 있는 보안취약점의 원인, 즉 보안약점을 최소화하는 한편, 사이버 보안위협에 대응할 수 있는 일련의 보안활동을 의미한다..

 

 

1자원 삽입

  • 정의
    • 외부 입력값을 검증하지 않고 시스템 자원(resource)에 대한 식별자로 사용하는 경우, 공격자는 입력값 조작을 통해 시스템이 보호하는 자원에 임의로 접근하거나 수정할 수 있다.
  • 해결방법
    • 외부의 입력을 자원(파일, 소켓의 포트 등) 식별자로 사용하는 경우, 적절한 검증을 거치도록 한다.
  • 안전하지 않은 코드 예제

                   


  • 안전한 코드 예제

              

 

2. 상대 디렉터리 경로 조작

  • 정의
    • 외부 입력을 통하여 디렉터리 경로 문자열을 생성하는 경우, 결과 디렉터리가 제한된 디렉터리여야 할 때, 악의적인 외부입력을 제대로 변환시키지 않으면 예상 밖 영역의 경로 문자열이 생성될 수 있다. 이 취약점을 이용하여 제한된 디렉터리 영역 밖을 공격자가 접근할 수 있게 된다. 가장 많이 쓰이는 것이 ".."을 사용하여 공격하는 것이다. 대부분의 시스템에서 ".."은 상위 디렉터리를 의미하기 때문에 제한된 디렉터리의 상위를 접근할 수 있는 경로를 생성하는 것이다.

  • 해결방법
    • 파일 접근 함수의 파일 경로 인수 일부를 외부 입력으로부터 조합하여 사용하지 않는다.
  • 안전하지 않은 코드 예제
    • void f() {

          char* rName = getenv("reportName");

          char buf[30];

          strncpy(buf, "/home/www/tmp/", 30);

          strncat(buf, rName, 30);

          unlink(buf);

      }

  • 안전한 코드 예제
    • void f() {

          char buf[30];

          strncpy(buf, "/home/www/tmp/", 30);

          strncat(buf, "report", 30);

          unlink(buf);

      }

 

 

3. 정수 오버플로우

  • 정의
    • 정수 연산 시에 정수형 변수가 취할 수 있는 범위를 초과할 때 발생하는 경우이다. 이러한 상황이 발생한 후에 순환문의 조건이나 메모리 할당, 메모리 복사 등에 쓰이거나 이 값에 근거해서 보안 관련 결정을 하는 경우 보안 취약점이 발생된다.
  • 해결방법
    • Signed 정수 타입의 경우, 오버플로우 발생시 양수 음수가 변할 수 있다. 이를 위해, 메모리 할당 함수에서는 할당량을 표시하는 파라미터를 unsigned 타입으로 전달해야 잘못된 값이 사용되는 것을 방지할 수 있으며 배열등의 인덱스를 외부에서 입력받을 경우에는 인덱스의 크기를 검사한다. 
  • 안전하지 않은 코드 예제
    • #include <stdlib.h>

      void* intAlloc(int size, int reserve) {

          void *rptr;

          size += reserve;

          rptr = malloc(size * sizeof(int));

          if (rptr == NULL)

              exit(1);

          return rptr;

       }

  • 안전한 코드 예제
    • #include <stdlib.h>

      void* intAlloc(int size, int reserve) {

          void *rptr;

          unsigned s;

          size += reserve;

          s = size * sizeof(int);

          if (s < 0)

              return NULL;

          rptr = malloc(s);

          if (rptr == NULL)

              exit(1);

          return rptr;

       } 

 

 

4. 스택에 할당된 버퍼 오버플로우

  • 정의
    • 스택에 할당되는 버퍼들 (지역변수로 선언되거나 함수의 인자)이 문자열 계산 등에 의해서 정의된 버퍼의 한계치를 넘는 경우 버퍼 오버플로우 발생
  • 해결방법
    • 입력에 대한 경계 검사를 한다.
    • strcpy()와 같이 버퍼 오버플로우에 취약한 함수를 사용하지 않는다.
  • 안전하지 않은 코드 예제

                   

  

  • 안전한 코드 예제

                  

 

4. 힙에 할당된 버퍼 오버플로우

  • 정의
    • 힙에 할당되는 버퍼들 (예, malloc() 함수를 통해 할당된 버퍼들)에 문자열 등이 저장되어 질 때, 최초 정의된 사이즈를 초과하여 문자열 등이 저장되는 경우
  • 해결방법
    • 입력에 대해 경계 검사를 한다.
    • strlcpy와 같이 버퍼 오버플로우 위험이 없는 함수를 사용한다.
  • 안전하지 않은 코드 예제

                  

 

 

  • 안전한 코드 예제

                   

 

 

 

5. 버퍼 시작 지점 이전에 쓰기

  • 정의
    • 포인터나 인덱스를 통해서 버퍼 시작 지점 이전에 데이터를 기록하는 오류이다. 배열인 경우 인덱스의 최저 위치 보다 적은 위치에 데이터를 기록하는 경우 발생하며 루프 내에서 점검 없이 포인터 감소 연산을 계속했을 경우 발생한다.
  • 해결방법
    • 버퍼에 인덱스나 포인터를 사용해 데이터를 기록할 경우 인덱스가 음수 값이 되거나 포인터 연산의 결과가 버퍼 이전의 값을 가지지 않도록 점검 후 사용한다.
  • 안전하지 않은 코드 예제
    • #include <stdio.h>

      void inverseWordOrder(char * string) {

          const int MAX_WORD_COUNT = 256;

          int wordIndex = MAX_WORD_COUNT - 1;

          char * words[MAX_WORD_COUNT]

          memset(words, NULL, MAX_WORD_COUNT * sizeof(int));

          char * token = strtok(string, " \t");

          while (token != NULL) {

              words[wordIndex] = token;

              token = strtok(NULL, " \t");

              wordIndex--;

          }

       

      }

  • 안전한 코드 예제
    • #include <vector>

      #include <stdio.h>

      using namespace std;

       

      void inverseWordOrder(char * string) {

          const int MAX_WORD_COUNT = 256;

          vector<char *> words(MAX_WORD_COUNT, NULL);

          int wordIndex = MAX_WORD_COUNT - 1;

       

          try

          {

              char * token = strtok(string, " \t");

              while (token != NULL) {

                  words.at(wordIndex) = token;

                  token = strtok(NULL, " \t");

                  wordIndex--;

              }

          }

      catch(const std::exception & exception)

      {

          // out_of_range exception 처리

      }

      }


 

 

6. 널 종료 문제

  • 정의
    • C에서 문자열의 끝을 나타내는 것이 널 문자이자. 개발자의 의도와 상관없이 널 문자가 붙지 않은 문자열이 생성될 수 있는데, 1) 문자열이 필요한 크기보다 작은 배열에 복사하는 경우, 2) 문자열을 strncpy() 함수를 이용해 복사할 때 실제 문자열보다 지정 크기가 작은 경우 등에 발생한다. 널 문자로 종료되지 않은 문자열을 다른 문자열에 복사할 때, 많은 양의 메모리가 복사되어 버퍼 오버플로우가 발생 가능하다.
  • 해결방법
    • read(), redline()로 읽을 문자열에 strcpy(), strcat(), strlen() 함수를 적용하지 않는다.
    • strlcpy()나 strcat() 과 같은 버퍼 오버플로우 위험이 없는 함수를 사용한다.
  • 안전하지 않은 코드 예제

               

 

  • 안전한 코드 예제

                

 

 

7. 의도하지 않은 부호 확장

 

 

  • 정의
    • 큰 수를 표현하는 데이터 형으로 값이 변환될 때 음수 값이 큰 양수 값으로 변환될 수 있다. 숫자형 데이터를 처리 할 때 해당 데이터 형보다 큰 데이터 형으로 복사되는 경우, 부호 확장이 수행된다. 특히, 음수 값을 unsigned로 복사하는 경우 잘못된 부호의 확장으로 엄청난 큰 숫자가 발생 할 수 있다.
  • 해결방법
    • 큰 사이즈의 변수와 작은 사이즈의 변수 사이의 값 교환 시 타입 체크를 통하여 잘못된 부호 확장이 일어나지 않도록 해야 한다.
  • 안전하지 않은 코드 예제
    • extern int info[256];

      int signed_overflow(int id) {

          short s;

          unsigned sz;

       

          s = id;

          if (s > 256)

              return 0;

          sz = s;

          return info[sz];

      }

  • 안전한 코드 예제
    • extern int info[256];

      int signed_overflow(int id) {

          int s;

          unsigned sz;

          s = id;

          if (s > 256 || s < 0)

              return 0;

          sz = (unsigned) s;

          return info[sz];

      }

 

 

 

8. 무부호 정수를 부호 정수로 타입 변환 오류

  • 정의
    • 무부호 정수 (unsigned integer)를 부호 정수 (signed integer)로 변환하면서 큰 양수가 음수로 변환될 수 있다. 이 값이 배열 인덱스로 사용되는 경우 프로그램 오동작이 발생된다.
  • 해결방법
    • 타입체크를 통해서 signed int를 묵시적으로 unsigned int로 변환되지 않도록 한다.
  • 안전하지 않은 코드 예제

#include <stdlib.h>

#include <string.h>

extern int initialized, chunkSize;

int chunkSz() {

    if (!initialized)

        return -1;

    return chunkSize;

}

void* chunkCpy(void *dBuf, void *sBuf) {

    unsigned size;

    size = chunkSz();

    return memcpy(dBuf, sBuf, size);

}

 

       

                

  • 안전한 코드 예제

#include <stdlib.h>

#include <string.h>

extern int initialized, chunkSize;

int chunkSz() {

    if (!initialized)

        return -1;

    return chunkSize;

}

void* chunkCpy(void *dBuf, void *sBuf) {

    int size;

    size = chunkSz();

    if (size < 0)

        return NULL;

    return memcpy(dBuf, sBuf, (unsigned) size);

}

 

                           

 

작성자 : mds 백정현

출처 : C 시큐어코딩 가이드 (행정안전부)