안드로이드

oday with a RESTful architecture becoming more and more standard it might be worthwhile to spend some time rethinking your current security approaches. Within this small series of blog posts we’ll explore a few relatively new ways of solving web related security issues in a Stateless way. This first entry is about protecting your website against Cross-Site Request Forgery (CSRF).

Recap: What is Cross-Site Request Forgery?

CSRF attacks are based on lingering authentication cookies. After being logged in or otherwise identified as a unique visitor on a site, that site is likely to leave a cookie within the browser. Without explicitly logging out or otherwise removing this cookie, it is likely to remain valid for some time.

Another site can abuse this by having the browser make (Cross-Site) requests to the site under attack. For example including some javascript to make a POST to “http://siteunderattack.com/changepassword?pw=hacked” tag will have the browser make that request, attaching any (authentication) cookies still active for that domain to the request!

Even though the Single-Origin Policy (SOP) does not allow the malicious site access to any part of the response. As probably clear from the example above, the harm is already be done if the requested URL triggers any side-effects (state changes) in the background.

 Common approach

The commonly used solution would be to introduce the requirement of a so-called shared secret CSRF-token and make it known to the client as part of a previous response.
The client is then required to ping it back to the server for any requests with side-effects. This can be done either directly within a form as hidden field or as a custom HTTP header. Either way other sites cannot successfully produce requests with the correct CSRF-token included, because SOP prevents responses from the server from being read cross-site. The issue with this approach is that the server needs to remember the value of each CSRF-token for each user inside a session.

Stateless approaches

1. Switch to a full and properly designed JSON based REST API.

Single-Origin Policy only allows cross-site HEAD/GET and POSTs. POSTs may only be one of the following mime-types: application/x-www-form-urlencoded, multipart/form-data, or text/plain. Indeed no JSON! Now considering GETs should never ever trigger side-effects in any properly designed HTTP based API, this leaves it up to you to simply disallow any non-JSON POST/PUT/DELETEs and all is well. For a scenario with uploading files (multipart/form-data) explicit CSRF protection is still needed.

2. Check the HTTP Referer header.

The approach from above could be further refined by checking for the presence and content of a Referer header for scenarios that are still susceptible, such as multipart/form-data POSTs. This header is used by browsers to designate which exact page (url) triggered a request. This could easily be used to check against the expected domain for the site. Note that if opting for such a check you should never allow requests without the header present.

3. Client-side generated CSRF-tokens.

Have the clients generate and send the same unique secret value in both a Cookie and a custom HTTP header. Considering a website is only allowed to read/write a Cookie for its own domain, only the real site can send the same value in both headers. Using this approach all you server has to do is check if both values are equal, on a stateless per request basis!

Implementation

Focussing on the 3rd approach for explicit but Stateless CSRF-token based security, lets see how this looks like in code using Spring Boot and Spring Security.

Within Spring Boot you get some nice default security settings which you can fine tune using your own configuration adapter. In this case all that is needed is to disable the default csrf behavior and add own own StatelessCSRFFilter:

Customize csrf protection

   @EnableWebSecurity
   @Order(1)

public class StatelessCSRFSecurityConfig

        extends WebSecurityConfigurerAdapter {

 

    @Override

    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable().addFilterBefore(

            new StatelessCSRFFilter(), CsrfFilter.class);

    }

}

 

 

 

And here is the implementation of the StatelessCSRFFilter:

Custom CSRF filter

public class StatelessCSRFFilter extends OncePerRequestFilter {

 

    private static final String CSRF_TOKEN = "CSRF-TOKEN";

    private static final String X_CSRF_TOKEN = "X-CSRF-TOKEN";

    private final RequestMatcher requireCsrfProtectionMatcher = new DefaultRequiresCsrfMatcher();

    private final AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();

 

    @Override

    protected void doFilterInternal(HttpServletRequest request,

            HttpServletResponse response, FilterChain filterChain)

            throws ServletException, IOException {

         

        if (requireCsrfProtectionMatcher.matches(request)) {

            final String csrfTokenValue = request.getHeader(X_CSRF_TOKEN);

            final Cookie[] cookies = request.getCookies();

 

            String csrfCookieValue = null;

            if (cookies != null) {

                for (Cookie cookie : cookies) {

                    if (cookie.getName().equals(CSRF_TOKEN)) {

                        csrfCookieValue = cookie.getValue();

                    }

                }

            }

 

            if (csrfTokenValue == null || !csrfTokenValue.equals(csrfCookieValue)) {

                accessDeniedHandler.handle(request, response, new AccessDeniedException(

                        "Missing or non-matching CSRF-token"));

                return;

            }

        }

        filterChain.doFilter(request, response);

    }

 

    public static final class DefaultRequiresCsrfMatcher implements RequestMatcher {

        private final Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");

 

        @Override

        public boolean matches(HttpServletRequest request) {

            return !allowedMethods.matcher(request.getMethod()).matches();

        }

    }

}

 

As expected the Stateless version doesn’t do much more than a simple equals() on both header values.

Client-side implementation

Client-side implementation is trivial as well, especially when using AngularJS. AngularJS already comes with build-in CSRF-token support. If you tell it what cookie to read from, it will automatically put and send its value into a custom header of your choosing. (The browser taking care of sending the cookie header itself.)

You can override AngularJS’s default names (XSRF instead of CSRF) for these as followed:

Set proper token names

 


$http.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';

$http.defaults.xsrfCookieName = 'CSRF-TOKEN';

 

Furthermore if you want to generate a new token value per request you could add a custom interceptor to the $httpProvider as followed:

Interceptor to generate cookie

app.config(['$httpProvider', function($httpProvider) {

    //fancy random token, losely after https://gist.github.com/jed/982883

    function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e16]+1e16).replace(/[01]/g,b)};

 

    $httpProvider.interceptors.push(function() {

        return {

            'request': function(response) {

                // put a new random secret into our CSRF-TOKEN Cookie before each request

                document.cookie = 'CSRF-TOKEN=' + b();

                return response;

            }

        };

    });   

}]);

 

 

 

You can find a complete working example to play with at github.
Make sure you have gradle 2.0 installed and simply run it using “gradle build” followed by a “gradle run”. If you want to play with it in your IDE like eclipse, go with “gradle eclipse” and just import and run it from within your IDE (no server needed).

Disclaimer

Sometimes the classical CSRF-tokens are wrongfully deemed a solution against replay or brute-force attacks. The stateless approaches listed here does not cover this type of attack. Personally I feel both type of attacks should be covered at another level, such as using https and rate-limiting. Which I both consider a must for any data-entry on a public web site
!

 

about author

PHRASE

Level 60  머나먼나라

사랑은 정신과 정신, 육체와 육체가 창조의 기쁨 속에서 황급히 끌어당기는 인력이다.

댓글 ( 0)

댓글 남기기

작성