CSRF (Cross-Site Request Forgery) 는  웹 애플리케이션이 사용자의 신뢰를 받는 상태에서 공격자가 사용자의 권한을 도용해 임의의 요청을 서버에 보내는 보안 취약점입니다.

공격 흐름

  1. 사용자는 인증된 세션(로그인 상태)을 가지고 특정 사이트에 접근.
  2. 공격자가 악의적인 사이트에 CSRF 스크립트를 심어 사용자가 해당 스크립트에 접근하도록 유도.
  3. 스크립트는 사용자의 인증 쿠키를 탈취해 서버로 요청을 보냄.
  4. 서버는 이 요청을 신뢰하고, 사용자가 의도하지 않은 작업(데이터 삭제, 송금 등)을 실행함.

ex) 사용자 A가 은행 웹사이트에 로그인한 상태에서 악의적인 링크를 클릭했을 때, 그 링크가 공격자의 계좌로 돈을 송금하도록 만든 요청을 서버에 보냄.

피싱 사이트에서 사용자의 세션 정보를 탈취해 요청을 전송하면, CSRF의 취약성이 있는 경우 서버가 정상처리로 간주하게 됩니다. 특히 <form> 요소로 전송되는 요청은 동일 출처 정책에 의해 제한되지 않습니다. 그래서 쿠키나 세션의 사용에 있어서 보안을 강화하는게 이번 챕터의 주제입니다.

토큰을 사용하는 CSRF 대책

CSRF의 가장 효과적인 대책으로, 다른 사람이 추측할 수 없는 비밀 문자열인 토큰을 사용하는 방법입니다. 이 토큰은 서버가 피싱 사이트에서 보낸 요청인지, 웹 어플리케이션에서 보낸 정상 요청인지 확인하는데 사용됩니다.

  1. 토큰 생성: 사용자가 웹 애플리케이션에 로그인하면, 서버는 임의의 토큰을 생성하여 세션과 연관 지어 저장합니다.
  2. 토큰 전송: 사용자가 폼을 제출할 때, 이 토큰은 숨겨진 필드에 담겨 전송되거나 HTTP 헤더에 포함되어 서버로 전송됩니다.
  3. 토큰 검증: 서버는 전송된 토큰과 세션에 저장된 토큰을 비교하여 일치하는 경우에만 요청을 처리합니다.
  4. 토큰 재발급: 일단 요청이 처리되면, 서버는 새로운 토큰을 발급하거나 기존 토큰을 재사용할 수 있습니다.

이 방법은 공격자가 결국 유효한 토큰을 알아야 하는데 Same-origin policy에 의해 공격자가 토큰을 도용할 수 없습니다.

<form action="/transfer" method="POST">
  <input type="hidden" name="csrf_token" value="generated_token">
  <input type="submit" value="Transfer">
</form>

위처럼 type=”hidden”으로 토큰을 같이 보냅니다.

그러나 요청마다 랜덤토큰을 발급하고 서버에서 유지해야한다는 점에서 서버 리소스를 소모합니다.

Double Submit 쿠키를 사용하는 CSRF 대책

이 방법은 서버에 토큰을 유지하지 않고 브라우저의 쿠키에 유지하는 방법입니다. 그래서 요청을 보낼때 본문 혹은 헤더에 토큰을, 쿠키에 토큰을 동시에 전달하여 요청을 검증하는 방식입니다.

  1. 토큰 발급: 서버가 사용자가 페이지에 접속할 때 세션용 쿠키와 HttpOnly 속성이 부여되지 않은 토큰을 생성하여 클라이언트의 쿠키에 저장합니다.
  2. 폼 전송: 클라이언트가 폼 데이터를 전송할 때, 자바스크립트로 가져온 쿠키 내부의 토큰을 요청 본문(POST 요청의 hidden 필드)이나 HTTP 헤더에 추가하여 함께 전송합니다.
  3. 서버 검증: 서버는 요청을 받았을 때, 쿠키에 저장된 CSRF 토큰과 폼 데이터(혹은 헤더)에 담긴 토큰이 일치하는지 확인합니다. 일치하면 요청을 처리하고, 일치하지 않으면 요청을 거부합니다.

CSRF 공격자는 서버로 요청을 보낼 때 클라이언트의 쿠키에 접근할 수 없도록 브라우저가 관리하므로 안전합니다. (그래서 XSS 안전성도 확보가 되어야합니다)

이 방법은 API 서버와 프론트엔드 서버가 분리되어있을때 토큰을 서버에 저장하는 방법을 사용할 수 없기 때문에 CSRF 방지에 효과적입니다.

SameSite 쿠키를 사용하는 CSRF 대책

SameSite 쿠키는 브라우저가 쿠키를 제어하는 기능으로, 쿠키가 전송되는 상황을 제한하는 방법입니다. 그래서 쿠키가 동일 출처에서만 전송될 수 있도록 합니다. (동일 출처 => eTLD+1)

Set-Cookie 헤더에 쿠키를 설정할 때 SameSite 속성을 지정하면 됩니다.

  1. SameSite=Lax(default):
  • 기본 설정 값으로, 사용자가 링크를 클릭하거나 GET 요청을 보낼 때는 쿠키가 전송됩니다.
  • 하지만 POST, PUT 등의 다른 요청이나 AJAX 요청을 보낼 때는 쿠키가 전송되지 않으므로 CSRF 공격을 상당 부분 방어할 수 있습니다.
  • 주로 사용자 경험을 해치지 않으면서 보안을 유지할 수 있는 옵션입니다.
  1. SameSite=Strict:
  • 엄격한 보안 옵션으로, 동일 출처에서 발생한 요청이 아니면 쿠키가 절대로 전송되지 않습니다.
  • 외부 사이트에서 링크를 클릭하더라도 쿠키가 전송되지 않기 때문에, 모든 CSRF 공격을 원천적으로 방어할 수 있습니다.
  • 하지만 로그인 세션을 유지하기 위한 쿠키도 전송되지 않기 때문에 사용자 경험에 부정적인 영향을 미칠 수 있습니다.
  1. SameSite=None:
  • 쿠키가 모든 요청에 전송됩니다. 이는 CSRF 방지에 도움을 주지 않으며, Secure 속성과 함께 사용해야만 작동합니다. Secure 속성은 HTTPS에서만 쿠키를 전송하게 합니다.

Lax라 해도 최소한의 안전장치 정도로 생각해야합니다. 왜냐하면 대부분의 공격을 막는다 하더라도 GET 요청으로 데이터를 수정하거나 전송하는데 사용된다면 막을 수 없기 때문입니다.

Origin 헤더를 사용하는 CSRF 대책

Origin은 해당 요청이 어떤 출처에서 발생했는지를 나타냅니다. 즉, 요청이 발생한 도메인, 프로토콜, 포트정보를 서버에 전송하기에 신뢰하는 Origin인지만 판단하면 됩니다. 간단하고 매우 강력한 방법이며 프론트에선 자동으로 호출해줍니다.

다만 Origin 헤더를 지원하지 않는 브라우저를 조심해야하는데 사실상 다 지원합니다…

(프론트 서버와 api 서버가 다를때 유리)

MDN

CORS를 사용하는 CSRF 대책

X-Requested-With: XMLHttpRequest와 같은 임의 헤더로 의도적인 Preflight request를 발생시킵니다. 그리고 허가된 출처로부터의 요청인지, 추가한 헤더가 있는지를 파악하여 요청을 에러처리하면 됩니다.

(프론트 서버와 api 서버가 다를때 유리)

profile
👨🏻‍💻 Front-End Developer

0개의 댓글