Part 4. 에러 처리
58개 중 3개. 에러를 처리하지 않거나 불충분하게 처리하여, 에러정보에 중요정보(시스템 정보 등)가 포함될 때 발생한다. 관통 질문: 실패했을 때 무엇을 할지가 코드에 적혀 있는가? 그리고 그 실패 내용을 사용자에게도 보여주고 있는가? 가이드 원문 순서 그대로 진행.
1. 오류 메시지 통한 정보 노출 CWE-209
신뢰 경계: 환경변수·내부 경로 → 그대로 → 사용자에게 보이는 오류 메시지 ⚠️ 상세 에러는 로그(내부)로, 사용자(외부)에게는 단일하고 무의미한 메시지로. 정보 분리가 원칙이다.
공격 시나리오: 공격자는 노출된 경로·환경변수·실패 지점 정보를 조합해 시스템 환경을 정밀하게 수집하고 다음 공격의 정확도를 크게 높인다.
❌ Bad
char* path = getenv("MYPATH");
fprintf(stderr, path); /* 환경변수 경로를 그대로 노출 */✅ Good
char* path = getenv("MYPATH");
/* 사용자에게는 최대한 간단하게, 상세 정보는 내부 로그로만 기록 */치환 규칙: 예외 상황은 내부에서 처리하고 사용자에게는 일반화된 단일 메시지만 노출, 상세 내용은 접근 통제된 로그로 분리.
2. 오류상황 대응 부재 CWE-390
신뢰 경계: (해당 없음 — 처리 누락 자체가 결함) ⚠️ 빈 if 블록은 “검사했다”가 아니라 “오류를 은폐했다”는 뜻이다.
공격 시나리오: 오류가 발생할 수 있는 부분을 검사는 했지만 아무런 예외처리를 하지 않으면, 오류가 묻혀 잘못된 상태 위에서 이후 로직이 계속 수행되어 데이터 손상이나 보안 검사 우회로 이어진다.
❌ Bad
if (fputs("string", stdout) == EOF)
{
/* do nothing */ /* 검사는 하지만 아무 조치도 없음 */
}✅ Good
if (fputs("string", stdout) == EOF)
{
printLine("fputs failed!");
exit(1); /* 실제 조치: 로그 기록 · 복구 · 종료 */
}치환 규칙: 오류 검사 블록에는 반드시 실제 조치(로그·복구·종료)를 넣는다. 빈 오류 처리 블록은 코드 리뷰·정적분석 금지 항목으로 다룬다.
주의 — "예외처리 생략"이 실무에서 허용되는 경우
위 원칙에는 실무에서 흔히 받아들여지는 예외가 하나 있다: 로그 기록·
stdout/stderr출력 자체의 실패다.fprintf(stderr, ...)나 로그 호출이 실패했을 때(디스크 풀·파이프 끊김 등) 별다른 조치 없이 그냥 다음 줄로 넘어가는(continue) 코드가 실무에 많다.
- 근거: 로그·출력은 비즈니스 로직의 결과가 아니라 관찰(observability) 수단이다. 로그 쓰기 실패를 이유로 본 업무 로직까지 중단·복구 경로를 타면, 부차적인 문제(로깅 인프라 장애)가 오히려 주 서비스 가용성을 떨어뜨리는 역설이 생긴다.
- 단, “빈 블록이라 안전하다”는 뜻은 아니다 — 구분 기준은 “그 실패가 이후 로직의 정확성에 영향을 주는가”다. 인증 결과·트랜잭션 커밋 기록처럼 감사(audit) 요구사항이 걸린 로그라면 이 예외는 적용되지 않는다. 그 경우엔 CWE-390 원칙 그대로 실패를 처리해야 한다.
- 생략하더라도 최소한: (1) 실패 자체를 카운터·메트릭 등 어딘가에는 남겨 완전히 무음(silent)이 되지 않게 하고, (2) 반복 실패로 무한 재시도·busy loop에 빠지지 않게 한다.
- 요약: “비즈니스 로직과 무관한 실패는 continue”는 CWE-390 위반이 아니라 어떤 실패가 “처리해야 할 오류”인지를 먼저 구분한 결과다 — 구분 없이 전부 무시하는 것과는 다르다.
3. 적절하지 않은 예외처리 CWE-754
신뢰 경계:
fgets()실패(널 종료 미보장) → 반환값 검사 없이 →strcpy()로 이어짐 ⚠️ 반환값은 반드시 읽고, 실패했을 때 무엇을 할지까지 코드에 적혀 있어야 예외처리다.
공격 시나리오: fgets() 가 중간에 실패해 버퍼가 널로 끝나지 않으면 이어지는 strcpy() 에서 버퍼 오버플로우가 발생한다.
❌ Bad
char fromBuf[10], toBuf[10];
fgets(fromBuf, 10, stdin); /* 반환값을 검사하지 않는다 */
strcpy(toBuf, fromBuf); /* 에러로 '\0' 종료가 안 되면 오버플로우 */✅ Good
char *retBuf = fgets(fromBuf, 10, stdin);
if (retBuf != fromBuf) /* 함수호출 이후 결과 값을 비교 */
{
println("에러");
return;
}
strcpy(toBuf, fromBuf);치환 규칙: 값을 반환하는 모든 함수의 결과를 검사하고, 광범위한 예외처리 대신 상황별 구체적 처리, 실패 시 기본값 초기화 등 복구 경로를 마련한다.
Part 4 미니 체크리스트
- 사용자에게 보이는 오류 메시지에 경로·환경변수·스택 정보가 섞여 있는가
-
if (... == 에러) { }처럼 본문이 빈 오류 처리 블록이 있는가 - 반환값을 검사하지 않고 바로 다음 연산에 쓰는 함수 호출이 있는가(
fgets→strcpy등) - 실패 시 복구 경로(기본값·재시도·중단)가 코드에 명시되어 있는가