CSRF token expires during login - spring

I'm working on Spring web application and I need to avoid problem with expire csrf token on login page, because if user is waiting too long and try to login only one way to resolve problem with csrf is to reload page and try to login again. But it's not user friendly and I want to avoid this situation.
First question: Is it possible in general(by spring security 3.2.4)? Without disable csrf.
I tried to use security="none" for login page and spring seciruty "login_check", but it's not working, i got infinity redirect or I got error that no mapping for url "myhost/login_check".
Second question: How can i do it?

Recommended solution
I would say that you should not disable csrf tokens on a production site. You may make session (and thus the csrf token) last longer (but it usually should not last longer than a day, especially for not-logged-in users as it is a DOS vector), but the real solution may be to automatically refresh the login page when the csrf token expires. You may use a
<META HTTP-EQUIV="REFRESH" CONTENT="csrf_timeout_in_seconds">
in your login page header. If the user lets the login page sit for hours, it should not bother him that the page got refreshed.
Second solution
A possible solution which does not require you to actually store sessions but allows for infinite timeout is that you can generate your csrf tokens with hashing from the session id and a server-side secret:
csrf = hash(sessionid+secret)
Note however that you need to really dig and override spring-security internal mechanisms, namely:
re-creating anonymous sessions on the fly if a request arrives and no such session exists
re-creating the csrf token on the fly from the session id
And choose a very secure hashing algorithm, preferably sha-512.
Third solution
You could have a small javascript that calls a no-op page on your server regularly (just before the session timeout), thus extending your session. This results in infinite session timeout only if the browser is on all the time, so the DOS aspect is mitigated.
Ok, one last solution
You can alter the CSRF token checking code, and disable it for the login page. This is actually synonymous with the second solution, but is specific for the login page, not generally for all anonymous sessions.
You can do this e.g. by setting a custom RequestMatcher in HttpSecurity:
http.csrf().requireCsrfProtectionMatcher(new MyCsrfRequestMatcher());
...
class MyCsrfRequestMatcher implements RequestMatcher {
#Override
public boolean matches(HttpServletRequest request) {
return !request.getServletPath().equals("/login");
}
}

Another option would be set no timeout for the session by default and then, when the user is authenticated, change the timeout to whatever you want. You can see an example of how to do this here.

In one of the projects I worked on, I implemented the following:
Implement an exception handler which handles CsrfException (or AccessDeniedException in general in my case). Forward the request to a controller method.
#ExceptionHandler(AccessDeniedException.class)
#ResponseStatus(value = HttpStatus.FORBIDDEN)
public void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
request.getRequestDispatcher("/Access_Denied").forward(request, response);
}
In the controller method, check whether the original request is for the login page. If so, show an appropriate message within the login page.
if ("/login".equals(request.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH))) {
model.addAttribute("error", "An invalid security token has been detected. Please try again.");
return "login.jsp";
} else {
return "accessDenied.jsp";
}
With this approach, user will be able to retry the login without the need to refresh.

You can also make your CSRF protection rely on cookies and NOT server side session state. Spring Security has full support for this.
CookieCsrfTokenRepository
You will only receive a timeout if your cookie expires. This scales well since it's basically stateless (from the server's perspective).
#EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
Andrew

Related

Why does setting sessioncreation policy to stateless break my oauth2 app

#Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
Why does setting SessionCreationPolicy to STATELESS break oauth2 login?
After authenticating with facebook, the app goes on a never ending loop that eventually leads to "localhost redirected you too many times".
The loop goes like this:
Authenticate with facebook and redirect to:
Redirect-Uri - //login/oauth2/code/facebook?code=&state=
Back to facebook authorization-Uri - /oauth2/authorization/facebook
Repeat
This all happens with the SessionCreationPolicy being STATELESS. Can someone explain to my why this happens?
That's expected behavior. OAuth2 Clients need to store the tokens somehow for using them in later requests. By using a stateless session creation policy, every time you call the application, it won't find any token (i.e. it doesn't know you've already authenticated yourself in the previous request), so it will trigger again the authentication flow.
On the other hand, OAuth2 Resource Servers can be stateless, since they don't rely on any session state. Every request sent to an OAuth2 Resource Server from an OAuth2 Client provides an Access Token in the HTTP request header (which is possible because the client stores the tokens in the session).

How to use key instead of `Set-Cookie` in Response Headers with Spring Security?

I am having difficulty to store cookies in a React Native apps.
My goal is to send the JSESSIONID and XSRF-TOKEN as Response Header's keys instead of Set-Cookie, and the client will handle to store it as cookie manually.
I will store the JSESSIONID as cookie with HttpOnly set to true.
I will store the XSRF-TOKEN as cookie with HttpOnly set to false.
Basic Security Concepts:
1. Session Cookie (JSESSIONID for example).
Should always be HttpOnly, have a domain set (or use the default as the server that provided it).
Never be stored anywhere other than the browser handling it. Your JS/HTML should know nothing about the JSESSION Cookie and only just move the user to a login screen if they get a 401 (UnAuthorised) from an endpoint.
2. CSRF Tokens.
Back in the day (5 years a go probably ha!), most sites were rendered on the server. The server created the HTML and then just sent it back via the URI. Like you went to /profile then the server knew who you were and then created the profile page on its server and just fed back the HTML document.
When wanting to get some user input, this HTML rendered by the server would contain a <form/> which would collect user's data (password/bank details etc.) and then with the onSubmit passes it back to the server in a application/x-www-form-urlencoded format
eg.
https://thewebsite.com/sendmoney_to?account=512&amount=1milliondollars
By simply sending that link to someone who has an active session with the site thewebsite.com the browser would visit it and carry out the request.
The victim is logged in as far as the site is concerned and will happily run that request sent by the attacker. These links were at one point even follower by simply loading an <img> like by posting on their wall or in an email etc.
So how did they fix this?
By adding some fields to the form called hidden fields. These hidden fields are created by the server when the page is rendered. They contain a value that is the CSRF TOKEN and also something in a CSRF COOKIE. So when the application/x-www-form-urlencoded form is sent, it must have these values produced on the server when rendering the form. The server can then verify the form was the one they created and not some malicious link an attacker created.
An attacker can not know/guess these when making their naughty link.
3. Nower Days
With having only JSON requests as many sites, like React Apps, are rendered client side and use Axios/Fetch...CSRF is somewhat redundant. You don't post forms/application/x-www-form-urlencoded...only make POST requests with a JSON body.
Sessions are still important as an XSS attack > CSRF attack. Store the session properly (JWT token or not...don't jump on the JWT band wagon and start throwing that JWT Auth Token around Local Storage which seems to have become some kinda strange default for newer devs).
If you are sure you only have application/json capable endpoints, then the only way an attacker is going to get you to POST their content instead of what you are meant to is via an XSS attack. But once they have an XSS attack it is game over anyway. They are simply then you as far as the server is concerned as they use their naughty injected <script> to manipulate the request before it sent and the server would have no way of knowing it had been manipulated on the fly (usually).
4. Getting your CSRF Token via a Header as it wont be made in the form
The XSRF-TOKEN you may need to expose the XSRF token by using a filter that will extract the CSRF Token (via the session) and adds it to a header on the response entity.
I wrote my own but it is basically the same in a library some use to do this same thing.
#Log4j2
public class CsrfBindingFilter extends OncePerRequestFilter {
protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER";
protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM";
protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrfToken = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);
log.debug("CRSF Token from session : {}", csrfToken != null ? csrfToken.getToken() : "CsrfToken is NULL");
if (csrfToken != null) {
response.setHeader(RESPONSE_HEADER_NAME, csrfToken.getHeaderName());
response.setHeader(RESPONSE_PARAM_NAME, csrfToken.getParameterName());
response.setHeader(RESPONSE_TOKEN_NAME, csrfToken.getToken());
}
filterChain.doFilter(request, response);
}
}
As far as the session, the browser should handle that and you should not be messing with it client side unless it is for a very specific reason (and I can't fathom one). The Cookies for Sessions are set as HttpOnly for the specific reason to disallow any JS running in the client to edit/read/add it.
One little cheeky advert/xss with some naughty code like get the cookie called JSESSION ID if the host is myvictim.com and post it over here... could mean you are compromised.
Read this for more detail:
Add HttpOnly Cookie via JS
The browser should handle the Set-Cookie headers as intended and is best practice (out of security reasons and also just for plain simple ease of use).
WebSocket STOMP Authentication
Once a user has been authenticated via your /login, Spring should send a Set-Cookie header with the JSESSIONID Cookie. This cookie will be stored by the browser and should be inaccessible to your front end javascript app.
If you then use the STOMP Spring WebSocket implementation, you can get the principal user name from a STOMP message by getting it via the StompHeaderAccessor in the #MessageMapping Controller params.
stompHeaderAccessor.getUser().getName() would give the Spring Security authenticated user's principal name (usually their username or id, username is default).
#MessageMapping("/agents/start")
public void start(StompHeaderAccessor stompHeaderAccessor) {
log.info("Subscriber Start! {}-{}", stompHeaderAccessor.getUser() != null ? stompHeaderAccessor.getUser().getName() : "ANON", stompHeaderAccessor.getSessionId());
mysessionstore.addSessionId(stompHeaderAccessor.getSessionId());
}
If you then want to edit the user's session attributes, you will then need to fetch their session id from the SPRING_SESSION table and you can use the Spring SessionRepository to fetch it.
https://docs.spring.io/spring-session/docs/current/api/org/springframework/session/SessionRepository.html

Spring Security loginPage Vs loginProcessingURL

what is the difference between loginPage vs loginProcessingURL.
.formLogin().loginPage("/login").usernameParameter("phone-number").passwordParameter("password")
Seems to be loginProcessingURL is like post method once user submits the data in the login page but when I remove also it is working fine. What is the significance of loginProcessingURL and how does it differ from loginPage?
The line loginPage("/login") instructs Spring Security
when authentication is required, redirect the browser to /login
we are in charge of rendering the login page when /login is requested
when authentication attempt fails, redirect the browser to
/login?error (since we have not specified otherwise)
we are in charge of rendering a failure page when /login?error is
requested
when we successfully logout, redirect the browser to /login?logout
(since we have not specified otherwise)
we are in charge of rendering a logout confirmation page when
/login?logout is requested
AND
.loginProcessingUrl("/login/process")
tells Spring Security to process the submitted credentials when sent the specified path and, by default, redirect user back to the page user came from. It will not pass the request to Spring MVC and your controller.
Refer documentation
Purpose of loginPage()
The loginPage() tells the framework where the user will be redirected when login is required. For example when you are not authorized to the page, you get redirected to this page. This page performs the login activity, for example when you implement a loginForm() or oauth2Login() like in my code using Google OAuth2,this page redirects to google login.
http.anonymous().and()
.authorizeRequests().antMatchers("/images**").permitAll().and()
.authorizeRequests().anyRequest().authenticated().and()
.oauth2Login()
.successHandler((request, response, authentication) -> {
request.authenticate(response);
})
.loginPage("/oauth2/authorization/google")
.loginProcessingUrl("/login")
Purpose of loginProcessingUrl()
The loginProcessingUrl() is the method that automatically set the rule antMatchers("/thisUrl").permitAll() to this URL so that when the response is returned (code, state, token, etc.) will be allowed to be GETed and this response is processed as you can see in the authenticate method of the request. Something more important is that this loginProcessingUrl() tells that the response should be processed to this URL. Without this the request.authenticate(response) will not be executed and authentication will not be returned or otherwise you implement another algorithm.
May the following code segment from spring security source code will help you:
loginPage the login page to redirect to if authentication is required
loginProcessingUrl the URL to validate username and password
DEFAULT_LOGIN_PAGE_URL = "/login"
/**
* Updates the default values for authentication.
*
* #throws Exception
*/
protected final void updateAuthenticationDefaults() {
if (loginProcessingUrl == null) {
loginProcessingUrl(loginPage);
}
//...
}

How to prevent HTTP session flood attack

Flood Attack:
In short, a hacker can keep hitting the server (without cookie) to force Java container to keep creating new session.
I am using Spring Security to manage session. I realize jsessionid keep being created before login, this is not what I want.
So I did:
1) in Spring security config:
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
2) disable session creation in jsp. Because I am using apache tile, due to it is using dynamic include, so I have to disable session creation in all the jsp fragment. This is very tedious.
<%#page session="false"%>
First glance, it is fine, but there is a scenario I still got the session created.
Let's say before login, I visit a url that only can be visited after authenticated, Spring will redirect me to login page.
Before I am redirected, the response already instruct to set a new cookie, a session already created.
My Question:
1) Is session flood attack a serious issue? Should I really take care of it?
2) Is there any better way to handle this issue? Any best practise?
3) What happen to my code? It should work actually, I suspect the cookie is created by Spring, although I already set it to SessionCreationPolicy.NEVER. I can't set it to Stateless, I still need the session after login.
I am more concerned session attack compare to DDOS actually, I have also set .maximumSessions(1) in Spring to prevent multiple login. But above issue happen before login. Please help. Thanks.
Your point looks valid, it probably can be a serious issue if not handled. I found there is already an open issue on this topic. But there is a work around available to control this behavior.
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
public HttpSessionRequestCache getHttpSessionRequestCache() {
HttpSessionRequestCache httpSessionRequestCache = new HttpSessionRequestCache();
httpSessionRequestCache.setCreateSessionAllowed(false);
return httpSessionRequestCache;
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.requestCache().requestCache(getHttpSessionRequestCache());
}
Refer following links for more details.
https://github.com/spring-projects/spring-security/issues/4242
How to stop Spring Security from creating a new session?

Getting 403 with CSRF token in spring security

I am encountering an issue with multiple tabs. If i logout from first tab and open another tab and after logging in and logging out if i go back to first tab and login i get 403. For example, the logout page of first tab had following added to the form by spring security and thymeleaf:
<input type="hidden" name="_csrf" value="7b9639ba-aaae-4ed2-aad2-bb4c5930458e">
where as the login form of second tab added a different csrf token.
<input type="hidden" name="_csrf" value="659324d5-ec5c-4c57-9984-dab740746285">
Now when i go to first tab and login from there i get 403 forbidden. Which makes sense since csrf token is now stale. But how do i get around this? I am also getting 403 forbidden if the user was logged out from inactivity and redirected to login page but tried logging in again only after a while, say half an hour.
As of Spring Security 3.2, we have the CsrfTokenRepository interface, which allows you to store the synchronizer token however you see fit, such as in a database. This gives you the option to expire those tokens however you want in order to avoid stale tokens in your use case.
If you want to provide a nicer error message when something does go awry, you can supply a custom AccessDeniedHandler implementation that manages the MissingCsrfTokenException and InvalidCsrfTokenException exceptions in order to produce a more informative message.
UPDATE:
I have an interceptor that handles all my uncaught exceptions, so I just built a little AccessDeniedHandler that rethrows the CSRF-related exceptions:
public class CustomAccessDeniedHandler extends AccessDeniedHandlerImpl {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
if(accessDeniedException instanceof MissingCsrfTokenException
|| accessDeniedException instanceof InvalidCsrfTokenException) {
throw new ServletException(accessDeniedException);
}
super.handle(request, response, accessDeniedException);
}
}
The simplest approach to handling access denied errors I've used has been setting the access denied handler within your security config to redirect to your login page.
<http ...>
<access-denied-handler error-page="/login.html" />
...

Resources