快乐的春饼

Virtual Judge 自动抓取 OJ Cookie

问题引入

Virtual Judge(简称VJ)可以向其他 Online Judge(简称 OJ)提交题目,并在 VJ 上留下记录。

VJ 有其中一些 OJ 的机器人账号,如果提交的题目是这些 OJ 上的,VJ 直接用机器人账号交题。

对于另一些 OJ,因为验证机制比较复杂,机器人难以通过,所以 VJ 不支持直接用机器人账号向它们提交题目。VJ 的办法是:只要用户提供了那个 OJ 的 Cookie,VJ 就可以伪装成用户的账号,利用用户自己的账号交题。另外有时候即使机器人账号可用,我们还是想在自己账号上留下提交记录,也能选择这种方法。

在 VJ 的提交页面,有“My Account”或者“Archive”选项,前者向用户账号提交一份新的代码,后者从用户账号中抓取之前提交过的代码。这两个操作都要用到 Cookie。在“Account”一栏中,有个“Update”选项,用户可以手动从浏览器的 Cookie 管理页面复制 OJ 的 Cookie 并填入。然后就可以在 VJ 上用自己的账号交题了。

问题在于,手动复制太麻烦了。有些 OJ(比如 UVA),几乎每天都要更新 Cookie,手动复制 Cookie 实在繁琐。那为什么 VJ 不能直接获取其他 OJ 的 Cookie 呢?

浏览器限制

询问 AI:为什么网站不允许访问其他网站的 Cookie?

1. 保护用户隐私

  • Cookie 中常包含用户的登录信息、偏好设置、会话 ID 等敏感信息。
  • 如果任意网站都可以读取其他网站的 Cookie,那用户在其他站点的私人数据将被泄露,严重威胁隐私。

2. 防止跨站攻击(XSS、CSRF)

  • 如果 A 网站能读取 B 网站的 Cookie,攻击者可以通过 A 网站诱导用户访问,并窃取 B 网站的 Cookie,从而劫持会话。
  • 这种能力会让 跨站请求伪造(CSRF)跨站脚本攻击(XSS) 更容易实现。

3. 遵循同源策略

  • 同源策略是浏览器最基本的安全机制之一,限制网页能访问的资源范围。
  • 两个网站必须“同源”(协议、域名、端口相同)才能访问彼此的数据(包括 Cookie、localStorage、DOM 等)。

初步解决

除了用户自己,还有没有什么方式可以拿到 Cookie 呢?使用用户脚本。编写一个在 Tampermonkey(油猴)等用户脚本管理器上运行的 JavaScript 程序,就可以自动获取 Cookie 并发送给某个网站。实话实说,这种工作方式很像木马。

我们只需要让程序模拟人工操作即可。通过浏览器开发人员工具对 VJ 抓包,我发现了以下关键特征。

原理

HTTP 请求方式

VJ用XMLHttpRequest发送 HTTP 请求,而非更现代的fetch,它们的 API 有较大差别。

  1. 以下所有操作的前提是 VJ 账号已登录,即所有对 VJ 的请求都带了 VJ 的 Cookie
  2. 在 VJ 中打开 Submit 界面的 My Account 或 Archive 时
  3. 浏览器请求GET /user/verifiedAccount?oj=xxx,VJ 检测之前是否验证过 xxx 的 Cookie,验证过则返回 {"accountDisplay": "<a href="xxx的用户主页" target="_blank">用户名</a>"},将classmy-accountspan的内容由(Not set)替换为刚才返回的内容;否则返回{}
  4. 如果3成功,请求 GET /user/checkAccount?oj=xxx,VJ 检测 xxx 的 Cookie 是否仍然有效,有效则返回{"result":"success"};否则返回{}
  1. 在 Update 页面点击 Confirm 后
  2. 请求POST /user/verifyAccount,负载为用户账号的 Cookie:{"oj":"xxx","proof":[{"name": "...", "value": "..."},...]},响应为{"success": true, "accountDisplay": "<a href="xxx的用户主页" target="_blank">用户名</a>"},或{"success":false,"error":"Verify failed!"}
  3. 如果2成功,再请求一次GET /user/checkAccount?oj=xxx

脚本详细步骤

理顺了以上流程,我们就可以设计一个油猴脚本了。具体操作如下:

IMPORTANT:一定要在油猴的设置里把“安全”中的“允许脚本访问 Cookie”设置为 All,否则获取不到有些 OJ 的 Cookie。因为那些 OJ 把关键 Cookie 设置成了 Http-Only,禁止 JavaScript 访问。

  1. 如果当前页面为 VJ,并检测到浏览器发起了请求GET /user/verifiedAccount?oj=xxxGET /user/checkAccount?oj=xxx,如果有一个响应失败,则
  2. 获取 xxx 的相应 Cookie
  3. 如果2没有获取到相应的 Cookie,则原因应该为用户未登录到相应 OJ,提示用户登录
  4. POST /user/verifyAccount,将负载设为获取到的 Cookie
  5. 如果4成功,则更新提示信息;否则可能是用户 Cookie 过期,提示用户登录
  6. 如果有哪步失败了,加个 Retry 按钮

代码

Github | 油叉 | 脚本猫

总结

这个问题出发点很好 ,属于没有困难制造困难也要上,思路很清晰,以上所述基本就是我的思考过程。因为我对 JavaScript 和油猴脚本都不太熟,所以代码的细节是一点一点和 AI 交流得来的。这个问题能成功解决也证明了 AI 在编程领域的重要作用。程序员不用再死扣细节,重要的是对程序整体架构上的把握,发挥创造能力。