보안 테스팅, 개발 프로세스, 여정
- 7.1 보안 테스팅이란 무엇인가
- 7.2 goto fail 취약점에 대한 보안 테스팅
- 7.3 보안 테스트 케이스 작성
- 7.4 퍼즈 테스팅
- 7.5 보안 회귀 테스트
- 7.6 가용성 테스팅
- 7.7 보안 테스팅 모범사례
- 7.8 코드 품질
- 7.9 의존성
- 7.10 취약점 분류
- 7.11 안전한 개발환경 유지
- 7.12 실천하고 행동할 것
- 7.13 보안의 미래
- 7.14 라스트 마일: 마지막 한걸음 완수
- 7.15 마치며
"안 되어야 할 것이 정말 안 되나?" — 보안 테스팅부터 개발 프로세스, 그리고 보안을 실제로 실천하는 여정까지 마무리하자.
기능 테스트와 보안 테스트는 질문이 근본적으로 달라. 기능 테스트는 "이게 되나?"를 묻지만, 보안 테스트는 **"안 되어야 할 것이 정말 안 되나?"**를 묻거든. goto fail이 테스트에서 안 잡힌 이유가 뭐였냐면, 정상 케이스만 테스트했기 때문이야. 유효한 인증서로 SSL 연결이 성공하는지만 확인했지, 잘못된 인증서로 연결이 거부되는지는 안 봤어. 만료된 인증서, 자체 서명 인증서, 불완전한 인증서 체인 — 이런 실패 케이스를 테스트했다면 바로 잡혔을 거야. 코드 커버리지 분석만 했어도, 서명 검증 코드가 도달 불가능하다는 걸 발견했을 거고. 해피 패스만 테스트하면 보안은 검증되지 않아.
그래서 보안 테스트는 **적대적 사고방식(adversarial mindset)**이 필요해. 경계값 테스트(최솟값-1, 최댓값+1), 악의적 입력(SQL 인젝션 페이로드, XSS 스크립트, 경로 탐색), 인증/인가 우회 시도(다른 사용자 데이터 접근, 관리자 기능 호출), 비즈니스 로직 악용(가격 음수, 결제 단계 건너뛰기) — 이런 것들을 체계적으로 던져봐야 해. 위협 모델링 결과를 입력으로 삼아서, 식별된 위협마다 "이게 실현되면 시스템이 안전하게 대응하나?"를 검증하는 거야.
여기서 퍼즈 테스팅이 강력한 도구가 돼. 사람이 생각하지 못하는 엣지 케이스를 기계적으로 탐색하니까. 특히 그레이박스 퍼징(AFL, libFuzzer)이 실용적이야 — 커버리지 피드백으로 새로운 코드 경로를 탐색하는 입력을 진화시키거든. Google의 OSS-Fuzz가 오픈소스에서 수만 건의 버그를 찾아낸 게 그 증거야. 파서, 디코더, 직렬화 라이브러리 같은 복잡한 입력 처리 코드에 특히 효과적이지.
그리고 취약점 한 번 수정했으면, 그 트리거 테스트를 영구적으로 CI에 포함시켜야 해. 보안 회귀 테스트야. CVE 번호를 주석으로 달아서 왜 이 테스트가 존재하는지 기록하고, 패치를 되돌리면 실패하는지 확인하고. 같은 실수를 두 번 하지 않겠다는 의지를 코드로 축적하는 거야.
가용성(CIA의 A)도 잊으면 안 돼. 스트레스 테스트에서 시스템이 우아하게 실패하는지, DoS 공격(Slowloris, XML Bomb)에 자원을 적절히 제한하는지, 인증 서버 다운 시 "일단 통과시키자"가 안 되는지 — 에러 상태에서 보안 정책이 무너지면 안 되거든. 이 모든 걸 CI/CD에 자동화해서 포함시키는 게 핵심이야. SAST는 빌드 시, DAST는 스테이징에서, 의존성 스캔은 상시. 수동이면 결국 빠지고, 빠지면 뚫려.
테스트만으로는 부족해 — 프로세스가 뒷받침하지 않으면 한계가 있어. 첫 번째는 코드 품질. 읽기 어려운 코드, 복잡한 코드에 보안 취약점이 숨을 확률이 높아. SonarQube, Semgrep, CodeQL 같은 SAST 도구를 빌드 파이프라인에 넣고, 코드 리뷰에 보안 체크리스트를 포함시키고, 위험한 함수 사용 금지(strcpy, eval()), 로그에 비밀번호 남기지 않기 같은 코딩 표준을 팀 단위로 적용해야 해. 순환 복잡도가 높은 함수는 리뷰어도 놓치기 쉽고, 버그가 숨기 좋은 환경이니까 리팩토링 대상이야.
두 번째는 의존성. 내 코드가 완벽해도 의존성에 취약점이 있으면 끝이야. Log4Shell이 그걸 극적으로 보여줬지. 원칙은 간단해 — 최소한의 의존성만 쓰고(left-pad를 기억해), 락 파일로 버전 고정하고, Dependabot이나 Snyk으로 자동 취약점 스캔하고, 보안 패치는 즉시 적용. 요즘은 공급망 공격도 심각해 — 타이포스쿼팅으로 비슷한 이름의 악성 패키지를 올리거나, 기존 패키지 권한을 탈취해서 악성 코드를 심는 거야. SBOM(소프트웨어 구성 목록)을 관리하면 새 취약점 공개 시 "우리 영향받나?"를 빠르게 판단할 수 있어.
세 번째는 취약점이 터졌을 때의 대응 체계. CVSS로 심각도를 정량화하고(9.0 이상이면 Critical), CWE로 유형을 분류하고(CWE-79는 XSS, CWE-89는 SQL 인젝션), CVE로 개별 취약점을 식별해. 접수 → 영향 분석 → 패치 → 공개 → 사후 분석의 프로세스가 있어야 하고, 책임 있는 공개(90일 기한)도 업계 표준이야.
그리고 놓치기 쉬운 게 개발환경 자체의 보안이야. SolarWinds 해킹은 빌드 파이프라인이 뚫려서 악성 코드가 제품에 삽입된 사례거든. Git에 비밀키 커밋 안 하기(git-secrets, gitleaks), CI/CD를 최소 권한으로 실행하기, 코드 서명으로 빌드 산출물 무결성 보장, 비밀은 Vault 같은 전용 시스템으로 관리 — 이런 게 다 코드를 만드는 환경을 지키는 거야.
마지막으로, 보안에 대해 아는 것과 실제로 하는 것 사이에는 간극이 있어. 저자는 이걸 **"라스트 마일"**이라고 불러. 일정 압박, 인센티브 부조화, 복잡성 — 이유는 뻔하지만, 보안 사고 터지면 수습에 드는 시간은 비교할 수 없이 크잖아. "아무 일도 안 일어나게 하는 것"의 가치를 인정해야 해. 극복 방법은 세 가지야. 자동화 — pre-commit 훅으로 비밀 커밋 차단, CI에서 SAST/DAST 자동 실행. 측정 — 평균 취약점 수정 시간, 열려 있는 취약점 수 같은 지표를 추적. 문화 — 보안 사고를 비난이 아니라 학습 기회로 삼는 블레임리스 포스트모템.
미래 트렌드도 이 방향이야. 메모리 안전 언어(Rust, Go, Swift)로 CVE의 가장 큰 비율이 구조적으로 제거되고, 제로 트러스트로 "사내 네트워크니까 안전하다"는 가정이 사라지고, SLSA 같은 공급망 보안 프레임워크가 빌드 무결성을 보장하고, 보안이 법적 의무가 되어가고 있어. 작은 것부터 시작하면 돼. 팀에 보안 챔피언을 두면 전문 보안 팀이 못 커버하는 일상적인 보안을 잡을 수 있어. 저자가 끝까지 하고 싶은 말은 이거야 — 보안은 특별한 전문가만의 영역이 아니라, 모든 소프트웨어 엔지니어의 기본 소양이다. 완벽하게 안전한 소프트웨어는 없어. 하지만 어제보다 오늘 조금 더 안전하면 그것이 진보야. 목적지가 아니라 여정이거든.
정리
7장 읽고 기억할 거 세 가지:
- 보안 테스트는 "안 되어야 할 것이 안 되나"를 묻는 거야. 해피 패스만 테스트하면 goto fail을 놓치고, 퍼즈 테스팅과 회귀 테스트를 CI/CD에 자동화해야 해.
- 의존성은 공격 표면이고, 개발환경 자체가 공격 대상이 될 수 있다. 코드 품질, 공급망 보안, 빌드 파이프라인 — 코드를 만드는 곳이 뚫리면 제품이 뚫려.
- 아는 것과 하는 것의 간극을 자동화, 측정, 문화로 메워야 한다. 보안은 목적지가 아니라 여정이고, 완벽을 기다리지 말고 오늘 시작해.