이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • 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-Forclient IP 추정, audit, rate limit마지막 hop이 trusted proxy이고 외부 입력을 정리함spoofed IP를 allowlist로 신뢰
X-Forwarded-Protoredirect 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를 구분할 수 없기 때문이다.

참고 문서