4、身份认证与授权:Token认证机制、OAuth 2.0集成、会话管理漏洞分析
身份认证和授权,说白了就是MCP协议里的「门禁系统」。你想想看,一个MCP服务器对外暴露了那么多工具和资源,要是谁都能随便调用,那不乱套了?我这些年挖过的漏洞里,至少有三分之一跟认证授权有关。今天咱们就好好聊聊这块。
4.1 Token认证机制:JWT的那些坑
MCP协议里最常见的认证方式就是Token认证,尤其是JWT(JSON Web Token)。嗯,这里要注意,JWT本身是个好东西,但用不好就是灾难。
核心要点:JWT的签名验证是安全基石,但很多人只验证了「有没有签名」,没验证「签名对不对」。
我在项目中遇到过这么个案例:某MCP服务器在解析JWT时,直接用了jwt.decode(token, verify=False)。你猜怎么着?攻击者随便伪造一个Token,里面写上{"role": "admin", "sub": "attacker"},就能直接调用管理员接口。这漏洞挖得我都不好意思上报。
正确的做法应该是这样:
# 错误示范 - 千万别这么写
import jwt
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
payload = jwt.decode(token, verify=False) # 危险!
# 正确做法
import jwt
from cryptography.hazmat.primitives import serialization
# 加载公钥
with open("public_key.pem", "rb") as f:
public_key = serialization.load_pem_public_key(f.read())
try:
payload = jwt.decode(
token,
public_key,
algorithms=["RS256"],
audience="mcp-server",
issuer="mcp-auth"
)
except jwt.InvalidTokenError as e:
print(f"Token验证失败: {e}")
这里有几个关键点:
- 算法白名单:只允许你预期的算法,别用
algorithms=["HS256", "RS256"]这种写法,否则攻击者可以搞算法混淆攻击 - 过期时间验证:
exp字段必须检查,我见过有人把过期时间设成2099年的 - 签名密钥保护:私钥别硬编码在代码里,也别提交到Git仓库
避坑指南:我曾经在审计一个MCP插件时发现,开发者把JWT的secret key写在了config.py里,还上传到了公开的GitHub仓库。攻击者直接clone下来,伪造了任意用户的Token。嗯,从那以后我养成了习惯——先扫一遍代码里的硬编码密钥。
4.2 OAuth 2.0集成:授权码流程的陷阱
MCP协议支持OAuth 2.0作为认证方式,这在大规模部署时很常见。但OAuth 2.0的集成复杂度高,容易出问题。
标准的授权码流程是这样的:
- 客户端请求授权码
- 用户授权后,授权服务器返回授权码
- 客户端用授权码换取访问令牌
- 用访问令牌调用MCP接口
听起来简单对吧?但实际集成时,我见过太多翻车案例了。
常见漏洞:CSRF攻击、重定向URI劫持、授权码泄露、令牌存储不当。
举个例子,重定向URI劫持。有些MCP客户端在配置OAuth时,把重定向URI设成了http://localhost:8080/callback。攻击者可以在同一台机器上开个恶意服务,监听这个端口,截获授权码。我去年挖过一个类似的漏洞,攻击者通过这个方式拿到了用户的访问令牌,然后调用了MCP服务器上的敏感数据查询工具。
正确的做法是:
# OAuth 2.0 授权码流程 - 安全实现示例
import requests
from flask import Flask, request, session
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
# 1. 生成并存储state参数,防止CSRF
@app.route('/auth/login')
def login():
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
auth_url = (
f"https://auth.mcp-server.com/authorize?"
f"response_type=code&"
f"client_id=YOUR_CLIENT_ID&"
f"redirect_uri=https://your-mcp-client.com/callback&"
f"scope=tools:read resources:write&"
f"state={state}"
)
return redirect(auth_url)
# 2. 验证state并交换令牌
@app.route('/auth/callback')
def callback():
# 验证state,防止CSRF
if request.args.get('state') != session.get('oauth_state'):
return "CSRF攻击检测!", 403
code = request.args.get('code')
# 交换令牌 - 使用POST请求,不要在URL中传递code
token_response = requests.post(
"https://auth.mcp-server.com/token",
data={
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': 'https://your-mcp-client.com/callback',
'client_id': 'YOUR_CLIENT_ID',
'client_secret': 'YOUR_CLIENT_SECRET'
},
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
tokens = token_response.json()
# 安全存储令牌
session['access_token'] = tokens['access_token']
session['refresh_token'] = tokens.get('refresh_token')
return "认证成功!"
这里有几个我特别想强调的点:
- state参数必须用:别嫌麻烦,这是防CSRF的关键
- redirect_uri要严格校验:最好用白名单,别用正则匹配
- 授权码只能用一次:用完就失效,防止重放攻击
- 令牌不要暴露在URL中:用POST请求,别用GET
4.3 会话管理漏洞分析
会话管理是MCP认证体系里最容易被忽视的一环。你想想看,Token认证和OAuth都做得再好,如果会话管理有漏洞,攻击者照样能绕过。
我总结了几类常见的会话管理漏洞:
| 漏洞类型 | 风险描述 | 修复建议 |
|---|---|---|
| 会话固定攻击 | 攻击者让用户使用已知的会话ID | 登录后重新生成会话ID |
| 会话劫持 | 攻击者窃取会话ID冒充用户 | 使用HTTPS、设置Secure/HttpOnly标志 |
| 会话超时不当 | 会话长期有效,增加泄露风险 | 设置合理的过期时间,实现空闲超时 |
| 并发会话控制缺失 | 同一用户多设备登录无限制 | 限制并发会话数,或实现踢下线机制 |
嗯,这里要特别说一下会话超时的问题。我在审计一个MCP服务器时发现,它的会话过期时间设成了24小时。你想想看,如果用户的Token泄露了,攻击者有整整一天的时间来搞破坏。更离谱的是,这个服务器还没有实现刷新令牌的轮换机制——也就是说,一个刷新令牌可以无限次使用。
避坑指南:我曾经在渗透测试中,通过抓包拿到了一个MCP客户端的会话Cookie。这个Cookie既没设HttpOnly,也没设Secure标志。我直接在浏览器里改了Cookie值,就成功冒充了管理员。嗯,从那以后我每次做安全审计,第一件事就是检查Cookie的安全属性。
安全的会话管理应该做到:
- 会话ID要足够随机:别用时间戳、用户ID这种可预测的值
- Cookie安全属性要齐全:HttpOnly、Secure、SameSite一个都不能少
- 实现会话绑定:把会话ID和客户端IP、User-Agent绑定,增加劫持难度
- 提供登出功能:登出时要清除服务端的会话记录,不只是删掉客户端的Cookie
4.4 知识体系总览
说了这么多,我画了张图帮你梳理一下MCP身份认证与授权的知识体系。这张图涵盖了Token认证、OAuth 2.0集成和会话管理三个核心模块,以及它们之间的关联关系。
这张图把整个认证授权体系分成了三层。最上面是三大核心模块,中间是安全基础要求,最下面是常见攻击面。你想想看,任何一个环节出了问题,攻击者都可能从最下面的攻击面找到突破口。
最后提醒一句:认证授权不是「配好了就完事」的东西。我建议你在MCP服务器上线前,至少做一次完整的认证授权安全审计。重点检查Token验证逻辑、OAuth回调处理、会话管理策略这三个方面。嗯,别问我为什么这么强调——都是踩过的坑换来的经验。
公众号:蓝海资料掘金营,微信deep3321