本文章没有针对任何厂商和平台, 我只是平等地看不起所有把开源代码修改几下, 自以为是地加个所谓的”加密”, 就敢把产品拿出来祸害用户的垃圾厂商.
本文章的所有内容纯属虚构, 如有雷同纯属巧合.
0 写在前面
本文中以虚构的笔X分享为例, 介绍一种通过 HTTP 流量抓包/重放实现的简单的获取声称”被加密”的 PDF 源文件的方法.
首先来细数这类分享平台的罪恶:
- 滥用开源代码, 不遵守开源协议. 不仅代码闭源, 甚至在不标注代码来源的情况下修改代码后商用.
- 以文件”加密”平台自居, 实际上并未对文件进行任何意义上的加密, 仅使用开源的前端 JS 代码对 web 端页面权限进行限制. 文件传输\读取过程暴露在完全未加密的状态下, 属于是害人来的.
- 文件水印功能实际上只显示在前端, 并未对文件进行加水印操作, 属于是想省钱想疯了.
1 工具准备
本文以 Burp Suite Community 为例.
2 XX, 启动!
启动 Burp, 在 Proxy 页上开启拦截.
在 Burp 内置的谷歌浏览器中访问得到的分享链接.
如果你使用的是 2025 年之后版本的 Burp 的话, 会看到 Burp 将页面的所有 web 请求都整齐地列了出来, 这点比老版本的 Burp 会舒服很多. 而且还可以多选 Forward/Drop.
作为例子的这个网站接入了腾讯的统计系统, 会频繁向 otheve.beacon.qq.com
发送请求. 并且每隔 1s 就会发起一次 heartbeat 请求. 比较烦人, 可以右键停止拦截到这些目录的请求.
放一些包, 直到看到一个明摆着加载了文件的包时:

好家伙, 文件直接存到 CDN 里面, 访问的时候前端限制一下, 然后 CDN 里面直接丢源文件过来.
把这个请求传到 Repeater 里面.

就拿到了源文件.
Response 栏上面三条杠, 保存到文件, 再用二进制编辑器把文件内容之外的 HTTP 响应体删掉. 如果你的 PDF 阅读器够现代化, 可以自动识别到文件头的位置, 甚至可以直接打开.
3 所以… 问题出在哪里
首先大概梳理以下这个网站的”加密”逻辑:
用户上传文件
存储至 CDN 并为文件生成一个 ID (疑似为 Base64 编码后的数字字符串)-> (猜测)将文件在 CDN 中的存储位置与随机生成的文件 ID 存储至数据库
这里就已经出现了比较严重的问题了.

可以看到, 当文件上传时, 会跳转至 /#/pdf/result/<digital id>
页面, 会将访问文件时使用的链接(https://pdfxx.cn/api/pdf/pdf?id=MTIyxxxx
)显示出来. 神奇的事情发生了, 链接中的 id
参数在 base64 解码之后正是 url 中的<digital id>. 因此猜测存在 id 爆破问题. 尝试访问几个其他的文件:

确实和猜测的一样. 多试了几个, 没有加密的文件是可以直接访问的:

访问文件时会从 CDN 上获取对应的文件

用户访问文件
根据用户访问的 id 查询文件在 CDN 上的路径 -> 用 pdfjs 加载 CDN 上的 pdf 文件.
也就是说, 全程实际上没有涉及到加密传输之类的技术, 仅在前端对按键\开发者工具等进行了限制.
在看 js 代码时, 发现了一个接口:

联系上下文, 应该是获取 pdf 文件在 CDN 中存储地址的接口, 浅试一下:

至此已经可以在获得文件分享地址的情况下提取到 pdf 源文件了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import requests from base64 import b64decode
host = "pdfxx.cn" id = "xxxxxx" url_api = f"https://{host}/api/pdf/uurl?id={id}"
headers = { "Host": host, "Sec-Ch-Ua": '"Chromium";v="133", "Not(A:Brand";v="99"', "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": '"Windows"', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", "Accept": "*/*", "Origin": f"https://{host}", "Sec-Fetch-Site": "same-site", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Referer": f"https://{host}/", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9", "Priority": "u=1, i", "Connection": "keep-alive" }
api_cdn = requests.get(url_api, headers=headers, timeout=10) url_cdn = b64decode(api_cdn.json.get("data").encode())
response = requests.get(url_cdn, headers=headers, timeout=100) response.raise_for_status()
with open(f"{url_cdn.split('/')[-1]}", "wb") as f: f.write(response.content)
|
用户浏览器加载 pdf 文件
前端 js 对用户行为进行限制, 包括:
- 监听并劫持键盘事件
- 定时发送 HeartBeat, 以实现限制单次访问时长的功能
- 检测到 Debugger 之后跳转到 ConsoleBan 页面
- 鼠标焦点离开页面后显示遮罩层
祭出我的 Tamper Monkey 脚本, 稍作适配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
(function() { 'use strict';
unsafeWindow.ConsoleBan = class { constructor() { console.log("[Bypass] ConsoleBan constructor neutralized"); } init() {} ban() {} fire() { console.warn("[Bypass] Blocked redirect attempt"); return false; } }; Object.defineProperty(unsafeWindow, 'ConsoleBan', { configurable: false, writable: false });
const nativeAddEventListener = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function(type, listener, options) { const blockedEvents = [ 'contextmenu', 'selectstart', 'visibilitychange', 'blur', 'mouseleave', 'mouseout' ]; if (blockedEvents.includes(type)) { console.log(`[Bypass] Blocked ${type} event binding`); return; } nativeAddEventListener.call(this, type, listener, options); };
GM_addStyle(` #mask-layer, .anti-shade, [class*="mask"] { display: none !important; opacity: 0 !important; pointer-events: none !important; } body > div:not(#viewerContainer) { filter: none !important; } `);
const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && ( node.id.includes('mask') || node.className.match(/shade|modal|overlay/) )) { node.remove(); console.log("[Bypass] Removed dynamic mask layer"); } }); }); }); observer.observe(document.body, { childList: true, subtree: true });
const timerStore = new Set(); const hijackedSetInterval = window.setInterval; window.setInterval = (fn, delay) => { const id = hijackedSetInterval(() => { try { if(!fn.toString().includes('debugger')) fn(); } catch(e) { console.error(e); } }, delay); timerStore.add(id); return id; };
setInterval(() => { timerStore.forEach(id => { clearInterval(id); clearTimeout(id); }); timerStore.clear(); }, 1000);
const nativeFetch = window.fetch; window.fetch = (url, options) => { if (/consoleBan|lunxun/.test(url)) { console.log(`[Bypass] Blocked request to ${url}`); return Promise.reject(new Error('Blocked by script')); } return nativeFetch(url, options); };
XMLHttpRequest.prototype.open = function(method, url) { if (/consoleBan|lunxun/.test(url)) { this._blocked = true; console.log(`[Bypass] Blocked XHR to ${url}`); } nativeOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(data) { if (this._blocked) return; nativeSend.call(this, data); };
document.addEventListener('DOMContentLoaded', () => { document.body.oncontextmenu = null; document.querySelectorAll('*').forEach(el => { el.oncontextmenu = null; el.style.pointerEvents = 'auto'; }); });
Object.defineProperty(unsafeWindow, 'console', { value: console, writable: false, configurable: false }); Object.defineProperty(unsafeWindow.console, 'log', { value: console.log, writable: false }); })();
|
至此, 除了需要密码访问和需要微信鉴权的文件之外, 已经完全结束了.
4 And more … ?
在尝试了密码加密和微信鉴权之后, 我发现了一些有趣的东西.
不过这些不打算写出来. 毕竟已经涉及到用户隐私了.
还是做一个提醒, 不要相信这些加密和微信鉴权.
另外, 这个平台的文件访问密码一定是 6 位纯数字的, 没有任何 captcha 机制, api 接口暴露且没有来源检测.