공격자가 사용자의 인증 정보를 이용해 사용자가 의도하지 않은 요청을 보내는 보안 취약점
이미지 출처 :https://www.indusface.com/blog/how-to-protect-your-web-apps-using-anti-csrf-tokens/
위와 같은 방식으로 csrf 공격이 이루어지는데, 여기서 주요한점은 크롬이나 사파리같은 브라우저는 자동으로 해당 요청을 사용자의 인증 정보(세션,쿠키)와 함께 전송함 그래서 서버는 요청이 유효하다고 간주
-> 사용자가 의도치 않게 공격자가 원하는대로 악의적인 작업이 수행됨
각 요청에 대해 고유한 CSRF 토큰을 생성하고, 이 토큰을 폼에 포함시킴
또한 서버는 요청을 수신할 때, 이 토큰이 세션에 저장된 토큰과 일치하는지 검증,
일치하지 않으면 요청을 거부
예시 코드
(프론트)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSRF Protection Example</title>
</head>
<body>
<form method="POST" action="/submit">
<!--실제 사용자는 csrf 토큰 인풋박스와 전송하는지도 모름-->
<input type="hidden" name="csrf_token" value="${csrfToken}">
<input type="text" name="data" placeholder="Enter some data" required>
<button type="submit">Submit</button>
</form>
</body>
</html>
서버 측 예시 코드
@GetMapping("/")
public ModelAndView index(HttpSession session) {
String csrfToken = generateCsrfToken();
session.setAttribute("csrfToken", csrfToken);
ModelAndView modelAndView = new ModelAndView("index");
modelAndView.addObject("csrfToken", csrfToken);
return modelAndView;
}
@PostMapping("/submit")
public String submit(@RequestParam("data") String data,
@RequestParam("csrf_token") String csrfToken,
HttpSession session) {
// 세션에 저장된 CSRF 토큰과 비교
String sessionToken = (String) session.getAttribute("csrfToken");
if (sessionToken == null || !sessionToken.equals(csrfToken)) {
return "CSRF token is invalid!";
}
return "Data received: " + data;
}
private String generateCsrfToken() {
SecureRandom secureRandom = new SecureRandom();
byte[] token = new byte[16];
secureRandom.nextBytes(token);
return Base64.getUrlEncoder().withoutPadding().encodeToString(token);
}
쿠키에 SameSite 속성을 설정하여, 외부 사이트에서의 요청 시 쿠키가 전송되지 않도록함
SameSite: 쿠키가 다른 사이트에서 오는 요청에 대해 어떻게 반응할지를 설정
요청의 Referer 또는 Origin 헤더를 확인하여, 요청이 신뢰할 수 있는 출처에서 온 것인지 검증
-> 신뢰할 수 없는 출처에서 온 요청은 차단
Referer Header: 현재 요청을 보내기 전에 방문한 페이지의 URL
Ex) example.com/page1 에서 example.com/page2 로 이동하면,
page2에 대한 요청은 example.com/page1 헤더를 포함
Origin Header: 요청이 발생한 사이트의 프로토콜, 도메인, 포트를 포함(=요청을 보낸 출처의 정보)
Ex) example.com에서 API 요청을 보내면, 요청에는 example.com이라는 헤더가 포함(원래의 헤더정보 포함)