🐞 제거되지 않은 디버그 코드 시뮬레이터 KISA A-6 · 제거되지 않은 디버그 코드

디버그·테스트 코드가 운영에 그대로 배포된 상황입니다. ?debug=1이 설정/시크릿을 덤프하고, /test/login-as 백도어가 인증을 우회하며, 상세 로그가 실토큰을 출력합니다. 직접 켜 보고, 빌드 단계 제거가 모든 문을 닫는 것을 비교합니다.

① 디버그 코드가 그대로 포함된 운영 앱 VULNERABLE

이 mock 운영 서버에는 DEBUG 분기가 남아 있습니다. 엔드포인트를 골라 실행하면, 시크릿이 새고 자격증명 없이 인증이 우회됩니다.

② 동일 요청 — 강화된 운영 빌드 SECURE

릴리스 빌드는 컴파일 시 디버그 분기를 제거하고(if(false) → 데드코드 제거), ENV=production에서는 /test/* 마운트를 거부하며, 로그 레벨을 운영 설정으로 고정합니다. 디버그 문은 404/거부로 응답합니다.

③ 취약 vs 안전 코드

VULNERABLE
// debug code shipped to prod
final boolean DEBUG = true;            // never flipped off

@GetMapping("/account")
String account(HttpServletRequest r){
  if ("1".equals(r.getParameter("debug"))) {
    return dump(Config.ALL) +          // secrets!
           "\nstack=" + currentStack();
  }
  return view();
}

// hidden helper left in the router
@GetMapping("/test/login-as")
String loginAs(String user){
  grantAdmin(user);                    // auth bypass
  return "logged in as " + user;
}

log.debug("issued token=" + jwt);      // token in logs
SECURE
// debug gated by compile-time/env flag
static final boolean DEBUG =
    Boolean.parseBoolean(env("DEBUG","false")); // false in prod

@GetMapping("/account")
String account(HttpServletRequest r){
  // if(DEBUG) branch removed by dead-code
  // elimination when DEBUG is a final false
  return view();
}

// /test/* only mounted in non-prod profile
@Profile("!production")
@Configuration class TestRoutes { /* ... */ }

// secure log config: level fixed, secrets masked
log.debug("issued token={}", mask(jwt)); // ****
방어 핵심: 디버그 동작은 운영에서 false인 단일 설정 플래그로 제어 · 빌드 플래그/데드코드 제거로 릴리스 산출물에서 디버그 분기를 삭제 · 운영에는 /test/*·백도어 라우트를 절대 마운트하지 않음(프로파일/피처 게이트) · 인증 우회 지름길 금지 · 로그의 시크릿·토큰 마스킹 및 로그 레벨 운영 설정 고정 · 릴리스 전 잔존 디버그 마커 스캔/린트.
⚠️ 교육용 시뮬레이션입니다. 서버·시크릿·토큰·라우트는 모두 페이지 내부 mock이며 실제 시스템·자격증명·백도어는 없습니다.