CSRF란
CSRF(Cross-Site request Forgery, 사이트 간 요청 위조)는 웹 보안 취약점 중 하나로, 사용자가 의도하지 않은 요청을 공격자에 의해 대신 보내게 하는 공격 방식이다. 사용자가 로그인 상태일 때 공격자가 의도한 요청을 사용자 계정으로 서버에 전송하게 만들 수 있어 위험하다.
예시
1. 사용자가 https://bank.com에 로그인했다고 가정하자.
- 로그인 후 세션 쿠키가 브라우저에 저장된다.
Set-Cookie: sessionid=abc123; Secure; HttpOnly; SameSite=None
2. 사용자가 https://evil.com에 방문 (악성 사이트)
- 악성 사이트에는 아래와 같은 자동 전송 요청 코드가 있다.
<!-- 악성 사이트: evil.com -->
<!DOCTYPE html>
<html>
<body>
<h1>Free iPhone! Click here!</h1>
<!-- 자동으로 bank.com에 요청 전송 -->
<img src="https://bank.com/transfer?to=hacker&amount=1000" />
<!-- 또는 form + JS 자동 submit -->
<form id="stealMoney" action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="hacker123">
<input type="hidden" name="amount" value="1000">
</form>
<script>
document.getElementById('stealMoney').submit();
</script>
</body>
</html>
3. 브라우저는 요청에 자동으로 사용자의 세션 쿠키를 포함시킨다.
POST /transfer HTTP/1.1
Host: bank.com
Cookie: sessionid=abc123
Content-Type: application/x-www-form-urlencoded
to=hacker123&amount=1000
4. 서버는 피해자의 세션 쿠키가 유효하므로 정상 요청으로 처리하게 된다.
- 결과적으로 사용자가 의도하지 않았지만 공격자가 원하는 행동이 실행된다.(CSRF)
- 예) 의도치 않게 사용자 계정을 삭제시키거나 계좌의 돈을 공격자의 계좌로 입금시킬 수 있다.
왜 위험할까?
- 사용자는 아무것도 클릭하지 않아도 의도치 않게 요청된다.
- img, form, iframe, fetch 등 다양한 방식으로 요청이 가능하다.
- 로그인 세션이 유지된 상태라면 모든 인증된 기능이 공격 대상이 된다.
CSRF의 특징
- 사용자의 세션 쿠키를 이용한다. (Http 요청에는 쿠키가 모두 자동으로 포함)
- 주로 GET/POST 요청을 통해 실행된다.
- 서버 입장에서는 정상 사용자의 요청처럼 보인다.
프레임워크에서의 CSRF 대응
- Django: 기본적으로 CSRF 토큰을 사용하며, 미들웨어에서 자동으로 검증한다.
- Spring Security: CSRF 보호 기능이 기본으로 활성화되어 있다.
CSRF 방어법
CSRF Token 사용 (가장 보편적이자 강력한 방법)
- 서버가 매 요청 또는 세션마다 랜덤 토큰을 발급한다.
- 클라이언트는 폼이나 요청 헤더에 이 토큰을 포함해서 전송한다.
- 서버는 이 토큰이 세션에 저장된 값과 일치하는지 검증 후 요청을 수행한다.
Html Form 예시
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="3f7x9x2c9" />
</form>
React + axios 예시
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.yourdomain.com',
withCredentials: true,
});
// CSRF 토큰 받아오기
async function initCSRF() {
const res = await api.get('/csrf-token'); // 서버에서 토큰 발급
const csrfToken = res.data.csrfToken;
// 이후 모든 요청에 토큰 헤더 추가
api.defaults.headers.common['X-CSRF-Token'] = csrfToken;
}
// 호출
initCSRF();
공격자의 위조된 요청이 아래와 같이 전송되어도 공격자는 CSRF 토큰을 알 수 없기 때문에 위조된 요청이 처리되지 않는다.
POST /transfer HTTP/1.1
Host: bank.com
Cookie: sessionid=abc123
Content-Type: application/x-www-form-urlencoded
CSRF 토큰 없음!!
to=hacker123&amount=1000
공격자는 왜 CSRF 토큰을 위조하지 못하는가?
1. CSRF 토큰은 세션마다 다르고, 랜덤 하게 생성된다.
- 서버는 세션/쿠키 기준으로 개별 사용자에게 토큰을 발급한다.
- 공격자는 사용자 세션의 토큰 값을 알 방법이 없다.
2. CSRF 토큰은 JavaScript에서 가져와야 한다.
- 공격자는 악성 사이트에서 <script>로 사용자의 브라우저에서 fetch('/csrf-token')을 시도해도 CORS 정책 때문에 요청이 막히거나, 응답 본문을 읽지 못한다.
3. X-CSRF-Token은 JavaScript로 명시적으로 넣어야 한다.
- HTML <form>이나 <img> 태그만으로는 헤더 조작이 불가능하다.
- 헤더를 조작하려면 동일 출처에서 실행 중인 JavaScript가 필요하지만 공격자는 권한이 없다.
SameSite 쿠키 설정
- SameSite 속성을 통해 쿠키가 외부 요청에 자동으로 붙지 않도록 제한한다.
Set-Cookie: sessionid=abc123; SameSite=Strict; Secure; HttpOnly
설정값 | 종류 |
Strict | 완전 차단 (다른 사이트에서 요청해도 쿠키 전송X) ✅ |
Lax | 일부 허용 (GET 같은 안전한 요청만 쿠키 전송) ⚠️ |
None | 전부 허용 (단, Secure 필요) 위험할 수 있음 ❌ |
Referer / Origin 헤더 검증
- 요청의 Origin 또는 Referer 헤더를 검사하여 요청이 신뢰된 출처에서 왔는지 확인한다.
- 일부 브라우저/환경에서는 Referer이 안 들어오는 경우도 있기에 보조 수단으로 사용하는 게 좋다.
CORS 정책 설정
- 백엔드 서버가 어떤 도메인에서 요청을 허용할지 명시한다.
- 서버에서 허용한 도메인의 요청만 수락하여 외부 도메인의 위조된 요청을 수행하지 않는다.
중요 요청은 GET 대신 POST/PUT/DELETE로만
- 대부분의 송금, 주문 같은 요청은 POST로 하는 게 기본이지만 다시 강조하자면 절대 GET으로 하면 안 된다.
- 공격자는 img src(이미지 GET 요청)에 요청을 숨기는 경우가 있다. 따라서 중요 작업은 GET을 사용하지 않는다.
방어법 정리
방어 | 방법효과 | 설명 |
CSRF Token | 강력 | 토큰 일치 여부로 위조 방지 |
SameSite 쿠키 | 효과적 | 외부 요청 시 쿠키 차단 |
Origin / Referer 검사 | 보조 수단 | 요청 출처 확인 |
CORS 정책 제한 | 보조 수단 | API 호출 도메인 제한 |
POST 요청 강제 | 기초 방어 | GET으로 민감 동작 금지 |
'CS' 카테고리의 다른 글
HTTP 헤더, 캐시와 조건부 요청 (0) | 2025.04.07 |
---|---|
HTTP 헤더, 쿠키에 대해서 (0) | 2025.04.07 |
HTTP 상태 코드 (0) | 2025.04.04 |
HTTP 메서드 활용 (0) | 2025.04.03 |
[ 프로그래머스,bfs ] 네트워크.python (0) | 2025.04.03 |