KCSA — 쿠버네티스 보안 어소시에이트 · 쿠버네티스 위협 모델 · 이론
STRIDE 를 쿠버네티스에 대입하기
한 줄 요약
위협 모델링은 "무엇이 무서운가"를 감으로 나열하는 대신 분류 체계로 빠짐없이 훑는 방법입니다.
STRIDE 여섯 항목을 쿠버네티스 오브젝트에 대입하면 점검 항목이 저절로 나옵니다.
왜 이게 필요했나
보안 검토가 사람마다 다른 결론을 내는 이유는 각자 자기가 아는 위협만 보기 때문입니다.
네트워크 담당자는 방화벽을, 개발자는 인젝션을 봅니다. STRIDE 는 "이 여섯 가지를 다 물어봤는가"
라는 체크리스트를 줍니다. 빠뜨림을 구조적으로 줄이는 것이 목적입니다.
어떻게 동작하나
여섯 항목의 쿠버네티스 번역
| STRIDE | 뜻 | 쿠버네티스에서의 모습 | 주 대응 |
| --- | --- | --- | --- |
| Spoofing | 신원 위장 | SA 토큰 탈취, 인증서 위조, --as 사칭 | 강한 인증, 토큰 수명 단축, impersonate 권한 통제 |
| Tampering | 변조 | 이미지 교체, etcd 직접 쓰기, 매니페스트 조작 | 이미지 서명, etcd 접근 통제, GitOps |
| Repudiation | 부인 | 누가 했는지 증명 못 함 | 감사 로그, 개별 신원(공유 계정 금지) |
| Information Disclosure | 정보 노출 | Secret 유출, kubelet 읽기 포트, 로그의 자격증명 | 저장 시 암호화, RBAC, 로그 위생 |
| Denial of Service | 서비스 거부 | 리소스 고갈, apiserver 폭주, 웹훅 장애 | ResourceQuota, LimitRange, PDB, API 우선순위 |
| Elevation of Privilege | 권한 상승 | privileged 파드, hostPath, RBAC 오설정 | PSA, 최소 권한, 어드미션 정책 |
권한 상승 경로 — KCSA 의 핵심
권한 상승은 "관리자 권한을 직접 받는 것"이 아니라 "관리자가 아닌 권한에서 관리자로 가는 경로"
입니다. 대표 경로 넷을 외워 두세요.
1) pods/exec 또는 pods/attach
파드에 들어갈 수 있으면 그 파드의 ServiceAccount 토큰을 읽을 수 있습니다.
그 SA 가 나보다 권한이 높으면 그 권한을 그대로 얻습니다. 디버깅 편의로 준 권한이
"클러스터 안의 모든 SA 중 가장 센 것"으로 가는 사다리가 됩니다.
2) secrets 읽기
Secret 에는 다른 SA 의 토큰, DB 자격증명, 외부 API 키가 들어 있습니다.
네임스페이스에 get secrets 가 있으면 그 네임스페이스의 모든 Secret 을 읽습니다.
3) rolebindings/clusterrolebindings 에 대한 create
스스로에게 더 높은 권한을 부여할 수 있습니다. 쿠버네티스는 이걸 알고 있어서 기본적으로
자기가 갖지 않은 권한은 부여할 수 없게 막습니다(권한 상승 방지). 그 방어를 뚫는 게 다음 항목입니다.
4) escalate, bind, impersonate 동사
escalate— 자기가 갖지 않은 권한도 Role 에 넣을 수 있게 하는 메타 권한bind— 자기가 갖지 않은 Role 을 바인딩할 수 있게 함impersonate— 다른 사용자/그룹/SA 를 사칭.system:masters를 사칭하면 끝입니다
그리고 파드 스펙 자체가 상승 경로입니다.
privileged: true→ 사실상 노드 roothostPath로/마운트 → 노드 파일시스템 전체(kubelet 인증서 포함)hostNetwork/hostPID→ 노드의 네트워크·프로세스 네임스페이스capabilities.add: [SYS_ADMIN]→ 컨테이너 탈출 도구상자
"파드를 만들 수 있다"는 것은 잠재적으로 "그 노드를 소유할 수 있다"에 가깝습니다.
그래서 PSA 나 정책 엔진으로 파드 스펙을 제한하는 것이 RBAC 만큼 중요합니다.
공급망 위협
내가 클러스터를 아무리 잘 잠가도, 내가 자발적으로 받아 실행하는 것에는 소용이 없습니다.
- 베이스 이미지 — 취약한 라이브러리가 그대로 들어옴
- 태그 이동 —
myapp:1.4.2가 어제와 오늘 다른 콘텐츠를 가리킬 수 있음 - 의존성 오염 — typosquatting, 계정 탈취로 인한 악성 버전 배포
- 빌드 파이프라인 — CI 가 클러스터 배포 권한을 갖고 있으므로 CI 침해 = 클러스터 침해
- 빌드 인자 잔존 —
--build-arg로 넣은 비밀은docker history --no-trunc로 그대로 나옵니다.
이후 레이어에서 파일을 지워도 이전 레이어에 남고, 푸시했다면 그 이미지를 받은 모두가 읽습니다
대응은 출처와 무결성입니다 — 신뢰하는 레지스트리만 허용, 다이제스트 고정, 이미지 서명 검증,
SBOM 으로 무엇이 들어 있는지 목록화.
지속성(persistence) 확보 기법
침입에 성공한 공격자는 다시 들어올 길을 만들어 둡니다. 정리해 두면 탐지 항목이 됩니다.
- DaemonSet 배치 — 모든 노드에 하나씩, 노드가 추가되면 자동으로 따라감
- CronJob — 주기적으로 되살아나는 백도어
- 어드미션 웹훅 등록 — 모든 오브젝트 생성을 가로채고 몰래 변형(mutating)할 수 있음
- ClusterRoleBinding 추가 — 눈에 잘 안 띄는 이름으로 cluster-admin 을 붙여 둠
- SA 토큰 확보 — 장기 토큰 Secret 을 만들어 두면 계정을 지워도 살아남을 수 있음
- 정적 파드 — 노드의 매니페스트 디렉터리에 파일을 놓으면 apiserver 를 거치지 않고 실행됩니다.
(--service-account-lookup=false 인 클러스터에서 특히)
kubectl 에서는 정상적인 미러 파드로 보입니다
현장에서 만나는 모습
저자의 블로그가 정리한 RBAC 사고 패턴 중 가장 흔한 것 — "모든 ServiceAccount 가 클러스터
리소스를 읽을 수 있는 비정상적인 상태"가 발견되는 경우입니다. 진단은kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name=="cluster-admin")'
로 cluster-admin 바인딩 주체를 전수 조사하는 것이고, 이건 정확히 지속성 기법 탐지와 같은 쿼리입니다.
공급망 쪽에서는 더 실감 나는 사례가 있습니다. docker build --build-arg NPM_TOKEN=... 로 넣은
토큰이 docker history --no-trunc 에 그대로 나오는 것입니다.RUN 단계에서 파일을 지워도 이전 레이어에 남습니다. 올바른 방법은RUN --mount=type=secret,id=npmrc,... 처럼 빌드 시크릿 마운트를 쓰는 것입니다.
그리고 시크릿 유출 대응에서 순서에 대한 저자의 지적이 위협 모델링과 맞닿습니다.
가장 흔한 반응은 Git 히스토리 재작성인데 순서가 틀렸습니다.
공개 저장소에 푸시된 자격증명은 수 초에서 수 분 안에 자동화된 스캐너에 수집되고,
히스토리를 지워도 포크·기존 클론·플랫폼이 보관하는 PR 참조·검색 캐시·CI 아티팩트에 남습니다.
폐기(revocation)가 먼저이고, 히스토리 정리는 재발을 줄이는 위생 작업입니다.
위협 모델링이 대응 순서까지 바꾸는 예입니다.
다음 퀴즈에서 확인할 것
이 모듈은 퀴즈로 마무리합니다. 다음 모듈에서 파드 스펙 기반 권한 상승을 실제로 막는 장치인
PSA 를 다루고, 실습에서 restricted 위반 파드가 거부되는 것을 직접 확인합니다.