AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例

我用 AI 逆向 Upwork 消息系统,2小时搞定数据层开发

前言

作为 Upwork 自由职业者,我一直觉得它的消息管理界面信息量太大,不够直观。我想做一个 Chrome 插件来简化消息管理,核心需求很简单:一眼看出哪些对话需要我回复,哪些在等对方。

传统做法是下载混淆后的 JS 文件慢慢分析,但这次我决定换个思路——全程和 AI 配合,看看能多快搞定。

结果远超预期。从零开始到完全摸清 API、认证方式、数据结构,总共不到 2 小时。

第一步:摸清技术栈(5分钟)

打开 Upwork 消息页面,F12 看 Sources 面板,从加载的 JS 文件名就能判断出技术栈:

ThunderNuxt/rooms.fdb6ff58.js ThunderNuxt/realtime.fa79131f.js ThunderNuxt/composer.9c0ad3d8.js

“Thunder” 是 Upwork 消息系统的内部代号,基于 Nuxt.js(Vue 2 SSR 框架)。实时通信用了两条 WebSocket 连接,都基于 Atmosphere.js 框架:

wss://tl.upwork.com/wp?app=thunder&... // 消息主通道 wss://tl.upwork.com/wp?app=global-dash-api&... // 通知/监控

同时还加载了 Forter、Incognia 两套反欺诈 SDK 和 OneTrust 隐私合规组件。整体架构很清晰。

关键发现:不需要下载和分析任何 JS 文件。 那些代码都是混淆压缩过的,变量名全是 o4YH1l 这种乱码。我需要的只是数据从哪来、长什么样。

第二步:找到 Vue 实例和 Store(15分钟)

Upwork 消息页面实际上有两个独立的 Vue 应用——顶部导航栏是一个微前端(spa_user.umd.js),消息主体是另一个。直接用 document.querySelector('#app').__vue__ 是找不到的。

最终通过遍历 DOM 定位到正确的入口:

void function() { let s = null; document.querySelectorAll('div').forEach(el => { if (el.__vue__ && el.__vue__.$store && !s) { s = el.__vue__.$store; } }); if (s) { console.log('模块:', Object.keys(s._modules.root._children)); console.log('State:', Object.keys(s.state)); console.log('Actions:', Object.keys(s._actions)); } }();

但发现了一个意外:Thunder 的 Vuex Store 里并没有消息数据模块。模块列表是 tracingcontextuserflagstheme 这些基础设施,消息数据完全走 REST API 获取。

这说明 Upwork 的消息内容不缓存在前端状态管理里,每次都是从服务端拉取。对我的插件来说反而更简单——直接调 API 就行。

第三步:抓取 API 端点(10分钟)

在 Network 面板筛选 Fetch/XHR,切换对话时可以看到所有请求。核心 API 一共就几个:

GET /api/v3/rooms/rooms/simplified?limit=20&callerOrgId={orgId} → 对话列表 GET /api/v3/rooms/rooms/{roomId}/stories/simplified?limit=20&callerOrgId={orgId} → 消息列表 GET /api/v3/rooms/rooms/{roomId}/users?limit=100&callerOrgId={orgId} → 对话参与者 GET /api/v3/rooms/users/messageCounts?callerOrgId={orgId} → 未读数统计

所有请求都挂在 /api/v3/rooms/ 路径下,参数结构统一,非常规整。

第四步:搞定认证(5分钟)

直接调 API 会返回 401 Unauthorized。Token 在哪?

遍历 localStorage 立刻找到:

localStorage.getItem('f60cac5f103c5518_api_token') // → "oauth2v2_int_36466ecdc9f06e6509a66b018cf9a60e"

加上 Authorization: Bearer 头就能正常请求:

const token = localStorage.getItem('f60cac5f103c5518_api_token'); const headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }; const res = await fetch('/api/v3/rooms/rooms/simplified?limit=20&callerOrgId=' + orgId, { headers }); const data = await res.json();

对于 Chrome 插件来说,Content Script 运行在 Upwork 页面上下文中,用户已经登录,直接从 localStorage 读 Token 即可,不需要用户手动输入任何凭据。

第五步:解析数据格式(20分钟)

这是最有趣的部分。API 返回的不是常规 JSON,而是 Thrift 序列化的 JSON 格式,字段名全是数字编号:

{ "1": {"str": "room_f0ff6267bae1..."}, "2": {"str": "某某客户"}, "7": {"str": "项目标题"}, "8": {"map": ["str", "str", 26, {...}]}, "10": {"i32": 0}, "12": {"i64": 1771698648604}, "13": {"rec": {"1": {"str": "story_865b..."}, "8": {"str": "消息内容"}}} }

看起来像加密?其实不是。Thrift 是 Apache 的跨语言序列化框架,数字编号只是字段 ID。通过对照页面上显示的内容和返回数据,每个字段的含义很快就反推出来了:

对话(Room)结构:

字段类型含义
1strroomId
2str对话标题
7str项目名称
8map上下文信息(合同ID、金额、状态等)
10i32未读消息数
12i64最后更新时间戳
13rec最后一条消息
30str客户ID
31str客户组织ID
34str合同ID

消息(Story)结构:

字段类型含义
1strstoryId
2strroomId
3i64创建时间
5str发送者ID
8str消息正文
10str消息类型(系统消息)
12lst关联对象(里程碑等)
13tf是否已读
36str摘要文本

这些数字编号在 Upwork 内部的 .thrift 定义文件里有对应的字段名,但我们不需要那个文件,通过数据本身就能完全还原语义。

第六步:实现核心业务逻辑(5分钟)

有了数据结构,插件的核心逻辑就非常简单了。我最想要的功能是对话状态自动分类——判断"最后一条消息是谁发的"来决定状态:

function getRoomStatus(room, myId) { const lastStory = room['13']; if (!lastStory) return { label: '无消息', icon: '⚪' }; const senderId = lastStory['5']?.str; const unread = lastStory['13']?.tf === 0; const time = lastStory['3']?.i64; const daysSince = (Date.now() - time) / 86400000; if (senderId !== myId && unread) { return { label: '新消息', icon: '🔴', priority: 1 }; } if (senderId !== myId) { if (daysSince > 3) return { label: '急需回复', icon: '🟠', priority: 2 }; return { label: '需要回复', icon: '🟡', priority: 3 }; } if (senderId === myId) { if (daysSince > 7) return { label: '对方可能忘了', icon: '💤', priority: 4 }; return { label: '等对方回复', icon: '🟢', priority: 5 }; } }

五种状态,按优先级排序,一眼就知道该先处理哪个对话。

总结:AI 改变了前端逆向的游戏规则

整个过程没有下载任何 JS 文件,没有用反混淆工具,没有读一行压缩代码。

传统逆向流程:下载 JS → 格式化 → 反混淆 → 读代码 → 猜逻辑 → 试错。可能需要几天。

AI 辅助流程:告诉 AI 你看到什么 → AI 告诉你下一步查什么 → 把结果贴回来 → AI 分析含义。2 小时。

本质上 AI 充当了一个"有经验的逆向工程师搭档"。它知道 Nuxt 应用的 Vue 实例挂在哪里,知道 Atmosphere.js 是 WebSocket 框架,知道 Thrift 序列化长什么样,知道 OAuth Token 通常存在哪。这些经验以前需要人积累多年,现在一个对话窗口就搞定了。

前端 JS "加密"的门槛已经非常低了。只要数据要展示给用户,它就必须在浏览器里被解密和渲染,这个过程中的一切都是透明的。AI 只是让找到这些数据的过程变得极其高效。

Read more

【AI开发】—— Copilot最佳使用方式与配置

【AI开发】—— Copilot最佳使用方式与配置

从 Claude Code 到 Copilot:我的 AI 编码工具选型与深度配置指南 本人使用过很多智能体开发工具,Claude Code、Codex、Cursor、Google Studio、Coze,其实千篇一律,大同小异。各厂商对 Agent 的 Prompt 设定与思考逻辑等略有差异,例如 Claude Code、Codex 等都有内置的系统提示词,作为开箱即用的 Coding 工具,专门针对编码、测试等开发流程进行了优化,使大家使用起来觉得非常高效(以 Claude Code 为例,感兴趣的可以参考 Claude Code 的系统提示词及智能体的设定);后来出现了 Skills、MCP 等、Plan Agent、SubAgent 等新特性,

主流大模型介绍(GPT、Llama、ChatGLM、Qwen、deepseek)

主流大模型介绍(GPT、Llama、ChatGLM、Qwen、deepseek)

GPT系列模型 一、ChatGPT 的本质 * 发布者:OpenAI(2022年11月30日) * 类型:聊天机器人模型,基于自然语言处理技术 * 核心能力:理解语言、生成对话、撰写邮件/文案/代码、翻译等 * 增长数据:2个月用户破1亿,日活约1300万 二、GPT 系列模型演进对比 模型发布时间参数量核心创新主要局限GPT-12018.061.17亿引入生成式预训练 + Transformer Decoder语言模型单向;需微调才能泛化GPT-22019.0215亿多任务学习 + Zero-shot 能力无监督能力仍有限GPT-32020.051750亿Few-shot 学习 + Sparse Attention成本高、长文本不稳定、内容不可控ChatGPT2022.11基于GPT-3引入 RLHF(人类反馈强化学习)服务不稳定、可能生成错误信息 三、核心技术点回顾 1. GPT-1 * 使用单向 Transformer Decoder(

[论文阅读] (38)基于大模型的威胁情报分析与知识图谱构建论文总结(读书笔记)

[论文阅读] (38)基于大模型的威胁情报分析与知识图谱构建论文总结(读书笔记)

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢。由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正,非常欢迎大家给我留言评论,学术路上期待与您前行,加油。 忙碌的五月终于过去,忙到来不及分享技术,六月开启,继续更新博客,感谢大家的支持,久等了! 本文旨在系统梳理大语言模型(LLM)在网络安全与威胁情报分析中的最新研究进展,侧重知识图谱构建、攻击行为建模以及模型泛化与推理能力等关键技术维度。结合作者当前的研究方向与兴趣,本文挑选并归纳了多篇代表性论文,其中重点详述的为与团队工作紧密相关、具有实际借鉴价值的工作。这些大佬的文章真心值得我们学习,希望本文对您有所帮助,写得不足之处还请海涵。 在逐篇阅读过程中,笔者特别关注以下要素:论文所提出的系统框架图、大模型的技术创新点、与知识图谱的融合机制、以及所采用的实验验证方法和开源代码。这些内容不仅拓宽了对 LLM 能力边界的理解,也为我们后续在威胁情报建模与网络安全防御方面提供了一定的路径指引。 同时,欢迎各位老师和大佬补充相关高质量论文,后续笔者也将不断更新与精炼此系列内容。希望