쿠키 변조 (CC) | 위험도: 3 (중간) | 통제구분: 5.2.5 쿠키 보안
인터넷뱅킹 사용자 인증
이름: 홍길동
등급: 일반회원
잔액: 1,234,000원
브라우저 쿠키:
// ❌ 쿠키 검증 없이 신뢰
@GetMapping("/mypage")
public String mypage(HttpServletRequest request) {
// 쿠키 값을 그대로 신뢰!
Cookie[] cookies = request.getCookies();
String userId = getCookieValue(cookies, "userId");
String role = getCookieValue(cookies, "role");
int balance = Integer.parseInt(
getCookieValue(cookies, "balance")
);
// 서버 검증 없이 권한 판단!
if ("admin".equals(role)) {
return "adminPage"; // 관리자 페이지
}
// 쿠키의 잔액을 그대로 표시!
model.addAttribute("balance", balance);
return "mypage";
}
// 쿠키 생성 시 서명 없음
@PostMapping("/login")
public String login(String userId, String password) {
User user = authenticate(userId, password);
// 평문 쿠키 생성 (서명 없음!)
Cookie userIdCookie = new Cookie("userId", userId);
Cookie roleCookie = new Cookie("role", user.getRole());
Cookie balanceCookie = new Cookie("balance",
String.valueOf(user.getBalance()));
// HttpOnly 미설정!
response.addCookie(userIdCookie);
response.addCookie(roleCookie);
response.addCookie(balanceCookie);
return "redirect:/mypage";
}
공격 시나리오를 선택하세요
// ✅ 안전한 쿠키 관리
// 1. 서명된 쿠키 생성 (HMAC)
@PostMapping("/login")
public String login(String userId, String password,
HttpServletResponse response) {
User user = authenticate(userId, password);
// 세션에 저장 (쿠키에 민감정보 X)
session.setAttribute("userId", userId);
session.setAttribute("user", user);
// 세션 ID만 쿠키로 전송
Cookie sessionCookie = new Cookie(
"JSESSIONID",
session.getId()
);
// HttpOnly 설정 (XSS 방어)
sessionCookie.setHttpOnly(true);
// Secure 설정 (HTTPS만)
sessionCookie.setSecure(true);
// SameSite 설정 (CSRF 방어)
response.setHeader("Set-Cookie",
String.format(
"JSESSIONID=%s; HttpOnly; Secure; SameSite=Strict",
session.getId()
)
);
return "redirect:/mypage";
}
// 2. 서버에서 세션 검증
@GetMapping("/mypage")
public String mypage(HttpSession session, Model model) {
// 세션에서 사용자 정보 조회
User user = (User) session.getAttribute("user");
if (user == null) {
return "redirect:/login";
}
// DB에서 실시간 정보 조회
User currentUser = userService.findById(user.getId());
// 서버에서 권한 확인
if (currentUser.getRole().equals("ADMIN")) {
model.addAttribute("isAdmin", true);
}
// DB에서 조회한 잔액 표시
model.addAttribute("balance", currentUser.getBalance());
return "mypage";
}
// 3. 쿠키에 민감정보 필요 시 HMAC 서명
public String createSignedCookie(String value) {
// HMAC-SHA256으로 서명 생성
String secret = "your-secret-key-256bit";
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey =
new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secretKey);
byte[] hash = sha256_HMAC.doFinal(value.getBytes());
String signature = Base64.getEncoder()
.encodeToString(hash);
// value + signature 결합
return value + "." + signature;
}
// 4. 서명 검증
public boolean verifyCookie(String signedValue) {
String[] parts = signedValue.split("\\.");
if (parts.length != 2) {
return false;
}
String value = parts[0];
String receivedSignature = parts[1];
// 서명 재생성
String expectedSignedValue = createSignedCookie(value);
String expectedSignature =
expectedSignedValue.split("\\.")[1];
// 서명 일치 확인
return receivedSignature.equals(expectedSignature);
}
// 5. Spring Boot 쿠키 보안 설정
@Bean
public ServletContextInitializer servletContextInit() {
return ctx -> {
SessionCookieConfig config =
ctx.getSessionCookieConfig();
config.setHttpOnly(true);
config.setSecure(true);
config.setName("SESSIONID");
config.setMaxAge(3600); // 1시간
config.setAttribute("SameSite", "Strict");
};
}
⚠️ HttpOnly 미설정 시:
' + '🔒 방어 방법
' + 'HttpOnly: true 설정 필수!
' + '→ document.cookie 접근 차단
' + '