본문 바로가기
Vulnerability

[Vulnerability] CWE-787: Out-of-Bounds Write - 메모리 경계 위반 취약점

by Tru5tC0der 2025. 11. 2.

 

CWE-787: Out-of-Bounds Write는 버퍼의 유효한 경계를 벗어나 데이터를 쓰는 취약점으로, C/C++ 계열 프로그램에서 자주 발생하며 메모리 손상(Memory Corruption), 프로그램 충돌(Crash), 원격 코드 실행(RCE)으로 이어질 수 있습니다. 이 취약점은 CWE Top 25에서 지속적으로 상위권을 유지하고 있으며, KEV(Known Exploited Vulnerabilities) 목록에 18개의 CVE가 등재되어 실제 공격에 활발히 악용되고 있습니다.


목차

  • CWE-787 개요와 현황
  • 취약점 발생 메커니즘과 유형
  • 실제 공격 사례와 CVE 분석
  • 탐지 및 분석 방법론
  • 완화 전략과 방어 기법
  • 개발 생명주기 통합 방안
  • 결론 및 실무 가이드

CWE-787 개요와 현황

정의와 핵심 특징

CWE-787: Out-of-Bounds Write는 프로그램이 의도된 버퍼의 끝을 넘어서거나 시작 이전에 데이터를 쓰는 취약점입니다. 이는 CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer)의 하위 분류로, 메모리 안전성 위반의 대표적인 사례입니다.

현재 위험도와 트렌드

지표 수치 설명
KEV CVEs 18개 CISA KEV 목록 등재 CVE 수
CWE Top 25 순위 #1 (2024) 2022년 #3에서 지속 상승
익스플로잇 가능성 High 높은 공격 성공률
영향 범위 Critical RCE, 메모리 손상, 시스템 장악

주요 영향 플랫폼

  • C/C++ 애플리케이션: 가장 빈번한 발생
  • 시스템 소프트웨어: 커널, 드라이버, 펌웨어
  • 웹 브라우저: JavaScript 엔진, 렌더링 엔진
  • ICS/OT 시스템: 산업 제어 시스템

취약점 발생 메커니즘과 유형

주요 발생 원인

Out-of-Bounds Write는 다음과 같은 상황에서 발생합니다:

1. 배열 인덱스 검증 부족

// 취약한 코드 예시
void vulnerable_array_access(int index) {
    int buffer[10];

    // ❌ 인덱스 범위 검증 없음
    buffer[index] = 42;  // index가 0-9 범위를 벗어날 수 있음
}

// 안전한 코드
void safe_array_access(int index) {
    int buffer[10];

    // ✅ 경계 검사 수행
    if (index >= 0 && index < 10) {
        buffer[index] = 42;
    } else {
        // 에러 처리
        handle_bounds_error(index);
    }
}

2. 동적 버퍼 크기 계산 오류

// 취약한 코드
char* create_buffer(size_t count) {
    // ❌ 정수 오버플로우 가능
    char* buf = malloc(count * sizeof(char));

    for (size_t i = 0; i <= count; i++) {  // ❌ off-by-one 오류
        buf[i] = 'A';
    }
    return buf;
}

// 안전한 코드
char* create_safe_buffer(size_t count) {
    // ✅ 오버플로우 검사
    if (count == 0 || count > SIZE_MAX / sizeof(char)) {
        return NULL;
    }

    char* buf = malloc(count * sizeof(char));
    if (!buf) return NULL;

    // ✅ 올바른 범위
    for (size_t i = 0; i < count; i++) {
        buf[i] = 'A';
    }
    return buf;
}

3. 안전하지 않은 문자열 함수 사용

// 취약한 코드
void vulnerable_string_copy(char *input) {
    char buffer[64];

    // ❌ 입력 길이 검증 없음
    strcpy(buffer, input);  // input이 64자를 초과하면 오버플로우
}

// 안전한 코드
void safe_string_copy(const char *input) {
    char buffer[64];

    if (!input) return;

    // ✅ 안전한 함수 사용
    strncpy(buffer, input, sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0';  // NULL 종료 보장
}

메모리 레이아웃과 공격 벡터

스택 기반 버퍼 오버플로우

void stack_overflow_example() {
    char buffer[256];
    char *user_input = get_user_input();

    // ❌ 스택 오버플로우 발생 가능
    strcpy(buffer, user_input);

    // 공격자가 return address를 덮어쓸 수 있음:
    // [buffer][saved ebp][return addr] <- 스택 레이아웃
}

힙 기반 버퍼 오버플로우

typedef struct {
    char data[100];
    void (*function_ptr)(void);
} heap_struct;

void heap_overflow_example() {
    heap_struct *obj = malloc(sizeof(heap_struct));
    char *malicious_input = get_malicious_input();

    // ❌ 힙 메타데이터나 인접 객체 손상 가능
    strcpy(obj->data, malicious_input);

    // function_ptr이 덮어써져 임의 코드 실행 가능
    obj->function_ptr();
}

실제 공격 사례와 CVE 분석

주요 CVE 사례 분석

CVE-2022-22620: WebKit Use-After-Free / OOB Write

CVE_ID: CVE-2022-22620
Product: WebKit (Safari)
Impact: Remote Code Execution
Attack_Vector: Malicious Web Page
Root_Cause: JavaScript 엔진의 메모리 관리 오류
Exploitation: 
  - 악성 웹페이지 방문
  - JavaScript 엔진 메모리 손상
  - 샌드박스 탈출 및 임의 코드 실행
Mitigation:
  - Apple 긴급 보안 업데이트
  - 브라우저 샌드박스 강화

CVE-2021-21220: Chrome V8 Engine Heap Corruption

// 공격 시나리오 (의사 코드)
function exploit_v8_oob() {
    // 1. 힙 스프레이로 메모리 레이아웃 조작
    let spray_array = new Array(0x1000);

    // 2. 타입 컨퓨전으로 경계 검사 우회
    let confused_array = trigger_type_confusion();

    // 3. Out-of-bounds write로 객체 메타데이터 손상
    confused_array[0x100] = crafted_object;

    // 4. 임의 코드 실행
    execute_payload();
}

CVE-2020-17087: Windows Kernel Pool Overflow

// 커널 풀 오버플로우 시나리오
NTSTATUS vulnerable_kernel_function(PVOID user_buffer, ULONG size) {
    // ❌ 정수 절단으로 작은 버퍼 할당
    USHORT truncated_size = (USHORT)size;  // CWE-197

    PVOID kernel_buffer = ExAllocatePool(PagedPool, truncated_size);  // CWE-131

    if (kernel_buffer) {
        // ❌ 원본 크기로 복사하여 오버플로우 발생
        RtlCopyMemory(kernel_buffer, user_buffer, size);  // CWE-787
    }

    return STATUS_SUCCESS;
}

공격 체인 분석

전형적인 Out-of-Bounds Write 공격 체인:

  1. 정보 수집: 메모리 레이아웃 파악
  2. 트리거: 취약한 함수 호출
  3. 익스플로잇: 메모리 손상을 통한 제어권 탈취
  4. 페이로드: 쉘코드 실행 또는 권한 상승

탐지 및 분석 방법론

정적 분석 도구

1. 컴파일 타임 검사

# GCC/Clang 정적 분석 옵션
gcc -Wall -Wextra -Warray-bounds -Wstringop-overflow \
    -fstack-protector-strong -D_FORTIFY_SOURCE=2 \
    vulnerable_code.c -o safe_binary

# Clang Static Analyzer
clang --analyze -Xanalyzer -analyzer-checker=security \
    vulnerable_code.c

2. 고급 정적 분석 도구

도구 특징 적용 분야
CodeQL 의미론적 분석, 대규모 코드베이스 오픈소스, 엔터프라이즈
Coverity 상용 도구, 높은 정확도 미션크리티컬 시스템
SonarQube 개발 워크플로우 통합 DevSecOps 파이프라인
PC-lint Plus C/C++ 전문, 임베디드 시스템 항공우주, 자동차

동적 분석 및 런타임 탐지

AddressSanitizer (ASan)

// ASan 활성화 컴파일
// gcc -fsanitize=address -g vulnerable_code.c

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[10];

    // ASan이 다음 라인에서 오버플로우를 탐지
    strcpy(buffer, "This string is longer than 10 characters!");

    return 0;
}

// ASan 출력 예시:
// ==1234==ERROR: AddressSanitizer: stack-buffer-overflow
// WRITE of size 44 at 0x7fff12345678 thread T0
//     #0 0x... in main vulnerable_code.c:8

Valgrind Memcheck

# Valgrind를 이용한 메모리 오류 탐지
valgrind --tool=memcheck --leak-check=full \
         --show-leak-kinds=all ./vulnerable_program

# 출력 예시:
# ==1234== Invalid write of size 4
# ==1234==    at 0x...: main (vulnerable_code.c:15)
# ==1234==  Address 0x... is 0 bytes after a block of size 40 alloc'd

하드웨어 기반 보호 기법

// Intel CET (Control Flow Enforcement Technology)
#ifdef __CET__
#include <immintrin.h>

void protected_function() {
    // 간접 분기 보호
    _endbr64();  // ENDBR instruction for IBT

    // 함수 로직
    sensitive_operation();

    // 리턴 주소 보호는 자동으로 처리됨 (Shadow Stack)
}
#endif

퍼징(Fuzzing) 기법

AFL++ 활용

# AFL++ 컴파일 및 실행
CC=afl-gcc ./configure
make

# 퍼징 실행
afl-fuzz -i input_corpus -o findings ./target_program @@

# 크래시 분석
afl-analyze -i findings/crashes/id:000000* ./target_program

libFuzzer 통합

// libFuzzer 타겟 함수
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size < 4) return 0;

    // 퍼징 대상 함수 호출
    parse_network_packet(data, size);

    return 0;
}

// 컴파일: clang++ -fsanitize=fuzzer,address fuzzer_target.cc

완화 전략과 방어 기법

1. 언어 수준 보호

메모리 안전 언어 도입

// Rust - 컴파일 타임 메모리 안전성 보장
fn safe_buffer_access(data: &[u8], index: usize) -> Option<u8> {
    // bounds checking이 자동으로 수행됨
    data.get(index).copied()  // panic 대신 Option 반환
}

// C++의 안전한 대안
#include <array>
#include <span>

void safe_cpp_access(std::span<const int> buffer, size_t index) {
    // at() 함수는 경계 검사 수행
    if (index < buffer.size()) {
        int value = buffer[index];  // 안전한 접근
    }
}

안전한 라이브러리 함수

// 안전한 문자열 처리 (Microsoft SafeStr)
#include <strsafe.h>

HRESULT safe_string_operations(LPCSTR source) {
    CHAR destination[256];

    // StringCchCopy는 버퍼 크기 검증
    return StringCchCopy(destination, 
                        ARRAYSIZE(destination), 
                        source);
}

// POSIX 안전 함수들
void secure_memory_operations(const void* src, size_t src_size) {
    char dest[100];

    // 크기 제한 복사
    if (src_size < sizeof(dest)) {
        memcpy_s(dest, sizeof(dest), src, src_size);
    }
}

2. 컴파일러 수준 보호

스택 카나리 (Stack Canaries)

// GCC/Clang 스택 보호 활성화
// -fstack-protector-strong 옵션

void protected_function(char* user_input) {
    // 컴파일러가 자동으로 카나리 값 삽입
    char local_buffer[256];

    // 취약한 연산
    strcpy(local_buffer, user_input);

    // 함수 종료 시 카나리 값 검증
    // 변조 시 __stack_chk_fail() 호출
}

FORTIFY_SOURCE

// 컴파일 시: -D_FORTIFY_SOURCE=2

#include <string.h>

void fortified_example(char* src) {
    char dest[50];

    // FORTIFY_SOURCE가 활성화되면
    // strcpy가 __strcpy_chk로 대체되어 경계 검사 수행
    strcpy(dest, src);  // 런타임에 오버플로우 탐지
}

3. 운영체제 수준 보호

ASLR (Address Space Layout Randomization)

# Linux ASLR 설정
echo 2 > /proc/sys/kernel/randomize_va_space

# Windows ASLR 확인
dumpbin /headers /loadconfig program.exe | findstr "Dynamic base"

DEP/NX Bit (Data Execution Prevention)

// 페이지 권한 설정 예시
#include <sys/mman.h>

void setup_secure_memory() {
    void* code_region = mmap(NULL, PAGE_SIZE, 
                           PROT_READ | PROT_EXEC,  // 실행 가능, 쓰기 불가
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    void* data_region = mmap(NULL, PAGE_SIZE,
                           PROT_READ | PROT_WRITE,  // 쓰기 가능, 실행 불가
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}

Control Flow Integrity (CFI)

// Clang CFI 활성화: -fsanitize=cfi

// 간접 호출 보호
typedef int (*function_ptr)(int);

int secure_indirect_call(function_ptr fp, int arg) {
    // CFI가 함수 포인터 유효성 검증
    return fp(arg);  // 유효하지 않은 대상 시 중단
}

4. 하드웨어 기반 보호

Intel CET (Control-flow Enforcement Technology)

; Intel CET - Indirect Branch Tracking
endbr64    ; 유효한 간접 분기 대상 마킹

; Shadow Stack - 리턴 주소 보호
call function
; 리턴 주소가 shadow stack에도 저장됨
ret        ; shadow stack과 일반 스택 비교

ARM Pointer Authentication

// ARM64 포인터 인증
#ifdef __ARM_FEATURE_PAC_DEFAULT

void* authenticated_return_address() {
    // 리턴 주소에 인증 코드 추가
    return __builtin_return_address(0);
}

#endif

개발 생명주기 통합 방안

Secure Development Lifecycle (SDL) 통합

1. 요구사항 단계

security_requirements:
  memory_safety:
    - "모든 버퍼 접근에 경계 검사 필수"
    - "동적 메모리 할당 시 정수 오버플로우 검증"
    - "외부 입력에 대한 길이 제한 적용"

  tool_integration:
    - "정적 분석 도구 의무 사용"
    - "AddressSanitizer 빌드 테스트"
    - "퍼징 테스트 케이스 작성"

2. 설계 단계

// 안전한 API 설계 원칙
typedef struct {
    size_t capacity;
    size_t length;
    char data[];
} safe_string_t;

// 경계 검사가 내장된 API
int safe_string_append(safe_string_t* str, const char* src, size_t src_len) {
    if (!str || !src) return -1;

    // 용량 검사
    if (str->length + src_len >= str->capacity) {
        return -1;  // 버퍼 오버플로우 방지
    }

    memcpy(str->data + str->length, src, src_len);
    str->length += src_len;
    str->data[str->length] = '\0';

    return 0;
}

3. 구현 단계

# Makefile 보안 플래그 설정
SECURITY_CFLAGS = -Wall -Wextra -Werror \
                  -fstack-protector-strong \
                  -D_FORTIFY_SOURCE=2 \
                  -fPIE -pie \
                  -Wl,-z,relro,-z,now

DEBUG_CFLAGS = -fsanitize=address,undefined \
               -fno-omit-frame-pointer \
               -g3

secure_build:
    $(CC) $(SECURITY_CFLAGS) $(DEBUG_CFLAGS) -o secure_app *.c

4. 테스트 단계

# 자동화된 보안 테스트
import subprocess
import os

def run_security_tests():
    test_cases = [
        "run_static_analysis",
        "run_asan_tests", 
        "run_fuzzing_suite",
        "run_valgrind_checks"
    ]

    for test in test_cases:
        result = subprocess.run([test], capture_output=True)
        if result.returncode != 0:
            raise Exception(f"Security test {test} failed")

def run_asan_tests():
    env = os.environ.copy()
    env['ASAN_OPTIONS'] = 'detect_odr_violation=1:abort_on_error=1'

    return subprocess.run(['./test_suite'], env=env)

CI/CD 파이프라인 통합

GitHub Actions 예시

name: Security Analysis Pipeline

on: [push, pull_request]

jobs:
  security-scan:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Static Analysis
      run: |
        clang --analyze --analyzer-output text *.c
        cppcheck --enable=all --error-exitcode=1 *.c

    - name: AddressSanitizer Build
      run: |
        gcc -fsanitize=address -g -o test_asan test_suite.c
        ./test_asan

    - name: Fuzzing Test
      run: |
        clang -fsanitize=fuzzer,address -o fuzzer fuzzer_target.c
        timeout 300 ./fuzzer corpus/ || true

    - name: CodeQL Analysis
      uses: github/codeql-action/analyze@v2
      with:
        languages: cpp
        queries: security-and-quality

결론

CWE-787: Out-of-Bounds Write는 현재 가장 위험하고 빈번하게 발생하는 취약점 중 하나로, 체계적인 접근이 필요합니다. 메모리 안전성 확보를 위해서는 기술적 완화 방안프로세스 개선을 동시에 추진해야 합니다.

핵심 권장사항

  • 언어 선택: 가능한 메모리 안전 언어(Rust, Go, Java) 도입 검토
  • 도구 활용: 정적/동적 분석 도구를 개발 워크플로우에 필수 통합
  • 컴파일러 보호: 모든 보호 기법(Stack Canaries, ASLR, CFI) 활성화
  • 교육 강화: 개발자 대상 메모리 안전성 교육 정기 실시
  • 지속적 모니터링: 런타임 보호와 모니터링 시스템 구축

앞으로는 "메모리 안전성(Memory Safety)"이 보안 코딩의 핵심 지표가 될 것입니다. C/C++ 프로젝트에서는 정적 분석과 하드웨어 보호 기법을 반드시 병행하여 Defense in Depth 전략을 구현해야 합니다.


실무 체크리스트

개발 단계별 점검사항

코딩 단계

  • 모든 배열 접근에 경계 검사 구현
  • 안전한 문자열 함수 사용 (strncpy, snprintf 등)
  • 동적 메모리 할당 시 정수 오버플로우 검증
  • 입력 데이터 길이 제한 및 검증
  • 포인터 역참조 전 NULL 검사

빌드 단계

  • 컴파일러 보안 플래그 활성화 (-fstack-protector-strong, -D_FORTIFY_SOURCE=2)
  • AddressSanitizer 빌드 테스트 수행
  • 정적 분석 도구 통과 확인
  • 링커 보안 옵션 적용 (-z relro, -z now, -fPIE)

테스트 단계

  • 퍼징 테스트 케이스 작성 및 실행
  • Valgrind 메모리 검사 통과
  • 경계값 테스트 케이스 포함
  • 부하 테스트에서 메모리 누수 확인

배포 단계

  • ASLR 활성화 확인
  • DEP/NX bit 설정 검증
  • 런타임 보호 모니터링 구성
  • 보안 패치 적용 프로세스 수립

긴급 대응 가이드

취약점 발견 시

  1. 즉시 조치: 영향 받는 시스템 격리 및 패치 적용
  2. 영향 분석: 데이터 유출 여부 및 피해 범위 조사
  3. 근본 원인 분석: 코드 리뷰 및 프로세스 개선점 도출
  4. 재발 방지: 유사 패턴 전수 조사 및 자동 탐지 규칙 추가

참고 자료