Chapter 4
Sessions That Hold
A Session Id Is a Key
After a login, the password is not sent again. The browser sends a secrets.token_urlsafe(16), which is 128 random bits, so nobody guesses one. This chapter is about every other way to end up holding a session id that works.
What is new in this chapter’s project
The project is your server from the end of Chapter 3 with one feature added. Here is what the new code does and which mistake it invites.
POST /prefs remembers a page size for a reader who has not logged in. To remember anything the server needs a session, so this route hands one out before login: a visitor session, whose user is None.
elif segments == ["prefs"]:
if method == "POST":
response = prefs_error(request)
if response is None:
page_size = json_body(request)["page_size"]
response = json_response(200, {"page_size": page_size})
session = current_session(request, state)
if session is None:
session_id = start_session(state, None, page_size=page_size)
response["headers"]["set-cookie"] = session_cookie(session_id)
else:
session["page_size"] = page_size
A session is now {"user": ..., "page_size": ...}, and GET /me reports both: {"user": null, "page_size": 5} for a visitor who chose 5. Two helpers came with the feature: session_id_from(request) reads the id from the cookie, and current_session(request, state) returns the session that id names. sample_state() also has a "clock" entry for Lesson 3, and three
The login branch had to change as well, because a visitor who logs in already has a session. After a correct password it now runs these four lines:
session_id = session_id_from(request)
if session_id is None or session_id not in state["sessions"]:
session_id = start_session(state, None)
state["sessions"][session_id]["user"] = user
When the request brings a cookie that names a session, the login keeps that session and writes the user into it. So the page size survives the login.
Read the four lines again and ask who chose the session id. The server did not choose it at this moment. The id arrived in the request, and the server only checked that it knows the id. An id that was handed to a visitor is then trusted with a logged-in user.
Which of the four supplied lines in the login branch gives a logged-in user to an id the server did not choose?
The attack, step by step
Three parties take part. The id is shortened to K7f...Qw.
attacker victim's browser server
| | |
1 |-- POST /prefs --------------------------->| new session K7f...Qw:
|<-- set-cookie: session=K7f...Qw ----------| user None, page_size 5
2 |-- plants K7f...Qw --->| |
3 | |-- POST /login --->|
| | cookie K7f...Qw |
| | ana's password |
4 | | | its user is now "ana"
5 |-- GET /me, cookie: session=K7f...Qw ----->|
|<-- {"user": "ana", "page_size": 5} -------|
The attacker sends
POST /prefsand receives a session id.The attacker gets that id into the victim’s browser. The next section says how.
The victim logs in with her own password. Her browser sends the planted cookie along.
The server finds the session the cookie names and writes
anainto it.The attacker sends
GET /mewith the id they have had since step 1, and the server answers as ana.
The same id travels in steps 1, 3, and 5. The attacker never guessed an id and never stole one. The server promoted a
This block acts the attack out on a plain
It prints the victim's cookie now holds: id-the-attacker-knows and the attacker asks who that is: ana.
What does this print?
sessions = {"abc": {"user": None}}
sessions["abc"]["user"] = "ana"
print(len(sessions), sessions["abc"]["user"])
How does the id get into her browser?
Step 2 has two common routes.
A neighboring host. Say the reading
lives at reading.exampleand a notes app atnotes.reading.example. A page on the notes host can set a cookie for the whole domainreading.example, and a browser then sends that cookie to the reading list as well. Lesson 4 closes this route in the cookie itself.A shared computer. The attacker visits the reading list first, notes the id, and leaves the cookie behind. The next person at that computer logs in with it.
Nothing on this site is a browser, so the course describes step 2 and does not show it: a real browser is what would carry the planted cookie. Every other step runs against your server in the next lesson.
A server that also accepted the session id from the
Four ways to lose a session id
| How the id is lost | What it means | Where it is handled |
|---|---|---|
| Guessed | The attacker tries ids until one works | Closed in HTTP with Python by an id from secrets |
| Fixed | The attacker knows the id before it is worth anything | This attack; Lesson 2 |
| Read in transit or from a page | A copy is made while the cookie crosses a network without HTTPS, or by a script in a page | Lesson 4 |
| Never retired | A copy keeps working because the server never ends the session | Finding F3; Lesson 3 |
A session id still works a year after its user last used it. Which of the four losses is that?
The repair, in one sentence
When what a session is trusted with changes, give it a new id. A login is such a change, and the old id, which someone else may know, must stop naming anything.
It prints the victim's new id is 22 characters the attacker never saw and the attacker asks who the old id is: None. The page size moved to the new session, and the old id names nothing.
The attacker never saw ana's password or the cookie her login returned. What did they know?
Nothing in your server has changed in this lesson. The next lesson runs the attack against it and closes it there.