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

  • 도메인 구조가 cookie, CORS, TLS, OAuth redirect, WAF 정책의 보안 경계가 되는 이유는 무엇인가?
  • www, api, admin, static, auth 같은 서브도메인을 어떻게 나누면 운영과 보안이 쉬워지는가?
  • subdomain takeover, host header injection, wildcard cookie, 인증서 범위 문제를 어떻게 예방하는가?

개요

도메인은 보기 좋은 주소를 정하는 일이 아니다. 브라우저 보안 정책, 인증서, 쿠키 범위, OAuth callback, WAF rule, CDN cache, admin 접근 제어가 도메인 구조에 얹힌다. 한 번 외부에 노출된 도메인은 오래 남기 때문에 초기에 대충 잡은 구조가 운영 부채가 되기 쉽다.

  • 도메인은 쿠키 Domain, CORS Origin, TLS SAN, OAuth redirect URI와 연결된다.
  • 서브도메인 설계는 단순 명명 문제가 아니라 보안 경계 설계다.
  • 개인 프로젝트에서도 www, api, admin, static 같은 역할 분리가 운영 품질을 높인다.

왜 백엔드 개발자가 알아야 하는가

백엔드 코드는 도메인 정책의 영향을 직접 받는다. 같은 API라도 어느 host로 들어오느냐에 따라 cookie가 붙거나 빠지고, CORS가 허용되거나 막히고, OAuth redirect가 성공하거나 실패한다.

  • Domain=.example.com cookie는 하위 도메인 전체로 퍼져 탈취 영향 범위를 키울 수 있다.
  • admin.example.com을 public WAF 밖에 두면 admin만 다른 보안 정책을 타게 된다.
  • dev.example.comprod.example.com을 섞으면 OAuth callback, webhook, CORS allowlist 사고가 난다.
  • 사용하지 않는 CNAME이 외부 SaaS를 가리키면 subdomain takeover 위험이 생긴다.

요청 흐름에서의 위치

도메인 설계는 DNS, TLS, HTTP, browser policy가 만나는 경계다.

DNS record
→ TLS SNI / certificate SAN
→ HTTP Host / proxy routing
→ CORS Origin / cookie Domain
→ OAuth redirect URI / webhook endpoint
→ application tenant or environment routing

도메인 변경은 DNS record만 맞추면 끝나는 일이 아니다. 인증서, proxy virtual host, cookie/CORS, OAuth provider, monitoring, WAF rule이 같은 구조를 이해해야 한다.

원리

역할별 도메인을 나누면 정책도 나누기 쉬워진다.

예시 도메인역할보안/운영 포인트
www.example.com사용자 웹canonical redirect, CDN cache, SEO
api.example.comAPICORS, rate limit, auth, request id
auth.example.com인증cookie scope, OAuth redirect, MFA
admin.example.com운영자 화면IP allowlist, SSO, 강한 WAF rule
static.example.com정적 자산cookie 없는 도메인, cache 정책
webhook.example.com외부 callbacksignature 검증, idempotency, 별도 rate limit

보안 경계는 eTLD+1 기준으로만 단순하게 보면 부족하다. 브라우저 cookie, SameSite, CORS origin, TLS wildcard 인증서, CDN rule, 조직의 계정 권한이 모두 다르게 작동한다.

핵심 판단 기준

  • cookie는 가능하면 host-only cookie를 기본으로 두고, 넓은 Domain 속성은 명확한 이유가 있을 때만 쓴다.
  • admin, internal, webhook, static은 사용자 웹과 같은 정책으로 묶지 않는다.
  • wildcard DNS와 wildcard certificate는 편하지만 잘못 쓰면 잘못된 host를 너무 쉽게 받아들인다.
  • CORS allowlist는 *.example.com 같은 포괄 규칙보다 실제 origin 목록을 선호한다.
  • 외부 SaaS 연결을 해제할 때 DNS record도 같이 제거해 takeover 위험을 줄인다.

실무에서 자주 만나는 문제

  • 쿠키 Domain을 너무 넓게 잡아 하위 도메인 탈취 영향을 키운다.
  • admin 도메인을 public WAF 정책 밖에 둔다.
  • dev/stage/prod 도메인을 섞어 OAuth callback 사고를 만든다.
  • staging cookie가 production에 붙어 로그인 상태가 꼬인다.
  • api.example.comwww.example.com의 CORS/cookie/SameSite 조합이 맞지 않아 브라우저에서만 인증이 실패한다.
  • wildcard certificate가 있다고 착각하고 더 깊은 하위 도메인 a.b.example.com까지 커버한다고 생각한다.
  • 외부 SaaS 삭제 후 남은 CNAME이 공격자 계정에 연결된다.
  • Host header를 신뢰해 password reset link나 tenant routing을 만든다.

도메인 설계 문제는 “로컬에서는 되는데 운영 브라우저에서만 안 됨”, “특정 환경 callback만 실패”, “관리자만 403”, “인증서가 맞는 것 같은데 SNI에서 실패”처럼 나타난다. DNS, TLS, browser policy, application config를 함께 봐야 한다.

디버깅 방법

  • 인증서 SAN에 필요한 도메인이 모두 있는지 본다.
  • 쿠키 Domain/Path와 CORS Origin을 도메인 구조에 맞춘다.
  • DNS record와 LB listener가 같은 도메인 전략을 따르는지 확인한다.
  • Host header와 SNI가 같은지, proxy가 upstream에 어떤 Host를 전달하는지 확인한다.
  • browser DevTools에서 Set-Cookie, Cookie, CORS error, preflight 응답을 본다.
  • OAuth provider에 등록된 redirect URI와 실제 production/stage URI를 비교한다.
  • 사용하지 않는 CNAME과 dangling SaaS 연결을 정기적으로 찾는다.
dig admin.example.com CNAME +noall +answer
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -ext subjectAltName
curl -I https://api.example.com/session
curl -I -H 'Origin: https://www.example.com' https://api.example.com/session

curl은 CORS 정책을 강제하지 않는다. CORS는 브라우저 보안 정책이므로 서버 응답 header 확인과 브라우저 DevTools 확인을 같이 해야 한다.

코드로 확인하기

Spring에서 Host header 기반 URL 생성을 쓸 때는 신뢰 경계를 명시해야 한다. 단순히 들어온 Host를 믿으면 reset link나 redirect URL이 오염될 수 있다.

import jakarta.servlet.http.HttpServletRequest;
import java.net.URI;
import java.util.Set;
 
public class PublicBaseUrl {
    private static final Set<String> ALLOWED_HOSTS = Set.of("www.example.com", "app.example.com");
 
    public static URI from(HttpServletRequest request) {
        String host = request.getServerName();
        if (!ALLOWED_HOSTS.contains(host)) {
            throw new IllegalArgumentException("unexpected host: " + host);
        }
        return URI.create("https://" + host);
    }
}

proxy 뒤라면 애플리케이션이 어떤 forwarded header를 신뢰할지 별도로 설정해야 한다. 신뢰 가능한 proxy 바깥에서 온 forwarded header를 받아들이면 공격자가 public URL 생성에 개입할 수 있다.

openssl s_client -connect example.com:443 -servername example.com </dev/null
openssl s_client -connect backend.internal:8443 -servername app.internal </dev/null

-servername은 SNI를 명시한다. 공개 구간은 정상인데 백엔드 구간만 실패하면 Gateway에서 Backend로 가는 TLS, Host Header, trusted root를 분리해서 본다.

주니어가 자주 하는 오해

  • 서브도메인은 그냥 보기 좋은 이름이라고 생각한다.
    • 서브도메인은 cookie, CORS, 인증서, WAF 정책의 단위가 된다.
  • wildcard 하나면 운영이 쉬워진다고 생각한다.
    • wildcard는 편하지만 잘못된 host 수용, 인증서 범위 오해, takeover 감지를 어렵게 만들 수 있다.
  • dev/stage/prod를 같은 OAuth app에 섞어도 괜찮다고 생각한다.
    • callback URI와 token audience가 섞이면 운영 사고로 이어진다.
  • CORS 오류를 서버 장애라고만 본다.
    • 서버가 200을 줘도 브라우저가 보안 정책으로 응답을 막을 수 있다.

시니어의 설계 판단 기준

  • 도메인은 역할, 데이터 민감도, 사용자 유형, 보안 정책 단위로 나눈다.
  • cookie scope와 CORS allowlist를 도메인 설계 문서에 함께 둔다.
  • admin과 internal 도메인은 공개 웹과 별도 WAF/rate limit/auth 정책을 적용한다.
  • 사용하지 않는 서브도메인과 DNS record를 정리하는 ownership 프로세스를 둔다.
  • 새 도메인 추가는 DNS, TLS, WAF, monitoring, OAuth, webhook, logging 체크리스트를 통과해야 한다.

인프라 협업 포인트

  • 새 도메인 요청에는 용도, public/internal 여부, 인증서 범위, WAF 정책, LB listener, origin host, monitoring owner를 포함한다.
  • 인프라 팀은 wildcard DNS나 wildcard certificate를 열 때 보안 영향과 소유권을 함께 요구할 수밖에 없다.
  • 앱 팀은 Host header, CORS origin, cookie domain, OAuth redirect URI를 코드 설정으로 명확히 제공해야 한다.
  • dangling CNAME 점검은 보안팀/인프라팀/앱팀이 함께 ownership을 정해야 한다.

실전 팁

  • 개인 프로젝트도 www, api, admin 정도는 분리하면 인증/캐시/운영 판단이 쉬워진다.
  • 정적 파일 도메인은 가능하면 cookie가 붙지 않게 설계한다.
  • production과 staging은 host, cookie name, OAuth client, webhook secret을 분리한다.
  • 새 도메인을 만들 때 삭제 절차도 같이 문서화한다. 만든 사람만 아는 DNS record가 가장 오래 남는다.

위험 신호!

  • Domain=.example.com cookie를 아무 이유 없이 쓴다.
  • 모든 환경이 같은 OAuth redirect URI 목록에 섞여 있다.
  • CORS allowlist가 * 또는 지나치게 넓은 wildcard다.
  • DNS record owner가 없고, 외부 SaaS 연결 해제 후 CNAME이 남는다.
  • 앱이 Host header를 그대로 믿어 absolute URL을 만든다.

확인 질문

확인 질문

  • 도메인 설계가 보안 경계가 되는 이유는 무엇인가?
    • cookie Domain, CORS Origin, TLS 인증서, OAuth redirect URI, WAF rule이 도메인 단위로 작동하기 때문이다.
  • subdomain takeover는 어떤 상황에서 생기는가?
    • DNS record가 외부 서비스 대상을 가리키지만 해당 외부 서비스 리소스가 해제되어 다른 사람이 연결할 수 있을 때 생긴다.
  • admin 도메인을 사용자 웹 도메인과 분리하는 이유는 무엇인가?
    • 접근 주체, 인증 강도, WAF/rate limit, monitoring, incident 대응 기준이 다르기 때문이다.

참고 문서