Cookie 与 Session
Cookie 与 Session
HTTP 本身是无状态的。服务器不会天然记得"刚才这个请求是谁发的"。Cookie 和 Session 就是用来维持登录状态和用户身份的机制。
Cookie 是什么
Cookie 是浏览器保存的一小段数据,每次请求同一网站时会自动带上。
例子:
Cookie: sessionid=abc123; role=user服务器可以根据 Cookie 判断用户是谁、是否登录、权限是什么。
Session 是什么
Session 通常保存在服务器端。浏览器只保存一个 session id:
sessionid=abc123服务器拿到 abc123 后,在后端查到对应用户数据。
所以 Cookie 不一定直接保存用户名和权限,它也可能只是服务器端状态的索引。
CTF 中常见考法
第一类是明文权限字段:
role=user改成:
role=admin如果服务器直接信任客户端 Cookie,就可能越权。
第二类是弱签名或伪造 token。比如 Cookie 看起来像 Base64、JWT 或序列化字符串。此时要先解码观察结构,再判断能不能改。
第三类是 Session 固定或预测。如果 session id 太短、递增或可猜,就可能伪造别人的身份。
JWT 和普通 Cookie 的区别
JWT 常见形态是三段 Base64URL:
xxxxx.yyyyy.zzzzz第一段是头部,第二段是载荷,第三段是签名。
JWT 的载荷可以被解码看到,但不能随便改。改了载荷后,如果没有正确签名,服务器应该拒绝。
CTF 中常见漏洞包括:
alg=none。- 弱密钥可爆破。
- 服务端没有校验签名。
- 把敏感信息直接放进 JWT 载荷。
观察方法
在 Burp 或浏览器开发者工具里看请求 Header。
记录:
Cookie 名称:
Cookie 值:
是否像 Base64/JWT:
修改后响应变化:
退出登录后是否失效:Session 固定攻击 (Session Fixation)
Session 固定是攻击者预先设置一个 session ID,诱骗受害者使用这个 ID 登录:
攻击流程:
1. 攻击者访问目标网站,获得一个 session ID (如 sess=abc123)
2. 攻击者构造一个包含这个 session ID 的链接
3. 受害者点击链接,使用 sess=abc123 访问网站
4. 受害者登录,服务端确认 sess=abc123 对应登录用户
5. 攻击者使用同样的 sess=abc123,即获得受害者权限import requests
def session_fixation_test(target_url):
"""Session 固定攻击测试"""
# 步骤1: 获取一个 session ID
resp1 = requests.get(target_url)
session_id = resp1.cookies.get("PHPSESSID") or resp1.cookies.get("session")
print(f"[*] 获取到 session ID: {session_id}")
# 步骤2: 构造带 session ID 的恶意链接
malicious_link = f"{target_url}?PHPSESSID={session_id}"
print(f"[*] 恶意链接: {malicious_link}")
# 步骤3: (模拟) 用户登录后检查 session 是否变化
# 如果登录后 session ID 不变,说明存在固定攻击
login_data = {"username": "victim", "password": "victim123"}
cookies = {"PHPSESSID": session_id}
resp2 = requests.post(f"{target_url}/login", data=login_data, cookies=cookies)
new_session = resp2.cookies.get("PHPSESSID")
if new_session == session_id:
print("[!] 登录后 session ID 未改变 - 存在 Session 固定漏洞!")
else:
print("[*] 登录后 session ID 已改变 - 安全")
# 防御: 登录后重新生成 session ID
# PHP: session_regenerate_id(true)
# Django: 会自动处理
# Flask: 需要手动调用 session.modified = TrueSession 劫持 (Session Hijacking)
常见 Session 劫持方式:
1. Cookie 窃取 (XSS)
2. 网络嗅探 (HTTP 明文传输)
3. Session 文件读取 (LFI)
4. Session 预测 (弱 session ID)
5. 会话固定攻击import requests
import re
def session_hijacking_check(target_url):
"""检查 Session 安全性"""
resp = requests.get(target_url)
cookies = resp.cookies
issues = []
for cookie in cookies:
# 检查 HttpOnly 标志
if "httponly" not in str(cookie).lower():
issues.append(f"[!] {cookie.name}: 缺少 HttpOnly 标志")
# 检查 Secure 标志
if "secure" not in str(cookie).lower():
issues.append(f"[!] {cookie.name}: 缺少 Secure 标志")
# 检查 SameSite 属性
if "samesite" not in str(cookie).lower():
issues.append(f"[*] {cookie.name}: 缺少 SameSite 属性")
# 检查 session ID 复杂度
value = cookie.value
if len(value) < 16:
issues.append(f"[!] {cookie.name}: 值太短 ({len(value)} 字符)")
# 检查是否只包含数字
if value.isdigit():
issues.append(f"[!] {cookie.name}: 值只包含数字,可能被预测")
return issues
# 使用示例
# issues = session_hijacking_check("http://target.com")
# for issue in issues: print(issue)Cookie 操纵攻击
import requests
import base64
import json
def cookie_manipulation_tests(target_url):
"""Cookie 操纵测试"""
tests = []
# 测试1: 明文权限字段修改
# 原始: role=user, 尝试: role=admin
original_cookies = {"role": "user"}
test_cookies = {"role": "admin"}
resp = requests.get(target_url, cookies=test_cookies)
if "admin" in resp.text.lower() or resp.status_code == 200:
tests.append("[!] 直接修改 role 有效")
# 测试2: Base64 编码 Cookie 解码修改
# 如果 cookie 值是 Base64 编码的
original_value = base64.b64encode(b'{"role":"user"}').decode()
decoded = base64.b64decode(original_value)
data = json.loads(decoded)
data["role"] = "admin"
new_value = base64.b64encode(json.dumps(data).encode()).decode()
test_cookies = {"data": new_value}
resp = requests.get(target_url, cookies=test_cookies)
if "admin" in resp.text.lower():
tests.append("[!] Base64 Cookie 修改有效")
# 测试3: 序列化数据修改
# PHP 序列化格式
original = 'O:4:"User":2:{s:4:"name";s:4:"test";s:4:"role";s:4:"user";}'
modified = original.replace('s:4:"user"', 's:5:"admin"')
test_cookies = {"user_data": modified}
resp = requests.get(target_url, cookies=test_cookies)
if resp.status_code == 200 and "admin" in resp.text.lower():
tests.append("[!] PHP 序列化 Cookie 修改有效")
# 测试4: Cookie 属性检查
resp = requests.get(target_url)
set_cookie_headers = resp.headers.get("Set-Cookie", "")
if "HttpOnly" not in set_cookie_headers:
tests.append("[!] Cookie 缺少 HttpOnly 标志")
if "Secure" not in set_cookie_headers:
tests.append("[!] Cookie 缺少 Secure 标志")
if "SameSite" not in set_cookie_headers:
tests.append("[!] Cookie 缺少 SameSite 属性")
return testsCookie 安全标志详解
HttpOnly:
- 防止 JavaScript 访问 Cookie
- 缓解 XSS 窃取 Cookie 的风险
- 设置: Set-Cookie: name=value; HttpOnly
Secure:
- Cookie 只通过 HTTPS 传输
- 防止中间人嗅探
- 设置: Set-Cookie: name=value; Secure
SameSite:
- Strict: 完全禁止跨站发送 Cookie
- Lax: 允许顶级导航的 GET 请求跨站发送
- None: 允许跨站发送 (必须同时设置 Secure)
- 设置: Set-Cookie: name=value; SameSite=Lax
Domain:
- 指定 Cookie 可发送到的域名
- 如果设置为 .example.com,子域名也能访问
- 错误设置可能导致子域名泄露 Cookie
Path:
- 指定 Cookie 可发送到的路径
- 限制 Cookie 的作用范围
Expires / Max-Age:
- Cookie 的有效期
- 会话 Cookie 在浏览器关闭时删除
- 持久 Cookie 保留到过期时间Burp Suite Cookie 分析
使用 Burp Suite 分析 Cookie:
1. Proxy > HTTP History 中查看请求和响应
2. 关注 Set-Cookie 和 Cookie Header
3. 使用 Repeater 修改 Cookie 值测试
4. 使用 Decoder 解码 Cookie 值 (Base64、URL编码等)
5. 使用 Comparer 对比不同用户的 Cookie
常见 Burp 测试:
- 修改 Cookie 中的用户 ID
- 修改 Cookie 中的角色字段
- 删除 Cookie 看是否重新认证
- 复制一个用户的 Cookie 到另一个用户Session ID 安全性评估
import re
import math
from collections import Counter
def analyze_session_id(session_ids):
"""分析 Session ID 的安全性"""
if not session_ids:
return "没有样本"
results = {}
# 长度分析
lengths = [len(sid) for sid in session_ids]
results["平均长度"] = sum(lengths) / len(lengths)
results["最小长度"] = min(lengths)
results["最大长度"] = max(lengths)
if min(lengths) < 16:
results["长度警告"] = "Session ID 长度不足 16 字符"
# 字符集分析
all_chars = set()
for sid in session_ids:
all_chars.update(set(sid))
results["字符集大小"] = len(all_chars)
if len(all_chars) < 10:
results["字符集警告"] = "字符集太小,可能被预测"
# 信息熵计算
total_chars = ''.join(session_ids)
freq = Counter(total_chars)
entropy = 0
for count in freq.values():
p = count / len(total_chars)
entropy -= p * math.log2(p)
results["信息熵"] = round(entropy, 2)
if entropy < 3:
results["熵警告"] = "信息熵过低,随机性不足"
# 重复检测
if len(set(session_ids)) < len(session_ids):
results["重复警告"] = "存在重复的 Session ID"
return results
# 使用示例
# sids = ["abc123def456", "abc123ghi789", ...]
# print(analyze_session_id(sids))常见误区
- 以为 Cookie 里看不到用户名就不能改身份。
- 看到 JWT 能解码,就以为能随便改。
- 只改页面里的用户 ID,不看 Cookie 是否也参与权限判断。
- 忽略
HttpOnly、Secure、SameSite等属性。
一句话判断
题目涉及登录态、身份、权限、管理员 Cookie、Session ID、Set-Cookie、JWT 或"改 Cookie 后权限变化"时,就按 Cookie 与 Session 分析。
核心问题是:服务端到底信任了客户端 Cookie 里的什么,哪些状态保存在服务端,哪些状态可被客户端伪造。
题目中常见信号
- Cookie 里有
role=user、admin=0、uid=1。 - Cookie 值像 Base64、JSON、JWT、PHP 序列化或加密串。
- 登录前后 Session ID 不变化。
- 删除 Cookie 后变游客,复制 Cookie 后变成另一个用户。
Set-Cookie缺少 HttpOnly、Secure、SameSite。- XSS 题里要读取 bot Cookie。
- 文件包含或路径穿越题能读取 session 文件。
核心概念
Cookie 是浏览器保存并自动带上的数据;Session 是服务端保存状态的一种方式。常见关系:
Cookie: sessionid=abc123
-> 服务端查 session 存储
-> 得到 uid/role/登录状态如果权限字段直接放在 Cookie 里,并且没有签名或服务端校验,攻击者可以直接改。如果 Cookie 只是 Session ID,就要看 ID 是否可预测、可固定、可泄露或可复用。
最小分析流程
- 抓登录前、登录后、退出后的请求和响应。
- 记录所有
Set-Cookie和Cookie。 - 判断 Cookie 值类型:明文、Base64、JWT、序列化、随机 ID。
- 修改单个字段,观察权限是否变化。
- 删除 Cookie,观察服务端是否重新认证。
- 复制低权/高权 Cookie 互换测试。
- 检查 HttpOnly、Secure、SameSite。
- 如果是 Session ID,测试长度、随机性、登录后是否轮换。
最小验证示例
直接改权限字段:
curl -i 'http://target/admin' \
-H 'Cookie: role=user'
curl -i 'http://target/admin' \
-H 'Cookie: role=admin'Base64 Cookie:
import base64, json
v = "eyJyb2xlIjoidXNlciJ9"
data = json.loads(base64.b64decode(v + "=="))
data["role"] = "admin"
print(base64.b64encode(json.dumps(data).encode()).decode())Session 固定:
curl -i 'http://target/login' -H 'Cookie: PHPSESSID=test123'登录后如果 PHPSESSID 仍是 test123,说明有固定风险。
常见利用 / 解题路线
路线总览:
路线一:明文字段篡改
role=user -> role=admin
admin=0 -> admin=1
uid=1 -> uid=2适合没有签名、没有服务端校验的入门题。
路线二:编码后字段篡改
Base64/URL/JSON/PHP serialize -> 解码 -> 改字段 -> 重新编码如果涉及对象结构,回到 反序列化入门。
路线三:JWT 伪造
三段式 token 进入 JWT基础,不要把可解码误认为可修改。
路线四:Session 固定或预测
登录前设置 session id -> 登录后不轮换 -> 攻击者复用同一 session或收集多个 Session ID 判断是否递增、短弱、重复。
路线五:Cookie 窃取
XSS 中如果 Cookie 无 HttpOnly,可用脚本读取;如果有 HttpOnly,要改为 CSRF、读页面内容或利用服务端接口。
常见失败原因
- 把编码当加密:Base64 能解码不代表有签名或保密。
- 忽略服务端 Session:Cookie 可能只是索引,改了无效很正常。
- 只测当前用户:需要低权和高权对比 Cookie 差异。
- 忘记退出登录测试:退出后 Session 是否失效很关键。
- 复制 Cookie 时漏字段:多个 Cookie 共同决定状态。
- 只看请求 Cookie,不看响应 Set-Cookie:服务端可能在响应里覆盖你的修改。
- HttpOnly 误判:HttpOnly 只防 JS 读取,不防请求自动携带。
迷你案例
题目登录后请求:
Cookie: uid=1001; role=user访问 /admin 返回 403。
测试:
curl -i 'http://target/admin' \
-H 'Cookie: uid=1001; role=admin'返回 200 并显示后台页面。说明服务端直接信任客户端 role 字段。
如果 role=admin 无效,再看 Cookie 是否是:
data=eyJ1aWQiOjEwMDEsInJvbGUiOiJ1c2VyIn0=解码、修改、重编码后再测。WP 中要写明 Cookie 类型、修改字段、响应差异和根因。