TT Lab
Get started
Learn Learning paths Courses

Sessions and Tokens — From the Browser to the Mesh

Browsers silently drop cookies that break the rules

Continue in TT Lab

In one line

A cookie is a small value that the server entrusts to the browser, and six attributes decide where it goes, how long it lasts, and to whom it is visible. A session cookie is itself the login, so the rules collapse if even one attribute is missing, and the browser discards a cookie that breaks the rules without any error. So for cookies you have to check not "it was sent" but "it was accepted".

Why this was needed

HTTP has no memory between requests. To recognize a logged-in person on the next request, you have to make them send something again every time, and that something is a cookie. The reason it is convenient is that the browser attaches cookies automatically, and that is also exactly the reason it is dangerous. If the server does not say clearly which requests to attach a cookie to, whether to show it to scripts, and whether to send it over plain HTTP too, the browser behaves in the loosest way.

The original specification, RFC 6265, does not hide this looseness. Cookies are not isolated by port, so a service running on another port of the same host reads the same cookie. If foo.example.com plants a cookie with Domain=example.com, it can overwrite the cookie of bar.example.com, and the receiving server cannot tell who planted it. It also states that splitting by path with Path cannot be trusted as a security boundary. Attributes and name prefixes are devices added later to narrow these holes one by one.

How it works

Attributes are attached after the Set-Cookie line, separated by semicolons. Going by MDN Set-Cookie, it is organized like this.

Secure           HTTPS 요청에만 붙인다
HttpOnly         document.cookie 로 읽을 수 없다. 요청에는 그대로 붙는다
SameSite         다른 사이트에서 시작된 요청에 붙일지 (Strict / Lax / None)
Path             이 경로 아래 요청에만 붙인다. 보안 경계는 아니다
Domain           생략하면 보낸 호스트에만(호스트 전용), 적으면 그 도메인과 하위 전부
Max-Age/Expires  없으면 세션 쿠키, 있으면 영속 쿠키. 둘 다 있으면 Max-Age 가 이긴다

The opposite of intuition is Domain. You might expect that writing Domain=app.example.com makes it narrower, but in fact the cookie also goes to subhosts such as x.app.example.com. The narrowest scope is to omit it. The OWASP session management cheat sheet also recommends leaving Domain out entirely. The same document says not to make a session cookie persistent — it is better to let it disappear when the browser closes. Persistent cookies also have a cap. The RFC 6265bis draft says not to set the expiry further out than 400 days.

What HttpOnly blocks is exactly one thing: a script reading the value. MDN states clearly that an HttpOnly cookie is still attached to fetch() requests. If the page has XSS, the attacking script cannot steal the cookie, but it can still send requests like the user from inside that browser. HttpOnly is not an XSS defense; it is a device that reduces the damage.

Name prefixes fill in a weakness of the cookie store. The store distinguishes cookies only by name, domain, and path and does not record who planted them. A prefix engraves the conditions into the name itself, so that the server can trust that if a cookie with that name exists, it passed the conditions and was stored. This is based on the storage model of the RFC 6265bis draft (revision 22).

__Secure-   Secure 가 있어야 한다
__Host-     Secure 가 있고, Domain 이 없고(호스트 전용), Path 속성이 / 여야 한다
            브라우저는 접두를 대소문자 구분 없이 대조한다 (__host- 도 같은 규칙)

The same draft says to ignore a Secure cookie sent in a response that is not over a secure connection. MDN notes localhost as an exception, and in this lab Pod, curl 8.5 also stored and sent back a Secure cookie received over http://127.0.0.1 (measured). That is why this lab also runs over plain HTTP.

By OWASP's standard, a session ID must have at least 64 bits of entropy produced by a cryptographic random number generator. In Python, secrets.token_urlsafe(32) gives 256 bits. Logout has to do both. It invalidates the server-side session and deletes the cookie with Max-Age=0. Here the deletion cookie must also pass the prefix rules. When we checked again with curl 8.5, __Host-sid=; Max-Age=0 without Secure was ignored and the old cookie remained in the store as it was.

What it looks like in the field

It is common for an app's logout button to delete only the cookie. On screen it looks logged out, but a request made with a value left in proxy logs or in a HAR file someone shared is still logged in. The server did not erase its memory, so that value is still a key.

Another case is "login fails only on staging". It is when staging is plain HTTP but the cookie has Secure on it, or when someone appended Domain to a __Host- cookie. The server log shows Set-Cookie going out normally and the browser silently discards it, so you have to look not at the response but at the stored cookie list in the network tab to see the cause.

What you will do in the next lab

You stand up a login server at 127.0.0.1:8301 and issue a __Host-sid session cookie. You check the attributes in the response header and see whether it is left in the curl cookie jar with the #HttpOnly_ mark. For logout, you use the common mistake of not deleting the server session as a counterexample and reproduce yourself that an old copied cookie turns into a 401. Finally you judge several Set-Cookie lines by the prefix rules.