HOME
NOTE

교차 사이트 요청 위조(Cross-Site Request Forgery)

CREATED
2025. 3. 30. 오후 12:19:59
UPDATED
2025. 3. 31. 오후 12:09:33
TAGS
#Web

개요

  • 정의: 공격자가 사용자가 자신의 의지와 상관없이 웹 사이트에 요청을 보내도록 속이는 공격
  • 작동 방식:
    1. 사용자가 웹 사이트에 로그인한 상태에서 공격자가 만든 악성 웹 사이트를 방문
    2. 악성 웹 사이트는 사용자의 브라우저를 통해 로그인된 웹 사이트에 위조된 요청을 보냄
    3. 웹 사이트는 사용자가 보낸 것으로 판단하고 요청을 처리, 예를 들어, 비밀번호 변경, 계정 정보 수정 등이 발생할 수 있음
  • 예방 방법:
    • CSRF 토큰 사용: 각 요청마다 예측 불가능한 토큰을 포함시켜 서버에서 유효성을 검사
    • SameSite 쿠키 설정: 쿠키가 동일한 사이트에서만 전송되도록 설정
    • 요청 출처 확인 (Origin Header Check): 요청을 보낸 출처가 신뢰할 수 있는 출처인지 확인

예시

시나리오: 사용자가 웹사이트에 로그인한 상태에서 CSRF 공격을 받는 경우

  1. 공격:
    공격자는 다음과 같은 HTML 코드를 포함한 악성 웹사이트를 만들 수 있음
    <form action="http://example.com/change-password" method="POST">
      <input type="hidden" name="password" value="new_password">
      <input type="submit" value="Submit">
    </form>
    <script>
      document.forms[0].submit(); // 자동 제출
    </script>
    
  2. 영향:
    • 사용자가 웹사이트에 로그인한 상태로 악성 웹사이트를 방문하면, 폼이 자동으로 제출
    • 서버는 사용자가 비밀번호 변경을 요청한 것으로 인식하고 비밀번호를 변경
  3. 대비 방법:
    • CSRF 토큰 사용:
      import React, { useState, useEffect } from 'react';
      import axios from 'axios';
      
      function ChangePasswordForm() {
        const [csrfToken, setCsrfToken] = useState('');
        const [password, setPassword] = useState('');
      
        useEffect(() => {
          axios.get('/csrf-token')
            .then(response => {
              setCsrfToken(response.data.csrfToken);
            });
        }, []);
      
        const handleSubmit = (event) => {
          event.preventDefault();
          axios.post('/change-password', {
            password: password,
            _csrf: csrfToken
          }).then(response => {
            console.log(response);
          });
        };
      
        return (
          <form onSubmit={handleSubmit}>
            <input type="hidden" name="_csrf" value={csrfToken} />
            <input
              type="password"
              name="password"
              value={password}
              onChange={e => setPassword(e.target.value)}
            />
            <button type="submit">Change Password</button>
          </form>
        );
      }
      
      각 요청에 예측 불가능한 토큰을 포함시켜 서버에서 유효성을 검사
      express server
      const express = require('express');
      const csrf = require('csurf');
      const cookieParser = require('cookie-parser');
      
      const app = express();
      
      app.use(cookieParser());
      const csrfProtection = csrf({ cookie: true });
      app.use(csrfProtection);
      
      app.get('/csrf-token', (req, res) => {
        res.json({ csrfToken: req.csrfToken() });
      });
      
      app.post('/change-password', csrfProtection, (req, res) => {
        // 비밀번호 변경 로직
        res.send('Password changed');
      });
      
    • SameSite 쿠키 설정:
      app.use((req, res, next) => {
        res.cookie('sessionid', '123', { sameSite: 'Strict' });
        next();
      });
      
      쿠키를 설정할 때 SameSite 속성을 사용하여 쿠키가 동일한 사이트에서만 전송되도록 함
    • 요청 출처 확인 (Origin Header Check):
      const allowedOrigins = ['http://example.com'];
      
      app.use((req, res, next) => {
        const origin = req.get('origin');
        if (allowedOrigins.includes(origin)) {
          res.header('Access-Control-Allow-Origin', origin);
        }
        next();
      });
      
      서버에서 요청의 Origin 또는 Referer 헤더를 확인하여 요청이 신뢰할 수 있는 출처에서 왔는지 확인

참고

  • Google Gemini 답변