The labs on this site need JavaScript to run. The same content is written out below.
Learn › Security › Authentication
Session hijacking and session fixation
Whoever knows the session ID is that user
The server decides "whose request this is" only from the session ID that arrives in the request's cookie. So anyone who knows the session ID of a logged-in session can act as that user without knowing the password (session hijacking). Session fixation reverses the order. If the session ID used before login stays the same after login, a person who already knew that ID gets to use the logged-in session.
Checked against real execution · A Spring Boot 4.1.1 and Spring Security 7.1.1 server was started on 127.0.0.1, and the session ID before and after login and the responses were checked for all 12 combinations
Concepts in this lab
- Session
- A way to store a user's state on the server side and give the client only an identifier (the session ID) that points to that stored state, delivered in a cookie. The actual information is on the server, so the client cannot change its contents (having the session ID stolen is a different problem). When there are several servers, a common setup keeps this state in a shared store such as Redis so that any server that receives a request can find the same state.
- Session hijacking
- An attack in which someone learns the session ID of another user's logged-in session and puts that ID in the cookie of their own requests to act as that user. The server decides whose request it is only from the session ID that arrives in the cookie, so it cannot tell the person who knows the ID from the original user. A session ID leaks when a script reads the cookie, or when someone eavesdrops on communication that is not encrypted. That is why you add HttpOnly and Secure to the session cookie, delete the session on the server at logout, and keep the session timeout from being long.
- Session fixation
- An attack in which the attacker makes the victim's browser use a session ID the attacker already knows, and then waits for the victim to log in with that session. If the session ID is the same before and after login, the ID the attacker knows becomes the ID of the logged-in session at the moment the victim logs in. The defense is for the server to change the session ID to a new value when login succeeds. Spring Security changes the session ID at login even if you configure nothing, and choosing none in the sessionFixation setting turns this protection off.
- HttpOnly, Secure, and SameSite
- Attributes that protect cookies. HttpOnly stops JavaScript from reading the cookie, which prevents the cookie from being stolen by a script injection attack. Secure makes the browser send the cookie only over HTTPS connections. SameSite restricts sending the cookie with requests that start from another site, which reduces CSRF attacks.
- Cookie
- A small piece of data that a server sends in a response. The browser stores it and automatically sends it back with every request to the same site. HTTP does not remember state between requests, so this data is used to keep information that must carry over, such as a login state. Because it is stored in the user's browser, you must use it on the assumption that the user can read or change its contents.
Try these
- Default settings — The session ID stays the same after login. A request from a person who knew that ID in advance is handled as kim.
- With the Spring Security default — The ID changes at the moment of login, so the ID obtained in advance is useless.
- Creating a new session — The ID changes just the same, but the cart filled before login is lost.
- If the ID after login leaks — Changing the ID at login does not stop this.
- If the victim logs out — The server deletes the session, so the leaked ID no longer works.
- Protection off, but the victim logs out — The request is blocked after logout, but the session was open for as long as the victim stayed logged in.
Running with the default settings
- Another user (the attacker) opens the login page of shop.example. The server creates a session for the browser that has just arrived and sends down session ID aaa111 in the Set-Cookie response header. No one has logged in to this session yet. The attacker writes this ID down.
- The attacker gets this session ID into the victim's browser as a cookie. This lab does not cover how, and only assumes the result: "the victim's browser sends aaa111 as a cookie". Nothing has been gained yet, because this is not a logged-in session.
- POST /cart: before logging in, the victim puts a keyboard in the cart. The browser sends the cookie JSESSIONID=aaa111 with the request, and the server writes the cart into that session.
- POST /login: the victim sends a user name and password. The cookie is still JSESSIONID=aaa111. The server checked the user name and password, and authentication succeeded.
- Right after login succeeds: nothing is done to the session. The logged-in user kim is recorded in the original session aaa111 as it is, and the response has no new Set-Cookie. The ID used before login has become the ID of the logged-in session.
- GET /cart: the victim's browser now sends the cookie JSESSIONID=aaa111. The keyboard is still in the cart.
- GET /me: from their own computer, the attacker puts the ID they know in the cookie (JSESSIONID=aaa111) and sends a request. The server finds the session by that ID, handles the request as the request of kim recorded there, and answers 200. The only thing the server looks at is the session ID, so it does not tell which computer sent the request. The attacker never entered the password.
Result of this run
- Session fixation: if the session ID is the same before and after login, a person who already knew that ID gets to use the logged-in session without knowing the password. You can prevent this by changing the session ID at the moment login succeeds, and Spring Security does that even if you configure nothing.
Code
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(a -> a
.requestMatchers("/cart").permitAll() // the cart can be used before login
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults()) // log in with POST /login
.sessionManagement(s -> s
.sessionFixation(f -> f.none())) // leave the session as it is on login (protection off)
.build();
}
@GetMapping("/me")
String me(Principal p) {
return p.getName(); // whose request this one was handled as
}
@PostMapping("/cart")
String add(@RequestParam String item, HttpSession session) {
session.setAttribute("cart", item); // write the cart into the session
return "ok";
}
// Response header when the server sends down a session ID (Spring Boot default settings)
// Set-Cookie: JSESSIONID=aaa111; Path=/; HttpOnly
Assumptions of this model
All 12 combinations were checked against real execution on the PC used to write this lab. A Spring Boot 4.1.1 and Spring Security 7.1.1 server was started on 127.0.0.1 and run with the same settings as on the screen. The checks were: whether the JSESSIONID that the server sends down changes before and after form login, whether the cart filled before login survives, and whether the response to a request that carries an ID another person knows in its cookie (200 with the body kim, or 302 with /login) matches the screen. Here the "attacker" and the "victim" are requests from the same test program that differ only in the session ID put in the cookie, and no real browser was used. How an attacker plants a session ID in the victim's browser, and how the ID of a logged-in session is learned, were not run; only the result that this happened is assumed. That a script cannot read an HttpOnly cookie was checked with a real browser in the "Cookies" lab. The real session ID was 32 characters long (checked on this server) and is generated randomly so that it cannot be guessed. The aaa111 and bbb222 on the screen are shortened to make them easy to read. That the Set-Cookie attributes of the session ID the server sends down are "Path=/; HttpOnly", and that Secure is added when you set server.servlet.session.cookie.secure=true, were also checked on this server. Even when the behavior at login was specified explicitly as changeSessionId or migrateSession, the result seen from outside was the same as the default. A session is created when the login page is first opened because the CSRF token placed on that page is kept in the session. That a session disappears when the session timeout passes was not checked by execution. The reference is the explanation of session fixation protection in the Session Management chapter of the Spring Security documentation (Servlet Applications, Authentication). Sharing sessions when there are several servers is covered in the "Sessions and JWT" lab.
Previous lab: OAuth login flow · Next lab: Security filter chain