Spring Security : CSRF / CSRF Filter

    이 글은 인프런 정수원님의 강의를 복습하며 작성한 글입니다.

    CSRF(사이트 간 요청 위조)

    보안에 대해서 잘 모르는 내가 간단히 봤을 때, CSRF은 예전에 자주 유행하던 피싱 메일인 것 같다. 내가 특정 메일의 링크를 클릭하면서 뭔가 크게 잘못되는 경우로 이해할 수 있다. 아래를 보면 좀 더 명확하다.

    1. 사용자가 쇼핑몰에 로그인한다. 서버는 인증 처리를 해주면서, 쇼핑몰에게 세션 쿠키를 전달한다. 
    2. 공격자가 링크를 사용자에게 전달한다. ex) 로또 당첨 축하드립니다! 같은 메일 
    3. 공격용 웹 페이지에는 로또 당첨 이미지가 있고, 그걸 누르면 거기에 있는 URL 태그에 맞기 쇼핑몰에 공격자 요청을 하게 된다. 
    4. 이 때, 잘못된 요청이라고 할지라도, 서버는 로그인 인증을 받은 사용자 브라우저가 요청을 하고 있는 것으로 받아들인다. 왜냐하면 사용자 브라우저가 정상 발급된 쿠키를 헤더에 포함해서 요청하기 때문이다. 따라서 쇼핑몰은 공격자의 요청을 정상적인 요청으로 판단하고 그에 대해 응답을 하게 된다. 
    5. 이 때 주로 하는 요청은 POST / POT 메서드 형식으로 Body나 Header에 특정 값을 넣어 서버에 주요한 값을 조작하도록 하는 것이다.
    6. 공격자는 이에 따라 새로운 이익을 취하게 된다. 

    즉, 공격자가 마련한 구매 링크 및 주소 링크를 쇼핑몰에 접속한 사용자가 클릭하게 되면, 쇼핑몰에서 엉뚱한 결제 메일이 날아오면서 가장의 평화가 부서지는 위기가 발생할 수 있다는 것이다. Spring Security는 가정의 평화를 지키기 위해 CSRF 필터를 제공하며, 이를 통해 CSRF 공격을 방지해준다. 

     

    Spring Security CSRF Filter 

    Spring Security는 CSRF Filter를 제공하여 CSRF 공격을 방지한다. CSRF Filter가 하는 일은 매 요청마다 랜덤하게 토큰을 생성하고, 클라이언트에게 그것을 발급하는 일이다. 이후 클라이언트가 서버의 자원에 접근할 때는 반드시 이 토큰을 헤더에 포함해서 와야한다. 이 토큰이 포함되지 않거나 일치하지 않을 경우 사용자의 요청은 실패하게 된다. 

    클라이언트는 다음 HTTP 메서드(Patch, Post, Put, Delete)로 서버에 요청할 경우, 반드시 서버에서 발급한 CsrF 토큰명 + 토큰값을 헤더에 포함해서 요청해야한다. 만약 이것이 지켜지지 않을 경우, 스프링 시큐리티는 이런 사용자 요청이 접근하지 못하도록 제어한다. 

     

    Spring Security CSRF API 

    • http.csrf() : CSRF 필터 기능을 활성화한다. Default로 적용되어있음.
    • http.csrf().disabled() : CSRF 기능을 비활성화한다. 

    Spring Securith는 HttpSecurity 객체를 통해 CSRF Filter 기능을 제공한다.

     

    CSRF 필터를 적용하면 어떻게 바뀌는가?

    1. 사용자가 쇼핑몰에 로그인하고, 서버는 쿠키를 발급해서 내려준다. 이 때, CSRF Token이 포함된다.
    2. 공격자가 제공하는 링크를 사용자가 클릭한다. 사용자가 공겨자용 페이지를 열면, 사용자의 브라우저는 이미지 파일을 받아오기 위해 공격용 URL을 연다.
    3. 사용자 브라우저가 서버로 보낸 공격용 URL의 Form에는 CSRF Token이 포함되어있지 않음. 
    4. 이 때, Form 형식은 POST 방식으로 요청하고, POST 방식으로 요청이 왔을 때 CSRF Toekn 명 + 데이터가 없기 때문에 서버는 공격자의 요청을 무시한다. 

     

    실습1 / CSRF Token 없이 POST로 접근

    CSRF Filter에 디버그 모드를 찍은 후, 포스트맨으로 POST 메서드로 localhost:8080를 요청해본다. 이 때, Header / Body에 어떠한 값도 설정하지 않는다.

    CSRFFilter

    먼저 CSRF Filter로 와서 loadToken을 통해 현재 요청에 대해 생성 혹은 저장된 토큰이 있는지 확인하고, 없는 경우 생성해서 csrfToken에 저장해준다.

    request.getHeader()를 통해 Header에 전달된 CsrfToken 값이 있는지 확인한다. Header에 없는 경우 getParameter()를 통해서 Body에 CSRF Token 값이 전달되었는지 확인한다. 이 때, 둘다 없기 때문에 actual Token에는 Null값만 설정된다. 

    actuacl Token의 값이 null이기 때문에 If문은 항상 True가 되고, 이어서 AccessDeniedException이 발생하고 후속 처리는 accessDeniedHandler가 처리를 해준다. 즉, CSRF Token이 없는 경우 접근을 할 수가 없게 된다. 

     

    실습2 / CSRF Token과 함께 POST로 접근

    먼저 CSRF Filter에서 loadToken을 통해서 얻어지는 CSRF 토큰 객체를 받는다. 그리고 거기에 적혀있는데로 headerName을 설정하고, 값을 token값을 복사한다.

    POST MAN에 다음과 같이 헤더에 토큰을 포함해주고 다시 POST로 요청을 발송한다. 

    이렇게 하면 Actual Token이 존재하는 것을 확인할 수 있다.

    최종적으로 CSRF 검사가 통과되어 후속 처리를 할 수 있게 된다.

     

    Csrf().disabled()를 하게 되면? 

     

    좌 : 아무 설정 X / 우 : csrf().disabled()

    FilterChainProxy에서 아예 CSRF Filter가 생성되지 않는 것을 확인할 수 있다.

     

    참고 사항

    Spring의 View Template 역할을 하는 타임리프는 개발자가 별도로 생성하지 않아도 POST 요청을 할 때 CSRF Token을 자동으로 생성해준다고 한다. 또한 스프링의 FORM 태그를 사용할 때도 CSRF TOKEN을 자동으로 생성해준다고 한다. 그렇지만 JSP 같은 경우에는 지원을 해주지 않기 때문에 사용하기 위해서는 Form Tag에 hidden 값으로 _csrf를 넣어줘야한다고 한다. 

     

    댓글

    Designed by JB FACTORY