OPEN HYPER STEP
← 목록으로 (화이트햇 보안)
SECURITY · 92 / 96
security
CHAPTER 92 / 96
읽기 약 2
FUNCTION

포맷 스트링 취약점


핵심 개념

⚠️ 교육 목적. printf(user_input) 위험성 — %x로 스택 읽기, %n으로 메모리 쓰기. 방어: 포맷 스트링 하드코딩.

본문

포맷 스트링 취약점

C📋 코드 (15줄)
// ❌ 매우 위험
char input[100];
fgets(input, sizeof(input), stdin);
printf(input);  // 사용자가 포맷 지정자 직접 제어!

// 사용자 입력: %x %x %x %x
// → printf가 스택의 인접 값을 16진수로 출력
// → 다른 함수의 비밀 값, 리턴 주소 등 노출


// ✅ 안전 — 포맷 스트링 하드코딩
printf("%s", input);
// 또는
fputs(input, stdout);
puts(input);

%x로 스택 읽기 (개념)

C📋 코드 (27줄)
// ⚠️ 교육 목적
#include <stdio.h>
#include <string.h>

void demo(const char *user_input) {
    char secret[] = "PASSWORD123";  // 스택에 위치
    char buffer[64];
    strncpy(buffer, user_input, 63);
    buffer[63] = '\0';

    // ❌ 취약 — user_input이 "%x %x %x %x %x %s" 같으면
    printf(buffer);
    printf("\n");
}

int main(int argc, char *argv[]) {
    if (argc != 2) return 1;
    demo(argv[1]);
    return 0;
}

// 실행:
// $ ./vuln "AAA %x %x %x %x"
// → AAA <16진수1> <16진수2> ... 스택 메모리 노출
//
// $ ./vuln "%s"
// → 임의 주소를 문자열로 해석 → 크래시 또는 정보 노출

%n으로 메모리 쓰기 (개념)

C📋 코드 (16줄)
// ⚠️ 교육 목적
#include <stdio.h>

int main(void) {
    int count;
    printf("Hello%n World\n", &count);
    // %n: 지금까지 출력한 바이트 수를 count에 저장
    printf("count = %d\n", count);  // 5 (Hello)

    // ⚠️ 공격: printf("%100x%n", ..., target_addr);
    //   → target_addr에 100 쓰임 (4바이트)
    //   → return address 같은 곳을 임의 값으로 변조 가능

    // 모던 glibc는 %n을 위치 기반에서 차단 (FORTIFY_SOURCE)
    return 0;
}

안전 코딩

C📋 코드 (17줄)
// ❌ 절대 금지
printf(user_input);
printf(unknown_format);

// ✅ 항상 포맷 스트링 하드코딩
printf("%s", user_input);
printf("값: %d, 이름: %s", num, name);

// 컴파일러 경고 활성화
// gcc -Wformat=2 -Wformat-security
// → printf(var) 같은 패턴 경고/에러


// 안전 함수 사용
fputs(str, stdout);
puts(str);
fwrite(buf, 1, len, stdout);

CVE 사례 분석

📋 코드 (11줄)
CVE-2000-0573 — wu-ftpd 포맷 스트링 → 원격 root
CVE-2002-1561 — washington imapd
CVE-2004-0180 — proftpd

공통 패턴:
syslog(LOG_NOTICE, user_input);
→ syslog도 printf 계열이라 동일 취약점


수정 후:
syslog(LOG_NOTICE, "%s", user_input);  // 포맷 하드코딩

안전 매크로

C📋 코드 (17줄)
#include <stdio.h>

// 컴파일 타임 검사 — 포맷 스트링이 const literal인지
#define SAFE_PRINTF(fmt, ...) \
    _Static_assert(__builtin_constant_p(fmt), \
                   "format string must be a literal"); \
    printf(fmt, ##__VA_ARGS__)


int main(void) {
    SAFE_PRINTF("Hello, %s!\n", "World");  // OK

    char user[] = "user input";
    // SAFE_PRINTF(user);  // 컴파일 에러

    return 0;
}

fortify source 효과

BASH📋 코드 (5줄)
# 모던 컴파일은 기본 적용 (-O2 이상)
gcc -O2 -D_FORTIFY_SOURCE=2 prog.c

# printf의 %n을 unsafe하게 사용한다면 런타임 abort
# *** %n in writable segment detected ***

검증 체크리스트

C📋 코드 (11줄)
// 코드 리뷰 시
□ printf(var) 패턴 0건
□ syslog/fprintf/sprintf 등 가변 포맷 0건
□ -Wformat=2 -Wformat-security 컴파일 옵션
□ -D_FORTIFY_SOURCE=2 활성
□ 사용자 입력은 항상 "%s" 등 포맷 지정자 통과


# 자동 검사
$ grep -rn "printf\s*(\s*[a-zA-Z_]" src/  # 의심 패턴
$ gcc -Wformat=2 -Wformat-security src/*.c

다음 챕터

CH.9 "힙 오버플로우와 Use-After-Free" — 힙에서 발생하는 취약점.


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 C 코드에서 모든 printf 계열 호출을
분석해서 포맷 스트링 취약점 가능 위치를
자동 찾아줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

실제 CVE 5개의 포맷 스트링 익스플로잇
원리와 패치 비교를 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 C 프로젝트에 -Wformat=2 +
FORTIFY_SOURCE 적용 가이드와 발견된
경고 처리 우선순위를 만들어줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 포맷 스트링 취약점 발생률과
실제 익스플로잇 사례 빈도를 솔직히 알려줘.

⭐ 이것만 기억하세요
포맷 스트링 취약점 이 3가지만 확실히 잡으세요
1.포맷 스트링 취약점은 printf(user_input) 패턴 — %x로 스택 읽기, %n으로 메모리 쓰기 가능
2.방어는 단순 — 포맷 스트링은 항상 const literal, 사용자 입력은 "%s" 등으로 통과
3.다음 챕터 CH.9에서 힙 오버플로우와 UAF — 힙 영역의 대표 취약점


공유하기
진행도 92 / 96