Redis セッションとセッション固定への防御
한국어 원문으로 표시합니다.
목표
Redis 를 세션 저장소로 붙인 로그인 서버를 세우고, 로그인 순간 세션 ID 가 재발급되는지·절대 만료가 idle 과 무관하게 동작하는지·로그아웃이 즉시 무효화하는지를 실제 HTTP 요청으로 확인한다.
왜 중요한가
서버 세션은 상태를 서버가 쥐고 브라우저에는 열쇠만 준다. 그래서 로그아웃 한 번으로 즉시 무효화할 수 있지만, 그 열쇠가 로그인 전후로 바뀌지 않으면 남이 미리 심어 둔 열쇠로 내 로그인에 올라타는 세션 고정이 성립한다. OWASP 는 권한 수준이 익명에서 인증으로 바뀌는 순간 세션 ID 를 재발급하라고 못박는다. 또한 만료는 idle(활동 없으면 만료, 요청마다 갱신)과 absolute(만든 지 오래되면 무조건 만료) 두 가지를 함께 두어야 하는데, Redis TTL 만으로는 absolute 를 표현할 수 없어 값 안에 만든 시각을 따로 둔다. 이 실습은 그 규칙들이 실제 요청에서 지켜지는지를 스스로 증명하게 한다.
단계
/root/st/session/app.py를 127.0.0.1:8302 에 띄운다.GET /health가 200 과{"ok":true}를 준다.- 세션 쿠키가
__Host-sid이름에Secure,HttpOnly,SameSite,Path=/를 갖는지/root/st/session/cookie.txt에 Set-Cookie 헤더 한 줄로 저장한다. - 익명 세션을 받아 그 ID 가
sess:<id>키로 Redis 에 있고 TTL 과 user 를 가지는지/root/st/session/store.txt에 적는다. - 익명 세션 ID 를 그대로 들고 로그인하면 응답이 다른 세션 ID 를 주고 옛 ID 가 사라지는지
/root/st/session/fixation.txt에 적는다. - 로그인 세션의 TTL 이 idle 상한 이하이고 요청으로 다시 차오르는지
/root/st/session/idle_ttl.txt에 적는다. created를 과거로 넣은 세션이 idle TTL 이 남아 있어도 인증으로 인정되지 않는지/root/st/session/absolute.txt에 적는다.- 로그아웃 뒤 같은 쿠키가 익명으로 떨어지고 저장소 키가 사라지는지
/root/st/session/logout.txt에 적는다. /root/st/session/e2e.sh로 전체 흐름을 시험해/root/st/session/e2e.out에 네 줄을 남긴다.
참고
- 세션 ID 는
secrets.token_urlsafe(32)로 만듭니다(256비트, OWASP 최소 64비트 초과). - idle 은 요청마다
EXPIRE로 TTL 을 밀어 sliding 으로, absolute 는 값 안의created로 판정합니다. - 흔한 실수 1: 로그인 시 세션 ID 를 재발급하지 않는 것 — 세션 고정이 그대로 성립합니다.
- 흔한 실수 2: absolute 를 TTL 로만 표현하는 것 — 요청마다 밀려 영원히 안 죽습니다.
Redis 세션 서버 띄우기
/root/st/session/app.py 를 127.0.0.1:8302 에 띄운다. GET /health 가 200 과 {"ok":true} 를 준다.
Redis 는 이미 6379 에 떠 있습니다. 서버는 백그라운드로 띄우고 준비될 때까지 폴링하세요.
세션 쿠키 속성 점검
세션 쿠키가 __Host-sid 이름에 Secure, HttpOnly, SameSite, Path=/ 를 갖는지 /root/st/session/cookie.txt 에 Set-Cookie 헤더 한 줄로 저장한다.
__Host- 접두는 Secure 와 Path=/ 를 요구하고 Domain 을 붙이면 안 됩니다. curl -D - 로 응답 헤더를 봅니다.
세션이 Redis 에 저장되는지
익명 세션을 받아 그 ID 가 sess:<id> 키로 Redis 에 있고 TTL 과 user 를 가지는지 /root/st/session/store.txt 에 sid= redis_key= exists=1 ttl=<양수> user=(anon) 로 적는다.
쿠키 병(-c)으로 세션 ID 를 잡고 redis-cli EXISTS/TTL/GET 으로 확인합니다.
로그인 시 세션 ID 재발급
익명 세션 ID 를 그대로 들고 로그인하면 응답이 다른 세션 ID 를 주고 옛 ID 가 저장소에서 사라지는지 /root/st/session/fixation.txt 에 before= after= changed=yes old_gone=yes 로 적는다.
세션 고정 방어의 핵심입니다. 로그인 성공 시 옛 세션을 delete 하고 새 ID 를 발급해야 changed=yes 가 됩니다.
idle 타임아웃은 sliding TTL
로그인 세션의 Redis TTL 이 idle 상한 이하이고, 요청을 한 번 더 보내면 TTL 이 다시 차오르는지 /root/st/session/idle_ttl.txt 에 ttl_before= ttl_after_request= 로 적는다. 둘 다 양수이고 after 가 상한 근처여야 한다.
요청마다 EXPIRE 로 TTL 을 다시 밀면 sliding idle 이 됩니다.
absolute 만료는 TTL 과 무관
created 를 absolute 상한보다 과거로 넣고 idle TTL 은 넉넉히 남긴 세션이 GET /whoami 에서 인증으로 인정되지 않는지 /root/st/session/absolute.txt 에 code=200 auth=False gone=yes 로 적는다.
load_session 에서 now - created > ABSOLUTE_SEC 이면 idle TTL 이 남아 있어도 거절해야 합니다.
로그아웃 즉시 무효화
로그인 뒤 로그아웃하고 같은 쿠키로 GET /whoami 하면 익명으로 떨어지고 저장소 키가 사라지는지 /root/st/session/logout.txt 에 revoked_auth=False key_gone=yes 로 적는다.
서버가 상태를 쥐므로 키 하나를 지우면 즉시 무효화됩니다 — JWT 와의 차이입니다.
전체 흐름 한 번에 증명
/root/st/session/e2e.sh 로 익명→로그인 재발급→인증→로그아웃 무효화를 순서대로 시험해 /root/st/session/e2e.out 에 reissue=yes, old_gone=yes, logged_in=True, after_logout=False 를 남긴다.
앞 스텝들을 한 스크립트로 이어 붙입니다. 네 줄이 모두 나와야 합니다.