diff --git a/docs/session-store-lab-roadmap.md b/docs/session-store-lab-roadmap.md index 414ec6d..ec65c17 100644 --- a/docs/session-store-lab-roadmap.md +++ b/docs/session-store-lab-roadmap.md @@ -75,8 +75,52 @@ if [ "$expected_count" -ne 39 ]; then | Traefik `forwardedHeaders.trustedIPs` | 받은 헤더를 **전달할지 버릴지** | | 앱 `forward-headers-strategy` / `KC_PROXY_HEADERS` | 도착한 헤더를 **읽을지** | -여기에 **프록시 우회 차단**(NetworkPolicy)까지 더해야 신뢰 경계가 닫힌다. -"헤더를 믿는다"와 "앞에 반드시 프록시가 있다"는 한 쌍이다. +**발견한 취약점 — 헤더 신뢰를 켠 순간 위조가 통했다** + +세 스위치를 다 켜고 나니 새 구멍이 생겼다. Traefik을 거치지 않고 앱에 직접 +요청하면서 헤더를 붙이자 **그대로 통과했다.** + +```bash +kubectl -n header-lab run t --rm -i --restart=Never --image=curlimages/curl -- \ + curl -s http://echo:8081/api/echo \ + -H 'X-Forwarded-Proto: https' -H 'X-Forwarded-Host: evil.example.com' -H 'X-Forwarded-For: 1.2.3.4' +``` + +``` +serverName evil.example.com ← 위조 성공 +remoteAddr 1.2.3.4 ← 위조 성공 +requestUrl https://evil.example.com/api/echo +``` + +**원인은 두 신뢰 설정이 모두 "대역"을 믿기 때문이다.** + +| 계층 | 신뢰 범위 | +|---|---| +| Traefik `trustedIPs` | 파드 대역 전체 (`10.42.0.0/16`) | +| 앱 Tomcat `internalProxies` | 사설 대역 전체 (기본 정규식) | + +IP로는 Traefik을 특정할 수 없다. **파드 IP가 재시작마다 바뀌기 때문**이다 +(측정 중 실제로 `10.42.0.8` → `10.42.1.12`로, 노드까지 옮겨갔다). +그래서 대역을 믿는 것으로 대신하게 되고, 그 대역 안의 **아무 파드나** 같은 +신뢰를 얻는다. + +**막은 방법 — NetworkPolicy는 IP가 아니라 라벨로 지정한다.** + +```yaml +- from: + - namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: kube-system } } + podSelector: { matchLabels: { app.kubernetes.io/name: traefik } } +``` + +차단 후 재측정에서 정상 경로는 그대로 동작하고 우회는 `curl exit 7` +(연결 자체 실패)로 막혔다. 파드 재시작은 0회 — kubelet probe 경로를 +노드 cni0 주소 `/32`로 따로 허용했기 때문이다. 이 규칙을 빠뜨리면 +probe가 실패해 **파드가 재시작 루프에 빠진다.** + +**"헤더를 믿는다"와 "앞에 반드시 프록시가 있다"는 한 쌍이다.** +앞의 것만 하면 위조 경로가 열린 채로 남는다. 이 교훈이 AP4에서 결정적이 +된다 — `X-Auth-Request-*`가 위조되면 그것은 쿠키 속성이 아니라 +**신원 위조**이기 때문이다. **다음 작업에 미치는 영향** — Keycloak을 올릴 때 `KC_PROXY_HEADERS=xforwarded`와 `KC_HOSTNAME=https://auth.hyeonworks.com`을