소개
C 프로그래밍 세계에서 문자열을 안전하게 읽는 것은 심각한 보안 취약점을 방지하는 필수적인 기술입니다. 이 튜토리얼에서는 버퍼 오버플로우, 메모리 손상 및 잠재적인 시스템 공격으로 이어질 수 있는 일반적인 함정을 해결하며 문자열 입력을 안전하게 처리하는 기본적인 기술을 탐구합니다. 위험을 이해하고 강력한 입력 방법을 구현함으로써 개발자는 더욱 안전하고 신뢰할 수 있는 C 코드를 작성할 수 있습니다.
C 언어의 문자열 기본
C 언어에서 문자열이란 무엇인가?
C 언어에서 문자열은 널 문자 (\0) 로 끝나는 문자들의 시퀀스입니다. 일부 고급 프로그래밍 언어와 달리 C 언어에는 내장된 문자열 타입이 없습니다. 대신 문자열은 문자 배열로 표현됩니다.
문자열 선언 및 초기화
정적 문자열 선언
char str1[10] = "Hello"; // 널 종결자 자동 추가
char str2[] = "World"; // 크기 자동 결정
동적 문자열 할당
char *str3 = malloc(50 * sizeof(char));
strcpy(str3, "Dynamic allocation");
문자열 특징
| 특징 | 설명 |
|---|---|
| 널 종결 | 항상 \0으로 끝남 |
| 고정 크기 | 선언 시 크기 결정 |
| 불변성 | 직접 크기를 변경할 수 없음 |
일반적인 문자열 연산
문자열 길이
char message[] = "LabEx Tutorial";
int length = strlen(message); // 14 를 반환
문자열 복사
char dest[50];
strcpy(dest, "Hello, LabEx!");
메모리 고려 사항
graph TD
A[문자열 선언] --> B{정적 또는 동적?}
B -->|정적| C[스택 메모리]
B -->|동적| D[힙 메모리]
D --> E[free()를 반드시 호출해야 함]
주요 내용 요약
- C 언어의 문자열은 문자 배열입니다.
- 항상 널 종결자로 끝납니다.
- 메모리 관리에 주의해야 합니다.
- 조작을 위해 표준 라이브러리 함수를 사용합니다.
입력 취약점
일반적인 문자열 입력 위험
버퍼 오버플로우
버퍼 오버플로우는 입력이 미리 정의된 버퍼 크기를 초과할 때 발생하며, 시스템 충돌 또는 보안 위반을 초래할 수 있습니다.
char buffer[10];
scanf("%s", buffer); // 위험: 길이 제한 없음
취약점 예시
void unsafeInput() {
char name[10];
printf("Enter your name: ");
gets(name); // 절대 gets() 를 사용하지 마십시오. 매우 위험합니다!
}
입력 취약점 유형
| 취약점 유형 | 설명 | 위험 수준 |
|---|---|---|
| 버퍼 오버플로우 | 할당된 메모리 초과 | 높음 |
| 포맷 문자열 공격 | 포맷 지정자 조작 | 매우 높음 |
| 제한 없는 입력 | 입력 길이 검사 없음 | 높음 |
잠재적 결과
graph TD
A[안전하지 않은 입력] --> B[버퍼 오버플로우]
B --> C[메모리 손상]
C --> D[보안 취약점]
D --> E[잠재적인 시스템 손상]
실제 위험
스택 훼손
공격자는 과도한 입력을 제공하여 메모리 위치를 덮어쓸 수 있으며, 이는 악성 코드 실행으로 이어질 수 있습니다.
메모리 손상
통제되지 않은 입력은 다음과 같은 결과를 초래할 수 있습니다.
- 인접 메모리 덮어쓰기
- 프로그램 실행 흐름 변경
- 보안 취약점 생성
취약점 시연
#include <stdio.h>
#include <string.h>
void vulnerableFunction() {
char buffer[16];
printf("Enter data: ");
gets(buffer); // 위험한 함수
}
LabEx 보안 권장 사항
C 언어에서 문자열 입력을 다룰 때는 다음 사항을 준수하십시오.
- 항상 입력 길이를 검증하십시오.
- 안전한 입력 함수를 사용하십시오.
- 경계 검사를 구현하십시오.
gets()대신fgets()를 사용하십시오.
안전한 입력 관행
void safeInput() {
char buffer[50];
// 버퍼 크기에 맞춰 입력 제한
fgets(buffer, sizeof(buffer), stdin);
// 줄 바꿈 문자 제거
buffer[strcspn(buffer, "\n")] = 0;
}
주요 내용 요약
- 입력 검증은 필수적입니다.
- 사용자 입력을 절대 신뢰하지 마십시오.
- 안전한 입력 함수를 사용하십시오.
- 엄격한 경계 검사를 구현하십시오.
안전한 읽기 방법
권장 입력 함수
1. fgets() - 가장 안전한 표준 입력 방법
char buffer[100];
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
// 줄 바꿈 문자 제거
buffer[strcspn(buffer, "\n")] = 0;
}
입력 검증 기법
길이 검사
int safeStringRead(char *buffer, int maxLength) {
if (fgets(buffer, maxLength, stdin) == NULL) {
return 0; // 읽기 실패
}
// 줄 바꿈 문자 제거
buffer[strcspn(buffer, "\n")] = 0;
// 추가적인 길이 검증
if (strlen(buffer) >= maxLength - 1) {
// 오버플로우 처리
return 0;
}
return 1;
}
안전한 입력 방법 비교
| 방법 | 안전성 수준 | 장점 | 단점 |
|---|---|---|---|
| fgets() | 높음 | 입력 길이 제한 | 줄 바꿈 문자 포함 |
| scanf() | 중간 | 유연성 | 버퍼 오버플로우 가능성 |
| gets() | 안전하지 않음 | 더 이상 사용되지 않음 | 길이 검사 없음 |
입력 정제 흐름
graph TD
A[사용자 입력] --> B[길이 검사]
B --> C{제한 내?}
C -->|예| D[줄 바꿈 문자 제거]
C -->|아니오| E[입력 거부]
D --> F[내용 검증]
F --> G[입력 처리]
고급 입력 처리
동적 메모리 할당
char* safeDynamicRead(int maxLength) {
char* buffer = malloc(maxLength * sizeof(char));
if (buffer == NULL) {
return NULL; // 메모리 할당 실패
}
if (fgets(buffer, maxLength, stdin) == NULL) {
free(buffer);
return NULL;
}
// 줄 바꿈 문자 제거
buffer[strcspn(buffer, "\n")] = 0;
return buffer;
}
LabEx 보안 권장 사항
입력 검증 체크리스트
- 항상 최대 입력 길이를 설정하십시오.
- gets() 대신 fgets() 를 사용하십시오.
- 줄 바꿈 문자를 제거하십시오.
- 입력 내용을 검증하십시오.
- 잠재적인 오류를 처리하십시오.
오류 처리 예시
int processUserInput() {
char buffer[100];
if (!safeStringRead(buffer, sizeof(buffer))) {
fprintf(stderr, "입력 오류 또는 너무 깁니다.\n");
return 0;
}
// 추가적인 입력 검증
if (strlen(buffer) < 3) {
fprintf(stderr, "입력이 너무 짧습니다.\n");
return 0;
}
// 유효한 입력 처리
printf("유효한 입력: %s\n", buffer);
return 1;
}
주요 내용 요약
- 항상 입력 길이를 제한하십시오.
- 안전한 읽기를 위해 fgets() 를 사용하십시오.
- 철저한 입력 검증을 구현하십시오.
- 잠재적인 오류 시나리오를 처리하십시오.
- 사용자 입력을 무조건 신뢰하지 마십시오.
요약
C 에서 문자열을 안전하게 읽는 능력은 신중한 입력 검증, 안전한 읽기 방법, 그리고 메모리 관리에 대한 깊이 있는 이해를 결합한 포괄적인 접근 방식을 요구합니다. 이 튜토리얼에서 논의된 기술들을 구현함으로써 C 프로그래머는 보안 취약점의 위험을 크게 줄이고, 일반적인 입력 관련 위협으로부터 보호되는 더욱 강력한 애플리케이션을 만들 수 있습니다.



