base

CSRF란

혹시 이메일이나 메신저에서 친구가 보낸 링크를 무심코 클릭했다가 본인의 계정에서 이상한 글이 자동으로 게시되거나, 의도하지 않은 상품이 구매되는 경험을 해본 적이 있으신가요? 이런 일이 발생하는 주요 원인 중 하나가 바로 CSRF(Cross-Site Request Forgery)입니다.


CSRF 공격은 인터넷에서 흔히 일어나지만 사용자 입장에서 쉽게 눈치채기 어렵고, 개발자 입장에서도 보안 설계가 소홀하면 간과할 수 있는 위험한 공격입니다. 오늘은 웹 서비스 개발자들이 반드시 알아야 할 CSRF의 기본 원리를 알아봅시다.


CSRF란 무엇인가요?

CSRF(Cross-Site Request Forgery)는 한국어로 사이트 간 요청 위조라고도 불립니다. 쉽게 말해, 사용자의 신뢰를 악용해 사용자가 원치 않는 요청을 서버로 보내도록 유도하는 공격입니다.


간단한 예를 들어볼까요? 사용자 A가 은행 웹사이트에 로그인한 상태에서 공격자가 보낸 링크를 클릭하면, A가 모르는 사이에 공격자가 설정한 계좌로 송금 요청이 실행되는 것입니다. 이때 사용자는 자신도 모르게 공격자의 요청을 대신 전달하는 역할을 하게 됩니다.


CSRF의 가장 큰 문제점은 사용자가 직접 행동한 요청인지, 공격자가 의도한 요청인지 서버가 구별하기 어렵다는 것입니다.


CSRF 공격의 원리 이해하기

CSRF 공격은 기본적으로 사용자가 로그인된 상태를 악용합니다. 웹사이트에서는 보통 쿠키나 세션을 통해 사용자의 로그인 상태를 유지합니다. 이때 공격자는 사용자가 접속한 사이트가 아닌, 다른 웹사이트에서 위조된 요청을 사용자 브라우저를 통해 전송하는 것입니다.


예시를 살펴보겠습니다. 사용자가 "hanpy-blog.com"에 로그인한 상태에서 공격자의 악성 웹페이지에 접속했다고 가정해 보겠습니다.


공격자가 다음과 같은 HTML 코드를 만들어 두었다면 어떻게 될까요?

csrf
<img src="https://hanpy.example.com/transfer?to=attacker&amount=10000" />

사용자가 이 악성 웹페이지에 접속하는 순간 브라우저는 이미지 요청이라고 생각하고 자동으로 이 요청을 실행하게 됩니다. 로그인된 상태라면 사용자의 쿠키 정보가 브라우저에 의해 자동으로 함께 전송되어 서버는 정상적인 요청으로 처리할 수도 있습니다.


이렇게 사용자는 본인도 모르게 의도치 않은 송금을 하게 되고, 피해를 입게 됩니다.


CSRF 방어 방법 (실무에서 자주 사용되는 방법들)

다행히 CSRF는 비교적 간단한 방법으로 효과적으로 방어할 수 있습니다. 주로 사용되는 몇 가지 방법을 소개해 드리겠습니다.

1. CSRF Token을 통한 방어

가장 널리 사용되는 방법은 CSRF Token을 사용하는 것입니다. 웹 페이지가 로드될 때 서버에서 무작위로 생성한 토큰을 HTML form 또는 헤더에 추가합니다. 요청이 들어왔을 때 서버는 이 토큰이 존재하는지 확인하고, 유효하지 않은 요청은 무시합니다.

csrf
// Express의 csrf 모듈 사용 예시
const csrf = require('csurf')
const express = require('express')
const cookieParser = require('cookie-parser')
 
const app = express()
 
app.use(cookieParser())
app.use(csrf({ cookie: true }))
 
app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() })
})
 
app.post('/transfer', csrf(), (req, res) => {
  // CSRF 토큰이 유효할 때만 송금 진행
})

이렇게 하면 공격자가 만든 사이트는 사용자의 올바른 CSRF 토큰을 알 수 없기 때문에, 위조된 요청을 보낼 수 없습니다.


2. SameSite 속성을 활용한 쿠키 보안 강화

쿠키에 SameSite 속성을 추가하여 CSRF 공격 가능성을 효과적으로 낮출 수 있습니다. SameSite 속성을 strict 또는 lax로 설정하면, 외부 사이트의 요청에서 자동으로 쿠키 전송을 차단해 공격을 예방합니다.

res.cookie('sessionId', sessionId, { httpOnly: true, sameSite: 'lax' })

SameSite 설정은 간편하고 강력한 보안 옵션으로, 최신 브라우저에서는 필수적인 보안 수단입니다.


3. Referer 헤더 검증하기

HTTP 요청의 Referer 헤더를 통해 요청이 우리 사이트에서 왔는지를 확인하여 CSRF 공격을 막을 수도 있습니다. 하지만 이 방법은 클라이언트 브라우저가 Referer 정보를 보내지 않는 경우도 있으므로, 보조적인 수단으로 활용됩니다.


정리

  • 모든 POST/PUT/DELETE 요청에 CSRF 토큰을 적용하세요. GET 요청은 데이터 변경을 유발하지 않아야 합니다.
  • 인증된 API에는 가능하면 SameSite와 CSRF 토큰을 함께 적용하여 보안성을 더욱 높이는 것이 좋습니다.
  • SPA(React, Vue 등)에서는 프론트엔드에서 헤더를 통해 CSRF 토큰을 전송하면, 보다 간편하게 구현할 수 있습니다.

참조