docs: record proxy-bypass closure with before and after evidence

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-03 16:17:24 +09:00
co-authored by Claude Opus 5
parent 3af52bb66a
commit e1ba9c5626
3 changed files with 151 additions and 12 deletions
+70 -12
View File
@@ -714,24 +714,82 @@ curl -s https://app1.hyeonworks.com/api/echo \
`$proxy_add_x_forwarded_for`(덧붙이기)로 바꾸면 클라이언트가 넣은 값이
사슬 앞부분에 남아 신뢰 경계가 무너진다.
### 잔여 위험 — 프록시 우회 경로
### 프록시 우회 경로 차단
앱이 헤더를 신뢰하게 됐으므로, **Traefik을 거치지 않고 파드에 직접 도달할 수
으면 위조가 가능하다.** 클러스터 안에서는 Service ClusterIP로 접근할
다.
앱이 헤더를 신뢰하게 되면 **Traefik을 거치지 않고 파드에 직접 도달할 수
는 경로가 곧 구멍**이 된다. 클러스터 안에서는 Service ClusterIP로 접근할
수 있으므로 실제로 위조가 성립했다.
```bash
# 클러스터 내부에서 (위조 가능함을 확인)
kubectl -n header-lab run t --rm -it --image=curlimages/curl --restart=Never -- \
curl -s http://echo:8081/api/echo -H 'X-Forwarded-Proto: https' -H 'X-Forwarded-For: 1.2.3.4'
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'
```
**NetworkPolicy로 Traefik에서 오는 트래픽만 허용하는 것이 정석이다.**
이 클러스터는 kube-router 내장 컨트롤러가 있어 적용 가능하다.
아직 적용하지 않았으며, `feature/keycloak-header-spoofing-defense`에서
다룰 항목이다.
```
serverName evil.example.com ← 위조 성공
remoteAddr 1.2.3.4 ← 위조 성공
requestUrl https://evil.example.com/api/echo
```
---
**두 신뢰 설정이 모두 "대역"을 믿기 때문**이다.
| 계층 | 신뢰 범위 | 지정 방식 |
|---|---|---|
| Traefik `trustedIPs` | 파드 대역 전체 | IP 대역 |
| 앱 Tomcat `internalProxies` | 사설 대역 전체 (기본 정규식) | IP 정규식 |
IP로는 Traefik을 특정할 수 없다. **파드 IP가 재시작마다 바뀌기 때문**이다
(측정 중 실제로 `10.42.0.8``10.42.1.12`로, 노드까지 옮겨갔다).
**해결 — NetworkPolicy는 IP가 아니라 라벨로 지정한다.**
| | |
|---|---|
| 저장소 파일 | `deploy/lab/k8s/echo-network-policy.yaml` |
```yaml
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik # ← IP 가 아니라 라벨
ports:
- protocol: TCP
port: 8081
```
`namespaceSelector``podSelector`를 **같은 리스트 항목**에 두면 AND로
결합된다. 별개 항목으로 나누면 OR이 되어 kube-system 전체가 허용되므로
주의한다.
**kubelet probe를 위한 규칙이 별도로 필요하다.** readiness/liveness는 파드가
아니라 노드에서 오므로 위 규칙에 걸리지 않는다. 빠뜨리면 probe가 실패하고
**파드가 재시작 루프에 빠진다.**
```yaml
- from:
- ipBlock: { cidr: 10.42.0.1/32 } # kc-lab-1 의 cni0
- ipBlock: { cidr: 10.42.1.1/32 } # kc-lab-2 의 cni0
```
probe의 출발지는 **노드의 flannel 브리지(cni0)** 이고, 각 노드 `/24`의 첫
주소다. `/32`로 정확히 지정해야 한다 — `10.42.0.0/16`으로 넓히면 임의의
파드가 다시 들어와 정책이 무의미해진다.
**적용 후 확인**
```
정상 경로 scheme=https, remoteAddr=100.123.124.30 계속 동작
우회 시도 HTTP 000 / curl exit 7 연결 자체가 거부됨
파드 상태 1/1 Running, restarts=0 probe 정상
```
**"헤더를 믿는다"와 "앞에 반드시 프록시가 있다"는 한 쌍이다.**
앞의 것만 하면 이 구멍이 남는다.
## 12. 증거