이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
X-Forwarded-For,X-Forwarded-Proto,Forwarded를 언제 믿고 언제 버려야 하는가?- client IP 기반 rate limit, audit log, geo policy가 proxy chain에서 왜 틀어지는가?
- Spring Boot 뒤에 reverse proxy가 있을 때 scheme/host/client IP를 어떻게 맞추는가?
개요
Forwarded header는 proxy를 지나며 사라지는 원래 client 정보를 앱에 전달하기 위한 약속이다. 문제는 이 header가 HTTP header일 뿐이라 외부 client도 마음대로 넣을 수 있다는 점이다. 그래서 앱은 “header 값”이 아니라 “신뢰 가능한 proxy가 넣은 header 값”만 사용해야 한다.
X-Forwarded-For는 client IP chain을 담는다.X-Forwarded-Proto는 원래 요청 scheme을 담는다.X-Forwarded-Host는 원래 Host를 담는다.- 표준 header로
Forwarded: for=...;proto=...;host=...가 있다. - trusted proxy 목록과 해석 방향이 없으면 spoofing과 redirect loop가 생긴다.
원리
proxy chain 예시다.
Client 198.51.100.10
-> CDN 203.0.113.20
-> LB 10.0.0.5
-> App
X-Forwarded-For: 198.51.100.10, 203.0.113.20일반적으로 오른쪽에 가까운 값일수록 앱에 가까운 proxy다. 신뢰 가능한 proxy 대역을 기준으로 오른쪽부터 제거하며 남는 첫 IP를 client로 보는 방식이 안전하다. 단, 제품과 framework마다 방식이 다르므로 운영 정책을 명확히 해야 한다.
잘못 믿으면 생기는 문제
- 공격자가
X-Forwarded-For: 127.0.0.1을 보내 admin IP allowlist를 우회한다. - client IP 기반 rate limit이 NAT나 proxy IP 기준으로 걸려 정상 사용자 전체가 막힌다.
- 앱이
http로 판단해 secure cookie를 만들지 않거나 HTTPS redirect loop를 만든다. - audit log에 proxy IP만 남아 사고 추적이 어렵다.
- OAuth redirect URL이 내부 host로 만들어진다.
Header별 신뢰 경계
| Header | 쓰는 곳 | 믿어도 되는 조건 | 흔한 사고 |
|---|---|---|---|
X-Forwarded-For | client IP 추정, audit, rate limit | 마지막 hop이 trusted proxy이고 외부 입력을 정리함 | spoofed IP를 allowlist로 신뢰 |
X-Forwarded-Proto | redirect URL, secure cookie 판단 | TLS termination 계층이 직접 설정함 | HTTPS redirect loop, insecure cookie |
X-Forwarded-Host | 원래 host 복원 | 허용 host allowlist와 함께 사용 | Host header injection, 잘못된 OAuth callback |
Forwarded | 표준 forwarding 정보 | proxy chain 해석 규칙이 문서화됨 | framework와 proxy의 해석 방식 불일치 |
시니어는 “이 header 값이 맞는가”보다 “누가 이 값을 쓸 권한이 있는가”를 먼저 묻는다. 외부 client가 직접 app에 닿을 수 있으면 forwarding header 신뢰 모델은 깨진다.
안전한 Proxy 설정
edge proxy는 외부에서 온 forwarding header를 그대로 넘기지 말고 정리한 뒤 다시 넣는 편이 안전하다.
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_pass http://app_upstream;
}더 강한 경계가 필요하면 외부에서 들어온 민감 header를 삭제하고 내부 proxy가 재주입한다.
Spring Boot 설정
Spring Boot 뒤에 proxy가 있다면 forwarded header 처리 방식을 설정한다.
server:
forward-headers-strategy: framework이 설정만으로 모든 보안이 끝나지 않는다. 실제 배포에서는 ingress/LB가 trusted proxy로 동작하는지, 외부 header를 덮어쓰는지, app이 direct access를 받지 않는지 함께 확인해야 한다.
코드와 명령으로 확인하기
spoofing 가능성을 테스트한다.
curl -i https://api.example.com/me \
-H 'X-Forwarded-For: 127.0.0.1' \
-H 'X-Forwarded-Proto: http'정상이라면 앱이 외부 client가 임의로 보낸 값을 그대로 신뢰하지 않아야 한다. access log에는 remote address, forwarded chain, 최종 해석 client IP가 구분되어 남는 것이 좋다.
remote_addr=10.0.0.5 xff="198.51.100.10, 203.0.113.20" client_ip=198.51.100.10인프라 협업 포인트
- trusted proxy CIDR와 header 해석 규칙을 앱/인프라/보안이 함께 문서화한다.
- CDN, LB, ingress 중 어느 계층이 header를 생성하고 덮어쓰는지 정한다.
- direct-to-app 접근이 가능하면 forwarding header 보안이 깨질 수 있으므로 network policy/firewall도 함께 본다.
- rate limit 기준이 IP인지 user id인지 API key인지 정하고 NAT 환경 영향을 고려한다.
주니어가 자주 하는 오해
X-Forwarded-For의 첫 번째 IP가 항상 실제 사용자 IP라고 생각한다.- proxy가 하나일 때만 맞는 코드를 만들고 CDN, WAF, LB가 추가된 운영 구조를 고려하지 않는다.
X-Forwarded-Proto만 켜면 secure cookie와 redirect 문제가 모두 해결된다고 생각한다.- IP 기반 rate limit을 user/session/API key 기반 제한보다 강한 보안 장치로 착각한다.
시니어의 설계 판단 기준
- app direct access가 network policy로 막혀 있는지 먼저 확인한다.
- client IP를 보안 판단, 감사 로그, 통계, rate limit 중 어디에 쓰는지 용도별 정확도 요구를 나눈다.
- trusted proxy 목록과 header overwrite 정책을 배포 산출물로 관리한다.
- 개인정보/감사 요구가 있는 서비스는 원본 IP 산출 근거를 로그에 남기되 보관 기간과 마스킹 정책을 함께 둔다.
실전 팁
- IP 기반 보안은 보조 신호로 다룬다. 모바일, 사내 NAT, VPN, 프록시 환경에서는 많은 사용자가 같은 IP를 공유한다.
- client IP가 꼭 필요한 기능은 audit, fraud, geo, rate limit마다 정확도 요구가 다르다.
X-Forwarded-Host를 URL 생성에 쓰면 Host header injection 위험을 같이 봐야 한다.- 개인 프로젝트에서는 reverse proxy와 app이 같은 private network에 있고 app port가 외부에 열리지 않게 하는 것부터 시작한다.
- 장애 보고에는 사용자 실제 IP, proxy remote address, XFF chain을 모두 남기면 인프라 팀이 추적하기 쉽다.
위험 신호!
- 앱이 모든
X-Forwarded-*값을 무조건 신뢰한다. - direct app port가 인터넷에 열려 있다.
- rate limit이 proxy IP 하나에 걸려 전체 사용자가 429를 받는다.
- 로그에 최종 client IP 산출 근거가 없다.
확인 질문
확인 질문
X-Forwarded-For를 그대로 믿으면 왜 위험한가?
- 외부 client도 같은 header를 임의로 보낼 수 있어 IP allowlist, audit, rate limit 판단을 속일 수 있기 때문이다.
- reverse proxy 뒤에서 redirect URL이
http://로 생성되는 이유는 무엇일 수 있는가?
- 앱이 원래 요청이 HTTPS였다는 forwarded proto를 해석하지 못했기 때문이다.
- trusted proxy 설정이 필요한 이유는 무엇인가?
- 어떤 proxy가 넣은 header만 신뢰할지 정하지 않으면 조작된 header와 진짜 proxy header를 구분할 수 없기 때문이다.