개념
도구는 "이미 알려진 것"만 찾는다
모듈 16(레슨 6)에서 SAST·DAST·SCA를 배웠다. 세 도구는 각각 코드 패턴, 실행 중 응답, 의존성 목록을 본다. 공통점이 있다. 셋 다 누군가 이미 규칙으로 정리해 둔 취약점 을 찾는다는 것이다. 그래서 도구는 "쿠폰을 두 번 적용할 수 있다", "탈퇴한 회원의 배송지가 다른 계정에서 조회된다", "관리자 승인 없이 정산 금액을 수정할 수 있다" 같은 것을 절대 찾지 못한다. 이런 것들은 우리 도메인에만 있는 규칙이 깨진 경우이고, 규칙을 아는 사람은 도구가 아니라 우리다.
위협 모델링은 그 규칙을 테스트 대상으로 바꾸는 절차다. 정의는 간단하다. 시스템을 그리고, 그 그림의 각 부분을 누가 어떻게 망칠 수 있는지 체계적으로 열거하고, 무엇부터 확인할지 정하는 것 이다. 테스터에게 이 활동은 낯설지 않다. 모듈 4의 위험 기반 테스트에서 "무엇이 잘못될 수 있는가"를 물었던 것과 구조가 같고, 다만 주체가 사용자의 실수가 아니라 의도를 가진 공격자 라는 점이 다르다.
| 위험 기반 테스트 | 위협 모델링 | |
|---|---|---|
| 잘못의 원인 | 개발 실수, 사용자 오조작, 환경 변화 | 이득을 노리는 행위자의 의도 |
| 빈도 추정 | 과거 결함 이력·변경량 | 공격 난이도와 공격자의 동기 |
| "정상 입력" 가정 | 대체로 유효 | 무효 — 입력은 조작된다고 본다 |
| 산출물 | 위험 기반 우선순위 테스트 계획 | 위협 목록과 그에 대응하는 점검 케이스 |
1단계 — 데이터 흐름도(DFD)와 신뢰 경계
위협 모델링의 그림은 아키텍처 다이어그램이 아니다. 데이터가 어디서 와서 어디로 가고, 어디에 머무는지 를 그린 데이터 흐름도(DFD)다. 구성 요소는 네 가지뿐이다.
| 기호 | 요소 | 예 |
|---|---|---|
| 사각형 | 외부 엔터티(External Entity) | 사용자, 파트너사 서버, 결제 PG |
| 원 | 프로세스(Process) | 주문 API 서버, 배치 워커, 관리자 웹 |
| 두 줄 | 데이터 저장소(Data Store) | orders 테이블, S3 버킷, Redis 세션 |
| 화살표 | 데이터 흐름(Data Flow) | HTTPS 요청, DB 쿼리, 메시지 큐 발행 |
여기에 하나를 더 그린다. 신뢰 경계(Trust Boundary) — 신뢰 수준이 달라지는 선이다. 위협의 대부분은 이 선을 넘는 지점에서 생긴다.
[사용자 브라우저] [파트너 정산 서버]
| |
========|=========== 신뢰 경계 A ===============|==========
| (인터넷 → 우리 인프라) |
v v
( API 게이트웨이 ) --------------------> ( 정산 배치 )
| | |
| JWT 검증 | 내부 호출(인증 없음) |
v v v
( 주문 서비스 ) --> ( 쿠폰 서비스 ) [[ settlements ]]
|
========|=========== 신뢰 경계 B ===============
| (앱 계층 → 데이터 계층)
v
[[ orders 테이블 ]] [[ Redis 세션 ]]이 그림 한 장만 그려도 질문이 저절로 나온다. "내부 호출은 인증이 없다고 했는데, 게이트웨이를 우회해 쿠폰 서비스에 직접 요청하면?" 이 질문이 곧 테스트 케이스다. 실제 현장에서 신뢰 경계를 그릴 때 가장 자주 드러나는 사실이 두 가지 있다. 하나는 아무도 정확한 그림을 갖고 있지 않다는 것 이고, 다른 하나는 "내부망이라 안전하다"는 가정이 문서화된 적 없다는 것 이다.
2단계 — STRIDE로 요소별 위협 열거
STRIDE는 마이크로소프트가 정리한 위협 분류다. 여섯 범주이고, 각 범주는 깨지는 보안 속성과 짝을 이룬다.
| 약자 | 위협 | 깨지는 속성 | 우리 시스템에서의 예 |
|---|---|---|---|
| Spoofing | 신원 위장 | 인증(Authentication) | 만료된 JWT를 서명 검증 없이 받아들임 |
| Tampering | 변조 | 무결성(Integrity) | 클라이언트가 보낸 price 값을 서버가 그대로 사용 |
| Repudiation | 부인 | 부인 방지(Non-repudiation) | 정산 금액 수정 이력이 남지 않음 |
| Information Disclosure | 정보 노출 | 기밀성(Confidentiality) | 오류 응답에 SQL 쿼리와 테이블명 포함 |
| Denial of Service | 서비스 거부 | 가용성(Availability) | 페이지 크기 제한 없는 목록 API |
| Elevation of Privilege | 권한 상승 | 인가(Authorization) | 일반 사용자가 관리자 엔드포인트 호출 성공 |
STRIDE의 실용적 가치는 체크리스트로 쓸 수 있다는 것 이다. DFD의 요소 종류마다 어떤 범주를 우선 보는지가 정해져 있다.
| 요소 | S | T | R | I | D | E |
|---|---|---|---|---|---|---|
| 외부 엔터티 | O | O | ||||
| 프로세스 | O | O | O | O | O | O |
| 데이터 저장소 | O | O | O | O | ||
| 데이터 흐름 | O | O | O |
표를 그대로 읽으면 "Redis 세션(데이터 저장소)에 대해서는 T·R·I·D 네 범주를 묻는다"가 된다. 묻는 방식은 항상 같은 문장이다.
<요소>에 대해 <STRIDE 범주>가 발생한다면, 공격자는 무엇을 얻는가?
그것을 막고 있다고 믿는 통제는 무엇인가?
그 통제가 실제로 작동하는지 어떻게 확인하는가? ← 이 줄이 테스트 케이스가 된다세 번째 줄이 핵심이다. 위협 모델링이 문서로 끝나는 팀과 테스트로 이어지는 팀의 차이가 여기서 갈린다.
요소: Redis 세션 저장소
범주: I (정보 노출)
위협: 세션 키를 알면 다른 사용자의 세션 값을 읽을 수 있다
통제: Redis는 VPC 내부에만 노출, AUTH 설정
확인: (1) VPC 밖에서 6379 포트 접근 시도 → 차단 확인
(2) AUTH 없이 접속 시도 → 거부 확인
(3) 세션 키가 예측 가능한 순번인지 확인 → 난수 엔트로피 확인3단계 — 공격 트리로 경로 분해
STRIDE가 넓게 열거하는 도구라면 공격 트리(Attack Tree)는 깊게 파는 도구다. 뿌리에 공격자의 목표를 두고, 그 목표를 이루는 하위 방법을 AND/OR로 분해한다.
목표: 다른 사용자의 주문 상세를 본다
├─ OR ─ 주문 조회 API에서 권한 검사를 우회한다
│ ├─ orderId를 남의 것으로 바꿔 호출한다 [IDOR]
│ ├─ 내 계정의 JWT에 담긴 userId를 조작한다
│ │ └─ AND ─ 서명 알고리즘을 none으로 바꿀 수 있다
│ │ + 서버가 alg 헤더를 신뢰한다
│ └─ 관리자 전용 조회 엔드포인트를 일반 계정으로 호출한다 [BFLA]
├─ OR ─ 남의 세션을 탈취한다
│ ├─ XSS로 세션 쿠키를 읽는다 (HttpOnly 미설정 시)
│ ├─ 로그아웃 후에도 토큰이 살아 있다
│ └─ 세션 고정(Session Fixation) — 로그인 시 세션 재발급 없음
└─ OR ─ 우회 경로로 데이터를 얻는다
├─ 주문 내역 CSV 내보내기에 권한 검사가 없다
├─ 검색 API의 응답에 다른 사용자 필드가 섞여 있다
└─ 고객센터 관리자 화면이 인증 없이 열린다공격 트리의 값은 가장 싼 경로를 드러낸다는 것 이다. JWT 서명 조작은 라이브러리가 제대로 되어 있으면 비용이 매우 높다. 반면 "CSV 내보내기에 권한 검사 누락"은 누구나 5분이면 확인한다. 공격자는 트리에서 가장 싼 가지를 고르고, 우리는 그 순서대로 테스트한다. 잎마다 비용·필요 권한·탐지 가능성을 붙이면 우선순위가 그대로 나온다.
| 잎 노드 | 필요 권한 | 난이도 | 탐지 여부 | 점검 우선순위 |
|---|---|---|---|---|
| CSV 내보내기 권한 누락 | 로그인 계정 | 낮음 | 로그에 안 남음 | 1 |
| IDOR (orderId 변경) | 로그인 계정 | 낮음 | 접근 로그 있음 | 1 |
| 로그아웃 후 토큰 유효 | 로그인 계정 | 낮음 | 안 남음 | 2 |
| 관리자 엔드포인트 호출 | 로그인 계정 | 중간 | 감사 로그 있음 | 2 |
| JWT alg=none | 없음 | 높음 | 안 남음 | 3 |
4단계 — 위험 등급, 그리고 DREAD를 쓰지 않는 이유
위협 목록을 만들면 반드시 "그래서 뭐가 제일 급한가"라는 질문이 온다. 오래된 교재는 DREAD(Damage·Reproducibility·Exploitability·Affected users·Discoverability)를 소개하지만, 마이크로소프트도 실무에서 걷어냈고 현장에서도 잘 쓰이지 않는다. 이유는 점수가 주관적이고 사람마다 결과가 달라 재현되지 않기 때문 이다. 현재 현장에서 쓰이는 방식은 두 가지다.
- 발생 가능성 × 영향 매트릭스 — 위협 모델링 단계처럼 아직 실제 취약점이 확인되지 않았을 때. 정성 등급(상·중·하)으로 충분하다
- CVSS — 실제 취약점이 확인된 뒤 심각도를 표준 점수로 매길 때(모듈 23 레슨 3에서 다뤘다)
위협 모델링 단계에서는 전자를 쓴다. 주의할 점은 영향을 기술적 영향이 아니라 사업적 영향 으로 적는 것이다.
나쁜 서술: "권한 검사가 없다" → 무엇이 문제인지 판단 불가
좋은 서술: "로그인한 누구나 전체 회원의 배송지와 연락처를
CSV로 내려받을 수 있다 (약 42만 건, 개인정보 유출 신고 대상)"두 번째 문장은 그 자체로 등급을 정해 준다. 이것이 이 모듈의 주제 문장 — 보안 테스트의 가치는 취약점 목록이 아니라 실제 위험의 판정에 있다 — 의 출발점이다.
언제, 얼마나 자주 하는가
위협 모델링을 전체 시스템에 대해 한 번 크게 하고 끝내면 반드시 낡는다. 현실적인 운영 방식은 두 겹이다.
| 주기 | 범위 | 참가자 | 산출물 |
|---|---|---|---|
| 연 1~2회 | 서비스 전체 DFD 갱신 | 아키텍트·보안·QA 리드 | 신뢰 경계 지도, 상위 위협 목록 |
| 변경 시마다 | 바뀐 흐름만 | 기능 담당 개발자 + QA | 위협 3~5개와 점검 케이스 |
두 번째가 실제로 효과를 낸다. 신뢰 경계를 새로 넘는 변경 — 새 외부 연동, 새 권한 역할, 새 파일 업로드, 새 웹훅 수신 — 이 생길 때만 30분짜리 세션을 여는 규칙이면 충분히 굴러간다.
실무에서 왜 필요한가
첫째, 테스트 범위를 방어할 수 있게 된다. "보안 테스트 다 했나요"라는 질문에 "OWASP Top 10 다 봤습니다"는 답이 되지 않는다. Top 10은 일반적인 목록이고 우리 도메인의 쿠폰 중복 적용은 거기에 없다. DFD와 위협 목록이 있으면 "신뢰 경계 A를 넘는 흐름 11개 중 9개를 점검했고, 남은 2개는 이번 스프린트 범위 밖"처럼 덮은 것과 안 덮은 것을 구분해서 말할 수 있다.
둘째, 개발팀과 대화가 된다. 취약점 티켓은 종종 "그건 내부망이라 괜찮다"로 닫힌다. 위협 모델의 신뢰 경계가 그려져 있으면 그 주장이 검증 가능한 문장이 된다. "내부망 가정이 맞다면, 게이트웨이를 우회해 이 서비스에 직접 도달하는 경로가 없어야 한다. 확인해 보자"로 대화가 바뀐다. 실제로 확인해 보면 우회 경로가 있는 경우가 흔하다.
셋째, 설계 단계에서 잡힌다. 위협 모델링은 코드가 없어도 할 수 있는 유일한 보안 활동이다. 스펙 리뷰에 30분짜리 STRIDE 세션을 붙이면, 구현 후에 발견하면 재설계가 필요한 문제를 설계 문서 수정으로 끝낼 수 있다. 모듈 34에서 본 제품 사이버보안 규격들도 같은 구조를 요구한다 — 자산을 식별하고, 위협을 도출하고, 통제를 정하고, 통제가 작동하는지 시험한다.