security
CHAPTER 92 / 96
읽기 약 2분
FUNCTION
포맷 스트링 취약점
핵심 개념
⚠️ 교육 목적. printf(user_input) 위험성 — %x로 스택 읽기, %n으로 메모리 쓰기. 방어: 포맷 스트링 하드코딩.
본문
포맷 스트링 취약점
// ❌ 매우 위험
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로 스택 읽기 (개념)
// ⚠️ 교육 목적
#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으로 메모리 쓰기 (개념)
// ⚠️ 교육 목적
#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;
}안전 코딩
// ❌ 절대 금지
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 사례 분석
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); // 포맷 하드코딩안전 매크로
#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 효과
# 모던 컴파일은 기본 적용 (-O2 이상)
gcc -O2 -D_FORTIFY_SOURCE=2 prog.c
# printf의 %n을 unsafe하게 사용한다면 런타임 abort
# *** %n in writable segment detected ***검증 체크리스트
// 코드 리뷰 시
□ 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