소프트웨어 개발보안 가이드 분석(2021) : 크로스사이트 요청 위조

크로스사이트 요청 위조(CSRF)란?

https://owasp.org/www-community/attacks/csrf

크로스사이트 요청 위조(CSRF)는 웹 애플리케이션의 보안 취약점 중 하나로, 사용자가 자신의 의지와는 무관하게 악의적인 요청을 보내게 만드는 공격 방법입니다. 이 공격은 사용자가 이미 인증된 세션을 통해 웹 애플리케이션에 요청을 보내도록 유도함으로써, 사용자의 권한을 악용합니다.

공격 매커니즘

크로스사이트 요청 위조(CSRF)
소프트웨어 개발 보안 가이드(행정자치부 / 한국인터넷진흥원)

 

크로스사이트 요청 위조 공격은 공격자는 악성 웹 페이지나 이메일을 통해 사용자의 브라우저에 악의적인 요청을 포함하는 링크나 스크립트를 제공하여 사용자가 이를 클릭하거나 실행하면, 웹 애플리케이션에 대한 요청이 자동으로 전송됩니다. 이때 사용자의 브라우저는 사용자의 인 정보를 가지고 있으며, 서버는 공격자의 요청과 정상적인 사용자의 요청을 구별하지 못하고 요청을 모두 수행합니다. 크로스사이트 요청 위조는 서버가 요청을 처리할 때 추가적인 확인 단계를 거치지 않거나, 요청의 출처를 확인하지 않는 경우에 발생합니다.

취약한 웹 어플리케이션의 예

다음은 이체 처리 기능을 수행하는 취약한 웹 애플리케이션에서의 크로스사이트 요청 위조 공격을 보여주는 간단한 예시입니다.

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class VulnerableServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String action = request.getParameter("action");
        if (action != null && action.equals("transfer")) {
            String toAccount = request.getParameter("toAccount");
            String amount = request.getParameter("amount");
            
            // 이체 처리
            transferFunds(toAccount, amount);
            response.getWriter().println("Transfer completed successfully.");
        } else {
            response.getWriter().println("Invalid action.");
        }
    }

    private void transferFunds(String toAccount, String amount) {
        // 이체 로직
        // ...
    }
}

위 코드에서는 GET 요청을 처리하는 doGet 메서드가 있으며, 이 메서드는 사용자의 요청에 따라 "이체" 처리 작업을 수행합니다. 그러나 해당 코드에서는 요청이 유효한지 검증하는 메커니즘이 존재하지 않습니다. 유효성을 검증하지 않는 취약점을 이용하여 공격자가 피해자로부터 사용자의 인증 정보를 획득한 후, 악의적인 GET 요청을 서버에 보내면 공격자의 요청과 정상적인 사용자의 요청을 구별하지 못하고 해당 요청을 처리하게 되어 피해자가 자신의 의지와는 무관하게 이체 기능을 수행하게 됩니다.

시큐어코딩 적용 방법

위와 같은 크로스사이트 요청 위조 공격에 대한 방안으로는 다음과 같은 방법들이 있습니다.

  1. CSRF 토큰 사용: 모든 중요한 요청에 CSRF 토큰을 포함하여 서버로 전송하고, 서버는 이 토큰이 유효한지 확인합니다.
  2. 리퍼러 검증: 요청의 리퍼러 헤더를 확인하여 해당 요청이 신뢰할 수 있는 출처에서 온 것인지 확인합니다.
  3. 인증 및 권한 검사: 모든 요청에 대해 인증 및 권한 검사를 수행하여 인증되지 않은 요청이나 권한이 없는 요청을 거부합니다.
  4. CAPTCHA 기능 추가: CAPTCHA는 "Completely Automated Public Turing test to tell Computers and Humans Apart"의 약자로, 컴퓨터와 사람을 구별하기 위한 완전 자동화된 공개 튜링 테스트로 웹사이트나 애플리케이션에서 자동 등록이나 악의적인 봇에 의한 공격을 방지하기 위해 사용되는데, 사용자가 비밀번호 변경이나 개인정보 수정 등과 같이 중요한 기능을 수행할 때 CAPTCHA 기능을 통하여 추가적인 인증 단계를 수행함으로써, 악성 스크립트로 인하여 발생하는 문제를 예방할 수 있습니다.

아래는 CSRF 공격을 방지하기 위해 CSRF 토큰을 사용하는 안전한 코드 예시입니다.

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

public class SecureServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String action = request.getParameter("action");
        if (action != null && action.equals("transfer")) {
            String toAccount = request.getParameter("toAccount");
            String amount = request.getParameter("amount");
            
            // CSRF 토큰 검증
            String csrfToken = request.getParameter("csrfToken");
            if (isValidCSRFToken(csrfToken)) {
                // 이체 처리
                transferFunds(toAccount, amount);
                response.getWriter().println("Transfer completed successfully.");
            } else {
                response.getWriter().println("CSRF Token is invalid.");
            }
        } else {
            response.getWriter().println("Invalid action.");
        }
    }

    private boolean isValidCSRFToken(String token) {
        // CSRF 토큰 유효성 검증 로직
        // ...
        return true;
    }

    private void transferFunds(String toAccount, String amount) {
        // 이체 로직
        // ...
    }

    private String generateCSRFToken() {
        // 랜덤한 CSRF 토큰 생성
        return UUID.randomUUID().toString();
    }
}

위의 코드에서는 사용자가 이체를 수행하는 경우에 CSRF 토큰을 요청 파라미터로 전송하도록 하고, 서버에서는 이 토큰이 유효한지 유효성 검증을 통하여 해당 CSRF 토큰이 유효하지 않은 경우 서버는 해당 요청을 거부하여 용자의 세션을 이용한 악의적인 요청을 방지할 수 있습니다.

  • 이전 소프트웨어 개발보안 가이드 분석(2021) : LDAP 삽입
  • 다음 소프트웨어 개발보안 가이드 분석(2021) : 서버사이드 요청 위조