보안 - CSRF

질문Bot·2026년 2월 8일

보안

목록 보기
1/1
post-thumbnail

사이드 프로젝트를 할 때는
CSRF 같은 보안 이슈를 깊게 고민해본 적이 거의 없었다.

사용자도 많지 않았고,
솔직히 말하면 이런 생각이 더 컸다.

“설마 누가 이걸 공격하겠어?”

하지만 서비스를 실제로 배포하고,
“모든 케이스를 고려해야 하는 상황”이 되니까
생각이 완전히 바뀌었다.

이제 보안은 옵션이 아니라 기본값이었다.
그래서 가장 먼저 정리하기로 한 게 CSRF였다.


1. CSRF (Cross-site request forgery) 란?

CSRF는 사이트 간 요청 위조를 의미한다.

웹 보안 취약점 중 하나로, 사용자가 자신의 의지와는 상관없이
공격자가 의도한 요청을 특정 웹사이트로 보내게 만드는 공격이다.

예를 들면 이런 것들이다.

  • 이메일 주소 변경
  • 비밀번호 변경
  • 배송지 변경
  • 결제 / 이체
  • 계정 탈퇴

특성에 따라 공격자는
사용자의 계정 권한으로 중요한 행위를 직접 실행해버릴 수 있다.

간단하게 정리하자면!

공격자가 내 계정에 로그인하는 게 아니라,
내 브라우저를 이용해서 “내 계정으로” 요청을 만든다.


2. CSRF가 성립하는 조건

CSRF는 아무 상황에서나 발생하지 않는다.
보통 아래 조건들이 동시에 만족돼야 한다.

  1. 사용자는 이미 로그인된 상태여야 한다

    → 브라우저에 인증 쿠키 / 세션이 남아 있음

  2. 서비스가 쿠키 기반 인증을 사용해야 한다

    → 요청마다 브라우저가 쿠키를 자동으로 포함

  3. 공격자가

    “어떤 요청이 상태를 바꾸는지”를 알고 있어야 한다
    → 엔드포인트, 파라미터 구조가 예측 가능해야 함

API 구조가 프론트에 그대로 노출되어 있거나
REST 규칙을 너무 정직하게 지키고 있을수록
오히려 공격 포인트가 되기도 한다.


3. CSRF 동작 원리 (가장 많이 하는 오해)

여기서 자주 생기는 오해가 하나 있다.

❌ “공격자가 세션 쿠키를 훔쳐야 CSRF가 되는 거 아닌가요?”

👉 아니다.

대부분의 CSRF 공격은
쿠키를 훔치지 않는다.

흐름은 생각보다 단순하다.

  1. 사용자가 우리 서비스에 로그인한다
  2. 브라우저 쿠키에 session_id / access_token 이 저장된다
  3. 사용자가 외부 사이트(또는 악성 페이지)를 방문한다
  4. 외부 사이트가 우리 서비스로 상태 변경 요청을 유도한다
  5. 브라우저는 자동으로 쿠키를 포함해서 요청을 보낸다
  6. 서버는 쿠키를 보고
    “로그인 사용자 요청이네?” 하고 처리해버린다

즉 핵심은 이거다.

공격자는
쿠키를 읽을 필요도 없고, 훔칠 필요도 없다.
사용자의 브라우저가 대신 요청을 보내주기만 하면 된다.


4. 예시

취약한 서비스에서
배송지 변경 API가 이렇게 열려 있다고 가정해보자.

POST /api/account/shipping-address
Cookie: session_id=s3ss10n_xxxxxxxxx
Content-Type: application/json

{"address":"서울시 어딘가 123-45"}

사용자가 이미 로그인된 상태라면
공격자는 session_id 값을 전혀 몰라도 된다.

외부 페이지에서
이 요청만 한 번 유도하면 끝이다.

서버 입장에서는
“로그인한 사용자가 보낸 요청”처럼 보이기 때문이다.


⚠️ GET 요청이 특히 위험한 이유

CSRF 이야기에서 빠지지 않는 주제가 하나 더 있다.
바로 GET 요청이다.

GET은 원래 조회용인데,
실수로 상태 변경 API를 GET으로 열어두면 바로 위험해진다.

  • /logout
  • /unsubscribe
  • /delete-account
<img src="https://vulnerable-app.local/logout" />

이 코드 한 줄만 있어도
클릭 없이 요청이 나간다.

그래서 결론은 명확하다.

상태를 바꾸는 요청은
무조건 POST / PUT / PATCH / DELETE 로만 처리한다.


5. 사용자 입장에서의 CSRF 방어

사용자 입장에서 할 수 있는 건 사실 많지 않다.

  • 이상한 링크 웬만하면 클릭 안 하기
  • 의심스러운 메일 / DM 링크 열지 않기
  • 로그인 상태로 여기저기 돌아다니는 거 줄이기
    CSRF가 골치 아픈 이유는
    클릭 한 번, 혹은 클릭조차 없이도
    내가 의도하지 않은 action이 실행될 수 있기 때문이다.

6. 개발자 입장 – CSRF 방어 방법

방법 1. Referer / Origin 체크

요청 헤더의 Referer 또는 Origin 값을 보고

우리 도메인에서 온 요청인지 검사하는 방식이다.

정상 요청 예시

Host: app.local
Referer: https://app.local/profile

외부에서 유도된 요청

Host: app.local
Referer: https://evil.local/page

이걸 보고 다르면 차단한다.

❗ 왜 이것만으로는 부족할까?

  1. Referer는 항상 오는 값이 아니다

    → 브라우저 / 정책에 따라 누락될 수 있음

  2. 절대적인 신뢰 값이 아니다

    → curl, Postman 같은 클라이언트에서는 조작 가능

그래서 결론은 하나다.

Referer / Origin 체크는
보조 수단이지, 단독 방어책은 아니다.


7. CSRF Token

원리는 단순하다.

  • 서버가 랜덤 토큰을 발급
  • 상태 변경 요청마다 토큰을 같이 보내게 함
  • 서버는 토큰이 일치하는지 검사

우리가 구현한 플로우 (프론트 기준)

  1. 로그인 시
    • access_tokenhttpOnly=true
    • csrf_tokenhttpOnly=false
  2. 프론트에서 API 요청 시
    • document.cookiecsrf_token 읽음
    • x-csrf-token 헤더에 담아 전송
  3. 서버에서
    • 쿠키의 csrf_token
    • 헤더의 x-csrf-token 비교
    • 같으면 통과 / 다르면 차단

핵심 포인트는 이거다.

  • 외부 사이트는 Same-Origin Policy 때문에 우리 도메인의 쿠키 값을 읽을 수 없다
  • 그래서 외부 사이트는 x-csrf-token 헤더를 구성할 수 없다
  • 결국 헤더 토큰 검증 단계에서 차단된다

8. 이러면 완벽한가?

아니다.

여기서 항상 나오는 질문이 있다.

“자바스크립트로 쿠키 읽어서 헤더 넣는 구조면
스크립트로 요청 보내면 되는 거 아닌가요?”

맞다. 그건 CSRF가 아니라 XSS다.

  • CSRF: 외부 사이트에서 요청을 위조
  • XSS: 우리 사이트 내부에서 JS 실행

공격자가 우리 사이트 컨텍스트에서 JS를 실행할 수 있다면

  • document.cookie 읽을 수 있고
  • CSRF 토큰도 헤더에 넣을 수 있다

그래서 결론은 이거다.

CSRF 방어는 XSS 방어와 같이 가야 좋을듯하다고 생각이 된다.

profile
유용한 정보를 전달하는 사람이 되고자 노력합니다.

0개의 댓글