Proven by Intelligence
보이지 않는 안전을 인텔리전스로 증명하다.
기술 인사이트를 만나보세요.
CWE - 658/659 정복하기 (2)
ㅣ CWE-14: Compiler Removal of Code to Clear Buffers
CWE-14는 메모리에 저장된 중요한 정보를 사용한 뒤 제거하는 작업을 수행하는 코드가 컴파일러 최적화로 인해 삭제되어, 중요 정보가 메모리에 그대로 잔존하는 문제를 말합니다.
아래 예시는 사용자로부터 암호를 입력 받은 후, 메인 프레임에 연결을 위해 암호를 사용한 뒤, memset() 함수를 통해 메모리에 저장된 암호의 값을 ‘0’으로 초기화하는 코드입니다.

사용자에게 입력 받은 암호는 변수 pwd에 저장되어 통신을 위한 정보로 사용됩니다.
이후 사용된 pwd는 메모리에 저장된 중요 정보의 내용을 지우기 위해 memset() 함수를 통해 ‘0’ 값으로 덮어씌우는 작업을 수행합니다.
위 예시는 소스 코드만 확인할 경우 개발자가 의도한 대로 pwd 변수를 사용한 뒤 정상적으로 초기화가 되는 것으로 보입니다.
그러나 컴파일러 최적화 옵션을 적용하여 컴파일을 수행할 경우, pwd 변수의 값을 ‘0’으로 초기화하는 memset() 함수의 작업이 불필요한 코드로 인식되며, 해당 부분의 소스 코드는 컴파일러를 통해 삭제됩니다.
이는 컴파일러가 불필요한 작업을 제거해버리는 최적화 활동을 수행하는 것으로 memset() 작업을 통해 ‘0’으로 초기화된 pwd 변수가 GetData() 함수에서 더 이상 사용되지 않는다고 판단하기 때문입니다. 컴파일러 최적화로 인해 코드가 제거되는 문제는 사실 컴파일러 최적화를 사용하지 않는 것이 가장 기본적인 문제의 해결 방안 입니다.
그러나 개발의 편의성을 위해 최적화를 회피할 수 없을 경우, 중요 정보가 담긴 변수는 volatile 키워드를 선언하여 정보를 저장하고 사용하도록 해야 합니다.
일반적으로 많은 개발자들이 시스템 또는 중요 정보 탈취를 방지하기 위해 이러한 방식으로 코드를 작성하여 데이터를 덮어쓰는 작업을 구현합니다.
그러나 위와 같이 컴파일러 최적화로 인해 작성된 모든 소스 코드가 실행되지 않는 의도하지 않은 현상이 발생하기도 하며, 심지어 특정 프로그램에서 사용된 메모리에 접근하거나 이를 복구하는 방식을 통해 외부로부터 중요한 정보가 탈취되기도 합니다.
또한 탈취된 중요 정보는 외부에 노출되거나 혹은 또 다른 정보에 접근하도록 하여 프로그램의 실행을 중단 시키고 서비스 거부 등의 심각한 문제를 발생 시킬 수 있습니다.
ㅣ CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
CWE-119는 소프트웨어 작업 수행 시, 메모리 버퍼의 의도된 범위 밖에서 참조가 발생하는 문제를 말합니다.
C나 CPP의 경우, 메모리의 직접 주소를 통해 데이터에 접근할 수 있지만 참조된 메모리에 대한 유효성 검사는 자동으로 수행되지 않습니다.
이러한 경우 다른 변수나 데이터 구조 또는 내부 시스템 메모리 등 의도하지 않은 위치에서 데이터 참조 작업이 수행될 수 있습니다.
잘못된 메모리 버퍼의 접근은 외부 공격으로부터 임의의 코드가 실행되도록 만들거나, 코드의 제어 흐름 변경, 중요 정보 탈취, 시스템 중단 등과 같은 SW의 무결성, 보안성 그리고 가용성에 대한 문제를 발생 시킵니다.

위 예시 코드는 배열에 저장된 아이템을 선택하기 위해 사용자로부터 offset 값을 입력 받고 있습니다.
소스 코드 개발자는 사용자로부터 목록에서 선택할 요소를 지정하도록 의도 하였으나, 공격자가 배열의 범위를 벗어난 offset을 주입할 경우 의도하지 않은 부분의 버퍼에 접근하게 되어 버퍼 오버 플로우(Over-Read)가 발생합니다.
이는 입력 받는 offset 값에 대한 범위를 지정한 뒤 유효한 offset 값이 참조될 수 있도록 방어 코드를 함께 작성하여 범위 밖의 데이터가 참조되지 않도록 예방할 수 있습니다.
이처럼 올바르지 않은 위치에 접근하여 의도하지 않은 데이터의 변경 또는 에러가 발생하는 현상을 일반적으로 ‘Memory Corruption’ 이라고 하는데, 올바르지 않은 포인터 연산, 잘못된 초기화 또는 메모리 해제로 인한 무효한 포인터를 통한 접근 등이 포함됩니다.
CWE는 이러한 문제를 검사하기 위한 다양한 방안을 제안하고 있는데, 가장 효과적인 방안으로 자동화된 정적 분석 도구를 사용하는 것을 권장하고 있습니다.
정적 분석 도구 CodeSonar는 CWE-119에 대한 위험을 검사하고 Buffer Overrun/Underrun, Type Overrun/Underrun, Tainted Buffer Access 등 다양한 결함으로 검출하여 사용자에게 소스 코드에 내재한 버그와 취약점을 보여줍니다.
ㅣ CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
CWE-120은 프로그램이 입력 버퍼의 사이즈 검사없이 출력 버퍼에 복사를 수행하여 버퍼 오버 플로우가 발생하는 문제를 말합니다.
버퍼 오버 플로우가 발생하는 가장 간단한 형태이자 일반적인 원인은 데이터의 복사 범위를 지정하거나 제한하지 않고 버퍼를 복사하는 것입니다.
이와 같은 기본적인 즉, 'Classic’한 오버 플로우가 존재한다는 것은 많은 개발자나 개발 업체들이 가장 기본적인 보안에 대한 방어나 대안을 구축하지 않고 있다는 것을 의미하기도 합니다.

위 예제 코드는 C 라이브러리 함수인 strcpy 함수를 통해 문자열을 복사하고 있습니다.
strcpy 함수는 문자열 복사를 수행하는 대표적인 함수입니다. 그러나 복사할 데이터의 사이즈를 지정하지 않고 복사를 수행하므로 버퍼 오버 플로우를 유발합니다.
이처럼 strcpy 함수는 저장할 데이터에 대한 사이즈를 지정하지 않기 때문에 buf 보다 큰 사이즈의 string 데이터가 복사될 경우 버퍼 오버 플로우가 발생할 수 있습니다.
이와 같은 사유로 MSDN(Microsoft Developer Network)에서는 strcpy 함수 대신 데이터의 크기를 지정하여 복사를 수행하는 strcpy_s 함수를 대신 사용하도록 권장하고 있습니다.
버퍼 오버 플로우는 요구사항 단계부터 설계, 디자인, 코드 구현 및 빌드 단계까지 개발 언어와 환경 등 다양한 관점에서 접근하여 사전적인 예방을 할 수 있습니다.
위 예제 코드에서 설명한 ‘strcpy’ 함수와 같이 오버 플로우를 유발하는 라이브러리 함수를 사용하지 않도록 디자인 또는 설계 단계에서 언어, 라이브러리, 프레임워크 등을 선정함으로써 사전에 문제를 예방할 수 있습니다.
또한 CWE-120은 CWE-199과 같이 자동화된 정적 분석 도구를 통해 효과적으로 결함을 검사할 수 있습니다.
더불어 바이너리 분석을 통해 잘못된 메모리 접근을 검사하여 오버 플로우를 검사할 수 있습니다.
정적 분석 도구 CodeSonar는 소스 코드와 바이너리 분석을 모두 지원하며 개발된 소스 코드와 더불어 Third Party와 같은 외부 코드에 내재한 버그를 검사하고 검출합니다.
ㅣ CWE-121: Stack-based Buffer Overflow / CWE-122: Heap-based Buffer Overflow
CWE-121과 CWE-122는 스택과 힙 메모리 영역에서 발생하는 오버 플로우를 말합니다.
오버 플로우가 발생하는 메모리 영역에 따라 구분되고 있지만, 2가지 모두 의도하지 않은 영역의 메모리에 접근하기 때문에 시스템 충돌이나 종료, 임의의 코드 실행을 통한 오작동을 유발하는 위험한 취약점입니다.


위 2가지의 예제 코드에서는 모두 오버 플로우가 발생합니다.
입력된 문자열에서 ‘&’ 문자를 찾아인코딩을 시도하지만, 주어진 문자의 4배 확장된 dst_buf 배열에 ‘&’의 인코딩은 5배씩 확장하는 작업을 수행하고 있습니다.
이때 공격자가 ‘&’ 문자가 다수 포함된 문자열을 입력할 경우, 인코딩 작업 수행으로 인해 오버 플로우가 발생됩니다.
이는 요구 사항이나 설계 단계 시, 인코딩으로 인해 추가되는 데이터의 크기를 계산하여 개발자가 충분한 크기의 배열을 선언하거나, 사전 조건을 통해 배열의 크기에 따른 올바른 Index가 입력될 수 있도록 코드를 수정해야 합니다.
위 2가지 예제 속 오버 플로우가 발생하는 메모리 영역은 상이합니다.
첫 번째 예제는 인코딩 후 문자열이 저장되는 메모리가 스택 메모리 영역이지만, 두 번째 예제는 인코딩 후 문자열이 저장되는 메모리가 힙 메모리 영역입니다.
이처럼 오버 플로우가 발생하는 위치에 따라 Stack 또는 Heap 기반의 오버 플로우를 구분할 수 있습니다.
정적 분석 도구 CodeSonar는 오버 플로우가 발생한 소스 코드에 대하여 사용자에게 문제가 발생한 코드의 흐름을 제공하며, 더불어 데이터 사이즈, 메모리 영역 등 다양한 정보를 제공함으로써 효과적인 결함 검출 및 제거 활동을 돕습니다.
출처 : CWE 웹사이트 (http://cwe.mitre.org/index.html)
