Skip to content
DAEILLIM edited this page Apr 30, 2024 · 1 revision

1. CSRF (Cross Site Request Forgery)

CSRF(Cross Site Request Forgery, 사이트 간 요청 위조)는 사이트 간 요청 위조를 의미한다. 구체적으로, 웹 애플리케이션 취약점 중 하나로 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 해서 특정 웹페이지를 보안에 취약하게 한다거나 수정, 삭제 등의 작업을 하게 만드는 공격 방법이다.

CSRF 공격은 웹 애플리케이션을 이용하는 사용자의 브라우저가 자동으로 보낼 수 있는 인증 정보, 예를 들어 쿠키나 기본 인증 세션을 이용하여 사용자가 의도하지 않은 요청을 서버로 전송하게 만든다. 일반적으로 사용자가 로그인한 상태에서 악의적인 웹사이트를 방문하거나 이메일 등을 통해 악의적인 링크를 클릭할 때 발생할 수 있다.

CSRF 시나리오

image


CSRF 시나리오 조건

CSRF 공격을 시도하기 위해 아래와 같은 몇 가지 조건이 필요하다.

  1. 사용자가 보안이 취약한 서버로부터 이미 인증을 받은 상태이어야 한다.
  2. 쿠키 기반으로 서버 세션 정보를 획득할 수 있어야 한다.
  3. 공격자는 서버를 공격하기 위한 요청 방법에 대해 미리 파악하고 있어야 한다. 예상치 못한 파라미터가 있으면 불가능하다.

위와 같은 세 가지 조건이 만족되면 다음과 같은 과정을 통해 CSRF 공격이 수행된다.

  1. 사용자는 보안이 취약한 서버에 로그인
  2. 로그인 이후 서버에 저장된 세션 정보를 사용할 수 있는 sessionID가 사용자 브라우저 쿠키에 저장
  3. 공격자는 서버에 인증된 브라우저의 사용자가 악성 스크립트 페이지를 누르도록 유도
  4. 사용자가 악성 스크립트가 작성된 페이지 접근시 쿠키에 저장된 sessionID는 브라우저에 의해 자동적으로 함께 서버로 요청
  5. 서버는 쿠키에 담긴 sessionID를 통해 해당 요청이 인증된 사용자로부터 온 것으로 판단하고 처리

구체적으로, 사용자가 해당 악성 스크립트가 담긴 페이지를 클릭하도록 유도하는 몇 가지 유형을 정리하자면 다음과 같다.

  1. 게시판에 악성 스크립트를 게시글로 작성하여 관리자 혹은 다른 사용자들이 게시글을 클릭하도록 유도
  2. 메일 등으로 악성 스크립트를 직접 전달하거나, 악성 스크립트가 적힌 페이지 링크를 전달

따라서 사용자는 전혀 모르는 상태 혹은 허가 없이 공격자가 의도한 기능이 동작해 사용자에게 피해가 발생한다. 이미지 태그의 URL에 쿼리 스트링을 사용해 사용자를 관리자로 승격 혹은 사용자의 비밀번호를 공격자가 원하는대로 바꾸는 등의 방법으로 악용 가능하다.




2. CSRF 방어

보통 스프링 시큐리티를 사용하여 CSRF 공격을 방어하려면 설정을 변경할 필요가 거의 없으며, 기본적으로 보안을 제공하고 있다. 스프링 시큐리티에서 CSRF 공격을 방어하는 방법은 다음과 같다.

  1. CSRF 토큰 사용
    스프링 시큐리티는 웹 애플리케이션의 모든 POST 요청에 대해 CSRF 토큰을 자동으로 생성하고 삽입한다. CSRF 토큰은 세션에 저장되어 있으며, 요청이 수행될 때마다 토큰을 검증하여 요청이 유효한지 확인한다.

  2. 폼 기반 보안
    스프링 시큐리티를 사용하여 폼 기반 로그인을 구현할 때, 자동으로 CSRF 토큰이 포함된다. 폼기반 보안을 통해 로그인 폼을 통한 인증 요청도 CSRF 공격으로부터 보호된다.

  3. API 보호
    RESTful API를 보호할 때도 스프링 시큐리티를 사용할 수 있다. 이러한 경우 CSRF 토큰을 API 요청의 헤더에 포함시켜야 한다.
    스프링 시큐리티는 API 요청에서도 CSRF 토큰을 검증하여 보호가 가능하다.


REST 방식 CSRF 대안책

다음과 같은 방식으로 백엔드에서 CSRF 토큰을 프론트엔드로 전송한다.

  1. HTTP 헤더

    • 백엔드에서 생성된 CSRF 토큰을 HTTP 응답 헤더에 포함하여 클라이언트에게 전달
    • 프론트엔드는 이 헤더에서 토큰을 읽어와서 API 요청을 할 때 요청 헤더에 포함
    • 가장 일반적인 방법으로 스프링 시큐리티에 의해 기본적으로 CSRF 토큰을 HTTP 응답의 헤더에 포함하여 전달
  2. 쿠키

    • CSRF 토큰을 쿠키에 저장하여 프론트엔드에서 사용할 수 있도록 전달
    • 이 경우 프론트엔드는 쿠키에서 토큰을 읽어와서 API 요청을 할 때 요청 헤더에 포함
  3. 응답 본문

    • CSRF 토큰을 API 응답의 본문에 포함시켜 클라이언트에게 전달
    • 프론트엔드는 API 응답을 받은 후 토큰을 추출하여 요청 헤더에 포함



3. CSRF - HTTP 헤더

백엔드에서 생성된 CSRF 토큰을 HTTP 응답 헤더에 포함하여 클라이언트에게 전달하고, 프론트엔드는 이 헤더에서 토큰을 읽어와서 API 요청을 할 때 요청 헤더에 포함시키면 된다.

서버에서 CSRF 토큰을 HTTP 응답 헤더에 포함시키는 코드 (스프링 시큐리티)

import org.springframework.security.web.csrf.CsrfToken;

// CSRF 토큰 생성 후 응답 헤더에 추가
@GetMapping("/csrf-token")
public void getCsrfToken(HttpServletRequest request, HttpServletResponse response) {
    CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
    if (csrfToken != null) {
        Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
        String token = csrfToken.getToken();
        if (cookie == null || token != null && !token.equals(cookie.getValue())) {
            cookie = new Cookie("XSRF-TOKEN", token);
            cookie.setPath("/");
            response.addCookie(cookie);
        }
    }
}

스프링 시큐리티를 사용하여 JWT 토큰을 발급하고 관리하고 있다면, 클라이언트(리액트 애플리케이션)에게 액세스 토큰과 리프레시 토큰을 보내는 방법은 일반적으로 다음과 같다. 또한 CSRF 토큰도 함께 보내어 보안을 강화할 수 있다.

  1. 액세스 토큰 및 리프레시 토큰:
    • 사용자가 로그인하거나 인증을 요청할 때, 백엔드 서버(스프링 시큐리티)는 인증이 성공하면 JWT 형식의 액세스 토큰을 생성
    • 이 액세스 토큰은 HTTP 응답의 헤더나 본문에 포함하여 클라이언트에게 전달
    • 리프레시 토큰 역시 액세스 토큰과 함께 클라이언트에게 전달
    • 일반적으로 HTTP 응답의 헤더에 "Authorization" 헤더를 사용하여 액세스 토큰을 전달(필요한 경우 별도의 HTTP 쿠키를 사용)
  2. CSRF 토큰:
    • CSRF 공격을 방지하기 위해 백엔드(스프링 시큐리티)는 CSRF 토큰을 생성하고 클라이언트(리액트 애플리케이션)에게 전달
    • 일반적으로는 HTTP 응답의 본문에 CSRF 토큰을 포함하여 클라이언트에게 전달
    • 클라이언트는 이 CSRF 토큰을 저장하고, 백엔드로 API 요청을 보낼 때마다 요청 헤더나 본문에 CSRF 토큰을 포함

시나리오

  1. 사용자가 로그인 페이지에 접근하고 로그인을 시도한다.
  2. 리액트 애플리케이션은 사용자의 로그인 정보를 백엔드 서버로 전송한다.
  3. 백엔드 서버(스프링 시큐리티)는 사용자의 인증을 확인하고, 인증이 성공하면 JWT 형식의 액세스 토큰과 리프레시 토큰을 생성하여 클라이언트로 전달한다.
  4. 액세스 토큰은 클라이언트의 로컬 스토리지나 메모리에 저장되어 사용자의 세션을 유지하고, 리프레시 토큰은 보안적인 이유로 안전한 방법으로 보관한다.
  5. 동시에 백엔드 서버는 CSRF 공격을 방지하기 위해 CSRF 토큰을 생성하고, CSRF 토큰 HTTP 응답의 본문에 포함하여 클라이언트로 전달한다.
  6. 클라이언트는 받은 CSRF 토큰을 저장하고, 나중에 백엔드로 API 요청을 보낼 때마다 요청 헤더나 본문에 CSRF 토큰을 함께 포함시킨다.



4. CORS, JWT, CSRF

RESTful API 환경에서는 보통 쿠키나 세션을 사용하지 않고, 클라이언트와 서버 간의 통신을 위해 HTTP 헤더를 사용한다. 특히, JWT와 같은 토큰 기반의 인증 방식을 사용할 때에는 토큰을 HTTP 헤더에 포함시켜 요청을 보내는 것이 일반적이다.

이렇게 함으로써 클라이언트와 서버는 상태를 유지하기 위해 쿠키나 세션을 사용하지 않고, 요청과 응답의 헤더를 통해 필요한 정보를 주고 받는다. 이는 RESTful API의 특성과 함께 상태를 저장하지 않고(stateless) 통신하는 것을 가능하게 한다.


CORS, JWT, CSRF 이해 관계

RESTful API 환경에서 백엔드는 스프링 시큐리티를 사용하여 JWT 토큰을 발급하고 관리하고, 프론트엔드는 리액트와 같은 클라이언트 측 프레임워크를 사용한다고 가정하자. 이러한 환경에서 CSRF 공격, JWT 토큰 관리, 그리고 CORS(Cross-Origin Resource Sharing)를 고려한 시나리오는 다음과 같다.

  1. CSRF(Cross-Site Request Forgery) 공격 대비:

    • 백엔드(스프링 시큐리티)는 CSRF 공격을 방지하기 위해 CSRF 토큰을 생성하여 클라이언트(리액트)로 전달한다.
    • 클라이언트는 받은 CSRF 토큰을 저장하고, 백엔드로 API 요청을 보낼 때마다 요청 헤더에 CSRF 토큰을 포함한다.
    • 이를 통해 백엔드는 모든 요청에 대한 유효성을 검증하고, CSRF 공격으로부터 보호된다.
  2. JWT(JSON Web Token) 토큰 관리:

    • 사용자가 로그인하거나 인증을 요청할 때, 백엔드(스프링 시큐리티)는 사용자를 인증하고 JWT 형식의 액세스 토큰과 리프레시 토큰을 생성하여 클라이언트로 전달한다.
    • 클라이언트는 받은 액세스 토큰을 로컬 스토리지나 메모리에 저장하고, 필요할 때마다 요청 헤더에 액세스 토큰을 포함하여 API 요청을 보낸다.
    • 리프레시 토큰은 보안적인 이유로 안전한 방법으로 보관되고, 만료되면 재인증을 요청해야 한다.
  3. CORS(Cross-Origin Resource Sharing) 대응:

    • 프론트엔드(리액트)에서는 서로 다른 도메인에서 API를 호출할 때 CORS 정책에 따라 요청이 차단될 수 있다.
    • 백엔드(스프링 시큐리티)는 CORS를 허용하기 위해 필요한 설정을 구성하고, 클라이언트의 요청에 대해 적절한 CORS 헤더를 응답에 포함한다.
    • 이를 통해 클라이언트는 다른 도메인에서 백엔드로의 API 요청을 안전하게 보낼 수 있다.

시나리오

로그인 시나리오에서는 사용자의 로그인 정보를 안전하게 전달하고, CSRF 공격으로부터 보호하며, CORS 정책에 따라 다른 도메인의 API에도 안전하게 접근이 가능하다. 로그인 시나리오는 RESTful API 환경에서 보안을 유지하고 사용자의 데이터를 안전하게 관리하는 시나리오로서 CORS, JWT, CSRF 설정에 대해 간략히 설명한다.


  1. 사용자 로그인:
    • 사용자가 로그인 페이지에 접근하여 로그인을 시도
    • 프론트엔드(리액트)에서는 사용자가 입력한 로그인 정보를 백엔드(스프링 시큐리티)로 전송
  2. 백엔드에서의 동작:
    • 백엔드는 사용자의 인증을 검증하고, 유효한 경우에는 액세스 토큰과 리프레시 토큰을 생성하여 클라이언트로 전달
    • 동시에 백엔드는 CSRF 공격을 방지하기 위해 CSRF 토큰을 생성하여 클라이언트로 전달
  3. 프론트엔드에서의 동작:
    • 프론트엔드는 받은 액세스 토큰을 로컬 스토리지나 메모리에 저장하여 사용자의 세션을 유지
    • 받은 CSRF 토큰은 저장하고, 백엔드로의 API 요청을 보낼 때마다 요청 헤더에 CSRF 토큰을 포함
  4. API 요청:
    • 사용자가 로그인 후 다른 페이지로 이동하거나 다른 작업을 수행할 때마다 프론트엔드는 백엔드로의 API 요청
    • 이 때 요청 헤더에는 액세스 토큰과 CSRF 토큰이 포함
    • 백엔드는 받은 토큰들을 검증하고, 요청을 처리한 후 응답을 반환
  5. CORS 대응:
    • 클라이언트가 다른 도메인의 API를 호출할 경우, 백엔드는 CORS 정책을 허용하기 위한 설정을 구성
    • 이 설정에 따라 클라이언트의 요청에 대해 적절한 CORS 헤더가 응답에 포함