IgnatiusHeo

구현단계 보안약점 제거 기준-포맷 스트링 삽입 본문

자격/SW보안약점진단원

구현단계 보안약점 제거 기준-포맷 스트링 삽입

Ignatius Heo 2023. 7. 4. 18:55

작성일: 230704

 

※ 본 게시글은 학습 목적으로 행정안전부·KISA의 소프트웨어 보안약점 진단 가이드, 소프트웨어 개발보안 가이드를 참고하여 작성하였습니다.

 

정리 내용: 소프트웨어 보안약점 진단 가이드(309~313p)

구분 - 입력데이터 검증 및 표현
설계단계 - 허용된 범위내 메모리 접근
https://cryptocurrencyclub.tistory.com/96
개요
메모리 버퍼 오버플로우 보안약점은 연속된 메모리 공간을 사용하는 프로그램에서 할당된 메모리의 범위를 넘어선 위치에 자료를 읽거나 쓰려고 할 때 발생한다.

메모리 버퍼 오버플로우는 프로그램의 오동작을 유발시키거나, 악의적인 코드를 실행시킴으로써 공격자 프로그램을 통제할 수 있는 권한을 획득하게 한다.

메모리 버퍼 오버플로우에는 스택 메모리 버퍼 오버플로우와 힙 메모리 버퍼 오버플로우가 있다.

진단 세부사항
(설계단계)
 
① C나 C++ 같이 메모리를 프로그래머가 관리하는 플랫폼을 사용하는 경우 메모리 버퍼의 경계값을 넘어서 메모리를 읽거나 저장하지 않도록 경계 설정 또는 검사를 반드시 수행해야 한다. 설계산출물을 검토하여 배열의 값을 다른 배열로 복사하여 넣는 경우 길이 검사 수행 방식이나 공통 함수가 설계되어 있는지 확인한다.

  ㅇ  리눅스 환경에서 ASLR(Address Space Layout Randomization), StackGuard와 같은 메모리 보호를 위한 설정을 사용하도록 설계되어 있는지 확인
  ㅇ  배열의 값을 다른 배열로 복사하여 넣는 경우, 처리되는 데이터의 길이를 검사하고 사용하도록 코딩규칙을 정의하였는지 확인
  ㅇ  할당된 메모리보다 더 큰 입력값을 사용하여 버퍼오버플로우가 발생되는지 점검할 수 있는 테스트계획의 수립 여부 확인


② 개발시, 메모리 버퍼오버플로우를 발생시킬 수 있는 취약한 API를 사용하지 않도록 통제해야 한다. 취약한 API를 정의하고 있는지 확인한다.
  ㅇ  메모리 버퍼오버플로우를 발생시킬 수 있는 API를 정의하고 사용하지 않도록 하는 코딩 규칙이 개발가이드에 정의되어 있는지 확인
보안대책
(구현단계)

printf(), snprintf() 등 포맷 문자열을 사용하는 함수를 사용할 때는 사용자 입력값을 직접적으로 포맷 문자열로 사용하거나 포맷 문자열 생성에 포함시키지 않는다.

포맷 문자열을 사용하는 함수에 사용자 입력값을 사용할 때는 사용자가 포맷 스트링을 변경할 수 있는 구조로 쓰지 않는다.

특히, %n, %hn은 공격자가 이를 이용해 특정 메모리 위치에 특정값을 변경할 수 있으므로 포맷 스트링 매개변수로 사용하지 않는다.

사용자 입력값을 포맷 문자열을 사용하는 함수에 사용할 때는 가능하면 %s 포맷 문자열을 지정하고, 사용자 입력값은 2번째 이후의 파라미터로 사용한다.

진단방법
(구현단계)

포맷 스트링을 이용하는 함수를 사용하는 경우 인자로 포맷 스트링이 포함되어 있는지 반드시 확인하고, 외부 입력 값이 포맷 스트링에 생성에 사용되는지 확인한다.

포맷 스트링이 함수의 인자로 포함 되어있고, 외부 입력 값이 포맷 스트링 생성에 사용되지 않으며, 포맷 스트링에서 사용하는 인자의 개수와 매개변수로 포함된 인자의 개수가 일치하는 경우 안전하다고 판단한다.

또한 포맷에 출력할 데이터의 길이를 반드시 한정하도록 한다. 그 외에는 모두 취약하다고 판단한다.

 

다. 코드예제

 

ㅇ 분석

// 외부 입력값에 포맷 문자열 포함 여부를 확인하지 않고 포맷 문자열 출력에 값으로 사용
// args[0]의 값으로 “%1$tY-%1$tm-%1$te"를 전달하면 시스템에서 가지고 있는 날짜(2014-10-14)
정보가 노출
1: import java.util.Calendar
2: ......
3: public static void main(String[] args) {
4: Calendar validDate = Calendar.getInstance();
5: validDate.set(2014, Calendar.OCTOBER, 14);
6: System.out.printf( args[0] + " did not match! HINT: It was issued on %1$terd of
some month", validate);
7: }

ㅇ 내용

1. args값을 받았는데 별 검증 없이 + 바로 printf에 사용함.

 

ㅇ 수정

// 외부 입력값이 포맷 문자열 출력에 사용되지 않도록 수정
1: import java.util.Calendar
2: :
3: public static void main(String[] args) {
4: Calendar validDate = Calendar.getInstance();
5: validDate.set(2014, Calendar.OCTOBER, 14);
6: System.out.printf("%s did not match! HINT: It was issued on %2$terd of some
month", args[0], validate);
7: }

ㅇ 내용

1. %s 포맷문자열 사용함


ㅇ 분석

1: void incorrect_password(const char *user) {
2: static const char msg_format[] = "%s cannot be authenticated.\n";
3: size_t len = strlen(user) + sizeof(msg_format);
4: char *msg = (char *)malloc(len);
5: if (msg == NULL) {
6: }
7: int ret = snprintf(msg, len, msg_format, user);
8: if (ret < 0 || ret >= len) {
/* 오류 처리 */
9: }
//
10: fprintf(stderr, msg);
11: free(msg);
12: msg = NULL;
13:}

ㅇ 내용

1. msg에 대해 검증 없이 + fprintf에 바로 적용함 (snprintf는 버퍼에 저장하는 명령어)

 

ㅇ 수정

1: void incorrect_password(const char *user) {
2: static const char msg_format[] = "%s cannot be authenticated.\n";
3: size_t len = strlen(user) + sizeof(msg_format);
4: char *msg = (char *)malloc(len);
5: if (msg == NULL) {
/* 오류 처리 */
6: }
7: int ret = snprintf(msg, len, msg_format, user);
8: if (ret < 0 || ret >= len) {
/* 오류 처리 */
9: }
//
10: if (fputs(msg, stderr) == EOF) {
/* 오류 처리 */
11: }
12: free(msg);
13: msg = NULL;
14:}

ㅇ 수정

1. fputs를 사용해 단순 msg 출력용으로 사용함.

 

 

 

 

끝.