CSRF 공격 원리와 웹 애플리케이션 방어 설계

CSRF의 요청 위조 원리와 XSS와의 차이, 토큰·SameSite 쿠키·Origin 검증을 활용한 웹 애플리케이션 방어 방법을 다룬다.

2026-08-14 · 최초 발행 2025-06-28

인증 쿠키가 의도하지 않은 요청에 실리는 방식

CSRF(Cross-Site Request Forgery), 즉 사이트 간 요청 위조는 인증된 사용자의 브라우저가 공격자가 만든 요청을 피해 사이트에 보내도록 유도하는 공격이다. 사용자가 직접 요청하지 않았더라도 브라우저가 세션 쿠키 등 인증 정보를 함께 전송하면, 서버는 이를 정상 사용자의 요청으로 받아들일 수 있다.

공격은 보통 로그인 세션이 남아 있는 상태에서 시작된다. 사용자가 공격자가 제어하는 페이지를 방문하면, 그 페이지는 피해 사이트를 대상으로 자동 요청을 만들고 브라우저는 기존 인증 정보를 포함해 전송한다. 상태 변경 요청의 출처를 검증하지 않는 서비스라면 이 요청이 처리될 수 있다.

브라우저에서 벌어지는 요청 위조 시나리오

공격사이트(evil.com)정상사이트(bank.com)사용자공격사이트(evil.com)정상사이트(bank.com)사용자1. 로그인 (쿠키 발급)2. 인증 쿠키 설정3. 악의적인 사이트 방문4. 숨겨진 폼이나 이미지 로드5. 자동 요청 전송 (쿠키 포함)6. 요청 처리 (사용자 의도와 무관)

자금 이체 기능은 전형적인 표적이다. 공격자는 이체 요청의 구조를 분석한 뒤, 방문만으로 요청이 전송되는 코드를 공격 페이지에 넣을 수 있다.

  • 공격자: 은행 웹사이트에서 자금 이체를 수행하는 요청 구조를 분석
  • 악성 코드: <img src="https://bank.com/transfer?to=attacker&amount=1000" style="display:none">
  • 결과: 사용자가 공격 페이지를 방문하면 자신도 모르게 공격자에게 돈을 송금

계정 이메일 변경도 같은 방식으로 악용될 수 있다. 공격자가 이메일 변경 요청 구조를 파악하고 자동 제출되는 숨겨진 폼을 배치하면, 사용자의 계정 이메일을 공격자 이메일로 바꿀 수 있다. 이후 비밀번호 재설정 등을 통해 계정 탈취가 가능하다.

점검은 상태 변경 요청부터 시작한다

CSRF 취약점 평가는 읽기 요청이 아니라 상태를 바꾸는 기능을 중심으로 한다. 계정 정보 변경, 자금 이체, 게시물 작성처럼 서버 상태에 영향을 주는 엔드포인트가 우선 대상이다.

각 요청에 CSRF 토큰이 있는지, Origin 또는 Referer 헤더를 검증하는지, 중요 작업에 추가 인증을 요구하는지를 확인한다. 쿠키에는 SameSite 속성이 설정돼 있는지와 Secure·HttpOnly 플래그가 적용돼 있는지도 함께 점검한다.

요청마다 서버 토큰을 확인하는 방식

CSRF 토큰은 서버가 발급한 고유 값을 요청에 포함시키고, 서버가 이를 검증하는 방식이다.

<form action="/transfer" method="post">
  <input type="hidden" name="csrf_token" value="랜덤생성토큰123456" />
  <input type="text" name="amount" value="1000" />
  <input type="text" name="to" value="friend@example.com" />
  <input type="submit" value="송금" />
</form>

서버는 세션에 저장한 토큰과 요청으로 받은 값을 비교해 일치하지 않는 요청을 거부한다.

@Controller
public class TransferController {
    @PostMapping("/transfer")
    public String processTransfer(@RequestParam("amount") String amount,
                                 @RequestParam("to") String recipient,
                                 @RequestParam("csrf_token") String token,
                                 HttpSession session) {
        // CSRF 토큰 검증
        String storedToken = (String) session.getAttribute("csrf_token");
        if (storedToken == null || !storedToken.equals(token)) {
            return "error";
        }

        // 정상 처리 로직
        transferService.transfer(amount, recipient);
        return "success";
    }
}

쿠키와 요청 출처를 함께 제한한다

SameSite 속성은 교차 사이트 요청에서 쿠키가 전송되는 범위를 제어한다.

Set-Cookie: sessionid=abc123; SameSite=Strict; Secure; HttpOnly
  • Strict: 같은 사이트 요청에만 쿠키 전송
  • Lax: 같은 사이트 요청과 외부 사이트의 GET 요청에만 쿠키 전송 (기본값)
  • None: 모든 요청에 쿠키 전송 (Secure 플래그 필수)

Origin과 Referer 헤더 검증은 상태 변경 요청이 허용된 출처에서 왔는지 확인하는 방법이다.

@RequestFilter
public class CSRFFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String origin = httpRequest.getHeader("Origin");
        String referer = httpRequest.getHeader("Referer");

        // 요청이 정상적인 출처에서 왔는지 확인
        if (isStateChangingMethod(httpRequest.getMethod()) &&
            !isValidOrigin(origin) && !isValidReferer(referer)) {
            ((HttpServletResponse) response).sendError(403, "CSRF 공격 감지");
            return;
        }

        chain.doFilter(request, response);
    }

    private boolean isStateChangingMethod(String method) {
        return "POST".equals(method) || "PUT".equals(method) ||
               "DELETE".equals(method) || "PATCH".equals(method);
    }
}

더블 서브밋 쿠키 방식은 쿠키와 폼 필드에 같은 랜덤 값을 넣고 두 값을 비교한다.

// 요청 시 생성
String randomToken = generateRandomToken();
Cookie cookie = new Cookie("csrf_token", randomToken);
response.addCookie(cookie);

// 폼에 추가
model.addAttribute("csrf_token", randomToken);

// 검증 시
Cookie[] cookies = request.getCookies();
String cookieToken = extractCsrfTokenFromCookies(cookies);
String formToken = request.getParameter("csrf_token");

if (cookieToken == null || !cookieToken.equals(formToken)) {
    // CSRF 공격 감지
}

XSS와 CSRF는 공격 지점이 다르다

보안 취약점XSSCross-Site ScriptingCSRFCross-Site Request Forgery공격자의 스크립트를피해자 브라우저에서 실행인증된 사용자로의도하지 않은 요청 전송사용자 입력 검증 부족요청 출처 검증 부족쿠키 탈취 가능상태 변경 작업 수행

XSS는 피해자 브라우저에서 악성 스크립트를 실행해 정보를 탈취하는 공격이며, 사용자 입력 데이터의 검증이나 이스케이핑이 부적절할 때 발생한다. 입력 검증, 출력 인코딩, CSP(Content Security Policy)가 주요 대응 수단이다.

CSRF는 인증된 사용자의 권한을 이용해 특정 작업을 수행하게 만들며, 상태 변경 요청의 출처 검증 부재가 원인이 된다. 토큰 검증, SameSite 쿠키, 출처 확인이 이에 대응한다.

금융 거래와 계정 변경에서의 통제

금융기관 웹사이트에서 송금 기능이 CSRF 공격에 취약했던 사례에서는 모든 상태 변경 요청에 CSRF 토큰을 적용했다. 송금과 계정 정보 변경에는 2차 인증(OTP)을 요구하고, 거래 정보를 포함한 서명을 생성하는 트랜잭션 서명도 도입했다. 그 결과 CSRF 공격 시도 99.9% 차단, 실제 공격 성공 사례 없음이 보고됐다.

소셜 미디어 플랫폼의 계정 설정 변경 기능이 취약했던 사례에서는 SameSite=Strict 쿠키와 모든 API 엔드포인트의 CSRF 토큰 검증을 적용했다. 보안 모니터링 시스템으로 의심스러운 패턴도 감지했으며, CSRF 취약점으로 인한 계정 탈취 시도가 근절됐다.

배포 전 확인할 항목

상태 변경 요청에는 로그인·회원가입 폼, 사용자 설정 변경 폼, 금융 거래 관련 폼, 관리자 기능 페이지까지 CSRF 토큰을 적용한다.

쿠키에는 SameSite 속성(Strict 또는 Lax), HttpOnly 플래그, HTTPS 환경의 Secure 플래그를 설정한다. 상태 변경 요청의 Origin·Referer 헤더와 JSON 요청의 Content-Type 헤더를 검증하고, 중요 작업에는 재인증을 요구한다.

프레임워크가 제공하는 보호 기능도 활성화 대상이다. Spring Security CSRF 보호 기능, Django CSRF 미들웨어, Express.js CSRF 보호 미들웨어를 적용 범위에 맞춰 확인한다.

CSRF웹 보안SameSite 쿠키보안 취약점접근통제