在构建 ProxyOrb 的过程中,我们在每一个设计决策上都面临一个根本性的悖论:网络代理的工作原理,本质上就是让浏览器误以为第三方内容是同源内容。这从定义上来说,就是在欺骗浏览器的核心安全模型。而现代浏览器在过去十五年里,恰恰在持续叠加越来越精密的防御机制,专门对抗这种源混淆行为。
这篇文章从第一性原理出发,正面审视这种内在张力。如果你是安全研究员、渗透测试工程师,或者希望真正搞懂浏览器安全与网络代理原理的开发者——不是停留在概念层面,而是落到 HTTP 头、Chromium 源码和生产工程取舍这个层面——那这篇文章正是为你准备的。
我们将依次覆盖:SOP 基础、URL 重写、CORS 跨域、CORB/CORP、COEP/COOP、Service Worker、iframe,以及这些机制对代理运营方和用户双方的安全影响。
1. 同源策略的基础
同源策略(SOP)由一个三元组定义:协议 + 主机 + 端口。只有三者完全一致,两个 URL 才算同源。https://example.com:443 和 http://example.com:80 指向同一台服务器,但属于跨域——这一点经常被忽略。
另一个常见的误解是 SOP 到底拦截什么、放行什么。SOP 不会拦截:
- 跨域加载图片(
<img src>) - 跨域加载脚本(
<script src>) - 跨域加载样式表(
<link rel="stylesheet">) - 跨域嵌入 iframe(尽管被嵌入文档的内容是隔离的)
- 跨域提交表单(
<form action>)
SOP 实际拦截的,是通过 fetch() 或 XMLHttpRequest 发起的跨域请求中读取响应的能力。请求照样发出去,网络往返照样发生,但响应体和响应头会被拦截,运行在不同源的 JavaScript 拿不到。
这个区别对 Web 代理至关重要。代理的全部工作就是把两个源(代理服务器和目标服务器)合并成一个,消除跨域边界。一旦所有资源看起来都来自 https://proxyorb.com,浏览器基于 SOP 的"读取拦截"就完全失效了——因为根本不存在跨域了。
Web 代理利用的"合法空间"正是 URL 重写:https://example.com/api/data 变成 https://proxyorb.com/api/data?__pot=aHR0cHM6Ly9leGFtcGxlLmNvbQ==。从浏览器视角看,这是一个同源请求。SOP 并没有被绕过——而是通过改变所有内容的表观来源,让它彻底失去了用武之地。
2. ProxyOrb 的 URL 重写架构
要理解安全影响,先得搞清楚编码机制。ProxyOrb 使用一个名为 __pot(proxy origin token,代理源令牌)的 URL 参数,其值是目标源的 Base64 编码。
__pot 参数
__pot 始终编码的是目标的源(协议 + 主机,不含路径),而非完整 URL。这意味着 https://example.com/some/deep/path?foo=bar 和 https://example.com/other/page 生成的 __pot 值完全相同:aHR0cHM6Ly9leGFtcGxlLmNvbQ==(即 https://example.com 的 Base64 编码)。真实的路径和查询字符串则原封不动地保留在代理 URL 自身的路径和查询中。
当用户访问 https://proxyorb.com/?__pot=aHR0cHM6Ly9leGFtcGxlLmNvbQ== 时,网关解码后重建原始 URL:
网关随后把请求转发给真实目标服务器,同时重写 Origin 头,让上游服务器看到的是自己的域名而非代理域名:
通过 Service Worker 在客户端重写 URL
网关负责服务端处理。客户端的工作——把 HTML、JavaScript、CSS 响应里的 URL 全部重写,使后续所有子请求都继续走代理——由 Service Worker 负责。
核心转换逻辑把页面内容中遇到的任何 URL 都转成代理格式:
举个例子,被代理页面中引用的 https://cdn.example.com/bundle.js 会变成 https://proxyorb.com/bundle.js?__pot=aHR0cHM6Ly9jZG4uZXhhbXBsZS5jb20=。
这套重写覆盖范围很全面:fetch()、XMLHttpRequest、<script src>、<img src>、WebSocket 连接、<link> 标签,一个都不放过。当页面内所有 URL 都指向代理源时,浏览器根本不会发出跨域请求——每个请求在构造上就已经是同源的了。
在没有完整导航的情况下恢复 __pot
有一个微妙的边界情况:来自被代理页面内部、但缺少 __pot 参数的请求——比如在 Service Worker 完成 URL 重写之前就发出的相对路径 fetch('/api/data'),或者由动态注入脚本发出的、绕过了 URL 重写器的请求。
Service Worker 通过级联查找来恢复缺失的令牌:
网关侧也有一条并行的恢复路径:如果请求到达时没有 __pot,但携带了一个 Referer 头,指向同源 URL 且该 URL 包含 __pot,网关就从 referer 中提取令牌并附加到当前请求上。这能处理页面自身的 JavaScript 在网关收到请求之前就把令牌从 URL 中剥掉的导航场景。
3. CORS:显式跨域权限系统
CORS(跨域资源共享)的设计初衷是让服务器通过发送特定响应头,主动选择开放跨域访问。从代理的角度看,CORS 带来了两个截然不同的问题。
问题一:CORS 预检拦截
当页面上的 JavaScript 发起携带非简单头(如 Authorization、Content-Type: application/json)的 fetch() 请求时,浏览器会先发一个 OPTIONS 预检请求。如果目标服务器的预检响应不包含宽松的 CORS 头,真实请求就会被拦截。
在 ProxyOrb 的架构里,Service Worker 会拦截所有 OPTIONS 请求,在请求到达网关之前就合成一个响应返回,解除对真实请求的拦截:
在网关层面,每个被代理的响应都会加上等效的 CORS 头:
问题二:携带凭证的请求
Access-Control-Allow-Credentials: true 配合非通配符的 Access-Control-Allow-Origin 这个组合很关键。CORS 规范要求:只要请求携带凭证,Allow-Origin 就必须是明确的源值,不能用 *。Service Worker 以 credentials: 'include' 发出所有出站请求,所以网关必须回显精确的请求源,而不能用通配符。
这意味着存储在 proxyorb.com 下的所有 Cookie——来自用户通过代理访问过的每一个目标站点——都会随每次代理请求一起发送。这是刻意为之(为了维持已登录站点的会话状态),但同时也是我们在第 8 节要讨论的重要安全考量。
CORS 头的"剥除-替换"模式
被代理的响应可能携带来自目标服务器的 CORS 头,但这些头从浏览器视角来看是错误的(它们引用的是目标源,不是代理源)。Service Worker 会剥除它们并替换成正确的:
4. CORB 与 CORP:Chromium 的更严格防线
CORS 依赖服务器显式选择开放。Chromium 额外加了两套默认激活的机制,与 CORS 配置无关。
跨域读取拦截(CORB)
CORB 在 Chrome 67(2018年)引入,作为 Spectre 缓解措施的一部分。核心洞察是:即使跨域响应的响应体从未被 JavaScript 读取,只要它被渲染进程取回并解码,就存在于该进程的地址空间里。Spectre 类攻击可以通过侧信道将其提取出来。
CORB 阻止某些跨域响应进入渲染进程。具体来说,当 <script> 或 <img> 标签获取一个跨域响应,且该响应的 Content-Type 是 text/html、text/xml 或 application/json 时,Chromium 会检查响应体(使用 CORB 规范 中定义的 MIME 嗅探器),如果类型匹配,就在渲染进程看到响应之前将其替换为空响应。
对 Web 代理而言,如果请求真的是跨域的,CORB 将是灾难性的——一个通过 <script> 标签获取的返回 application/json 的 API 端点会静默返回空。然而,由于 ProxyOrb 把所有 URL 都重写为同源,CORB 的跨域触发条件从未成立——浏览器从来不会看到跨域 JSON 响应,因为从它的视角,所有响应都来自 proxyorb.com。
CORB 有影响的唯一情况是 Service Worker 完全注册并开始拦截请求之前的过渡期。如果在那个时间窗口里有请求逃脱了 URL 重写,CORB 可能会拦截它。正是这个竞态条件促使 ProxyOrb 同时维护了一个主线程拦截层,在任何页面 JavaScript 运行之前同步地 patch XMLHttpRequest、fetch 和 DOM 属性设置器——作为 Service Worker 启动期间的兜底。
值得一提的是,CORB 已经部分被不透明响应拦截(ORB)所取代,Chromium 正在推进落地。ORB 在保持 Spectre 防护的前提下,细化了 CORB 规则以减少误判。对代理兼容性的影响类似。
跨域资源策略(CORP)
CORP 定义在 Fetch 规范中,允许服务器声明其资源只应被同源或同站点上下文加载:
当 Chromium 在跨域子资源请求的响应中遇到这个头时,会完全拦截响应。这比 CORB 更激进——它与内容类型无关,也无法通过 MIME 嗅探绕过。
同样,由于 ProxyOrb 的 URL 重写确保所有请求从浏览器视角来看都是同源的,来自目标服务器的 CORP 头不会触发拦截。网关也会防御性地从响应中剥除这些头,处理请求在未经 URL 重写的情况下溜过去的边界情况。
CORP 更深层的问题其实是代理架构有助于兼容性的一个场景:如果 https://api.example.com 和 https://www.example.com 都在被代理,它们都映射到同一个代理源,因此一方对另一方的 fetch 现在是真正的同源——CORP 限制在它们之间不再适用。
5. COEP 与 COOP:跨域隔离
从 Chrome 92(2021年)开始,访问 SharedArrayBuffer——以及由此延伸的、用于某些性能 API 的高精度计时器——被限制在已选择加入跨域隔离的页面中。这个选择加入需要两个响应头协同工作。
跨域嵌入者策略(COEP)
当一个页面发送这个头时,浏览器会强制要求该页面加载的每个子资源满足以下条件之一:
- 同源,或者
- 显式发送
Cross-Origin-Resource-Policy: cross-origin,或者 - 对携带凭证的 fetch 有宽松的 CORS 响应
对 Web 代理的麻烦在于:如果目标站点在其主文档上发送了 COEP: require-corp,且该文档通过代理提供时保留了这个头,浏览器现在就要求该页面加载的每个子资源也选择加入。而大多数子资源不会——这会导致一片网络错误。
跨域开放者策略(COOP)
COOP 控制页面是否可以与跨域页面共享浏览上下文组。设为 same-origin 时,跨域导航会打开一个新的浏览上下文组,断开 window.opener 引用和页面与任何跨域开启者之间的 postMessage 通道。
对代理而言,COOP 不像 COEP 那样立即造成破坏,但它仍然会破坏依赖跨域 postMessage 流程的站点——OAuth 弹窗流程是最典型的例子。
代理运营方的两难困境
这是我们在 ProxyOrb 面临的最难取舍:
方案 A:从响应中剥除 COEP 和 COOP。
- 优点:子资源加载正常工作;被代理页面不会应用这些限制。
- 缺点:我们在移除目标站点有意设置的安全头。如果该站点用跨域隔离来保护敏感数据不受 Spectre 类攻击,剥除这些头会重新暴露那种风险。
方案 B:保留 COEP 和 COOP。
- 优点:目标站点的安全意图得到尊重。
- 缺点:任何没有显式设置
CORP: cross-origin的子资源都会加载失败,大多数复杂 Web 应用会直接挂掉。
ProxyOrb 目前的策略是方案 A:网关从响应中剥除 Cross-Origin-Embedder-Policy 和 Cross-Origin-Opener-Policy。这样能最大化站点兼容性。这个安全取舍是经过深思熟虑的:使用 Web 代理的用户,本身就在接受这样一个事实——他们是在一个与内容预期部署环境不同的环境里运行内容。
每一个新的浏览器安全机制都遵循类似的模式:先以选择加入的形式引入(站点可以发送头来获得保护),然后逐渐成为新 API 的强制要求,最终考虑全面强制执行。COEP 走的就是这条路:Chrome 83 可选,Chrome 91 成为 SharedArrayBuffer 的必要条件。嵌入第三方内容但缺乏协调的站点发现自己的内容挂了。Web 代理的问题更严重,因为它们本来就是在调解第三方内容。
浏览器安全社区意识到了这个问题。新兴的 Document-Isolation-Policy 提案旨在将进程隔离与跨域隔离要求解耦,有望在不要求所有子资源选择加入的情况下实现 Spectre 缓解。等它落地,代理与隔离头的兼容性可能会改善。
6. Service Worker:浏览器端的信任层
Service Worker 不只是优化手段——它在架构上是 ProxyOrb 安全模型的核心。原因如下。
注册作用域绑定到源
Service Worker 注册时带有一个 scope,它始终在注册页面的源范围内。当 ProxyOrb 注册其 Service Worker 时,scope 是 https://proxyorb.com/,意味着它会拦截该源下任何页面的每一个 fetch。
这就是为什么同源 URL 重写能奏效的根本原因:一旦 SW 控制了一个客户端,来自该客户端的每一个网络请求——无论页面里的 JavaScript 尝试 fetch 什么 URL——都先经过 Service Worker。
拦截流水线
Service Worker 拦截请求后,会经过几个决策阶段:
透明头转发
一个微妙的挑战:浏览器限制 JavaScript 通过 Fetch API 设置某些"禁止"的请求头(Host、Origin、Referer 等)。Service Worker 的解决方案是把受限头编码进一个自定义的透传头里;网关解码后,在转发给目标服务器之前应用它们:
SW 作为受信中间方的安全含义
从安全角度看,Service Worker 占据着一个特权位置:它能检查、修改并伪造其作用域内任何页面发出的任何请求。对于合法代理用途,这正是它的全部价值所在。但对于恶意代理而言,这会是一个极其强大的攻击面。
这就是 Service Worker 脚本本身的可信度至关重要的原因。ProxyOrb 通过 HTTPS 从自己的源提供拦截脚本,并配以严格的缓存控制。如果攻击者能注入一个修改过的 Service Worker,就能拦截受影响用户的所有流量——不仅仅是代理流量,而是来自该源下任何页面的所有请求。
7. iframe:frame-ancestors 难题
iframe 代表着与普通子资源截然不同的挑战,因为它涉及一个嵌套的浏览上下文,有自己的导航、自己的 SOP 边界,以及自己的一套嵌入限制。
X-Frame-Options 与 frame-ancestors
两套机制可以阻止页面被嵌入 iframe:
传统方式: X-Frame-Options: DENY 或 X-Frame-Options: SAMEORIGIN
现代方式: Content-Security-Policy: frame-ancestors 'none' 或 frame-ancestors 'self'
当网关代理一个发送了上述头之一的目标页面时,该页面无法在代理源下被嵌入 iframe。因为 X-Frame-Options: SAMEORIGIN 引用的是目标源(example.com),而代理从 proxyorb.com 提供页面,浏览器的同源检查就会失败。
代理必须从被代理响应中剥除这些头,并替换成宽松版本的 CSP:
iframe 拦截与脚本注入
嵌入的 iframe 带来第二个问题:它们的内容从代理加载,但 iframe 的浏览上下文不会自动激活 Service Worker 或主线程拦截器。如果被代理页面创建了 <iframe src="https://widget.example.com/...">,那个 iframe URL 必须也被重写成代理格式,代理的拦截脚本必须注入到 iframe 的文档中。
iframe 拦截器在两个层面工作:
第一层——原型链 patch(捕获程序化创建的 iframe):
第二层——MutationObserver 兜底(捕获 DOM 插入的 iframe):
sandbox 属性问题
HTML 的 sandbox 属性限制 iframe 的能力:sandbox="allow-scripts allow-same-origin" 控制脚本执行、表单提交、跨域访问等。要让代理注入正常工作,iframe 需要能运行脚本并访问父级的代理上下文。
allow-same-origin 这个 token 尤其微妙:当它与 allow-scripts 同时存在时,会打败沙箱的源隔离——iframe 以嵌入页面的源运行。当它缺席时,iframe 获得一个唯一的不透明源,这会完全破坏代理脚本注入。
ProxyOrb 对 sandbox 属性的当前处理方式是在注入代理脚本前直接删除 sandbox 属性:
这是一个重大的安全取舍:原始站点有意放置沙箱来限制嵌入内容的能力。移除它会扩大那段内容能做的事情。这类决策对终端用户来说是不可见的,但实质性地影响着代理会话的安全姿态。
8. 代理运营方需要面对的安全问题
攻击面全景
用户通过 ProxyOrb 浏览时,有几类敏感数据流经代理的源:
会话 Cookie:由于所有目标站点都通过 proxyorb.com 代理,Cookie 存储在该源下。用户在银行、邮件、社交媒体的已认证会话,全都是单一域名下的 Cookie。Service Worker 的 credentials: 'include' 意味着这些 Cookie 随每次代理请求一起发送。
Referer 头:发送给上游服务器的 Referer 头会暴露用户正在查看什么页面。代理在转发给目标服务器之前会仔细剥除自身域名。如果请求来自 https://proxyorb.com/some/path?__pot=...,出站 Referer 头会被重建为原始目标 URL(https://example.com/some/path)——代理域名绝不会泄露给上游服务器。
JavaScript 执行上下文:通过代理提供的每个 JavaScript 文件都被修改(URL 重写),并在代理的源中执行。来自 evil.example.com 经由 ProxyOrb 代理的恶意脚本,运行在 proxyorb.com 源中,对该源的 localStorage、Cookie 和 IndexedDB 有完全访问权——其中包含用户通过代理访问过的所有其他目标站点的数据。
恶意代理的威胁模型
出于教育目的,有必要明确说明一个恶意代理可能做什么,而 ProxyOrb 刻意不做的事:
-
凭证窃取:恶意代理可以在请求体(包含密码和表单数据)流经网关时记录下来。
-
会话劫持:由于所有目标站点的 Cookie 都存储在代理域名下,恶意代理运营方可以在服务端通过网关访问这些 Cookie。
-
内容注入:代理必然会修改页面内容(注入 Service Worker、重写 URL)。恶意代理可以注入任意 JavaScript——键盘记录器、挖矿脚本、广告欺诈脚本——用户完全看不见。
-
SSL 剥除:如果代理将网关到用户这一段的 HTTPS 降级为 HTTP,所有流量都是明文。
ProxyOrb 全程使用 HTTPS,不记录请求体。任何 Web 代理的运营方都具备上述能力,这就是为什么选择值得信赖的代理运营方,和选择值得信赖的 VPN 提供商一样重要。
混合内容
现代浏览器强制执行混合内容拦截:HTTPS 页面不能加载 HTTP 子资源。ProxyOrb 通过强制网关的所有出站连接使用 HTTPS 来处理这个问题,即使目标 URL 使用 HTTP 也是如此。CSP 覆盖包含 upgrade-insecure-requests,指示浏览器在加载前把所有 HTTP 子资源 URL 升级到 HTTPS。
这通常是有益的,但可能会破坏刻意通过 HTTP 提供资源的站点(遗留 CDN、localhost 资源等)。
9. 持续的攻防博弈:结语
当我们开始构建 ProxyOrb 时,主要的兼容性挑战是 CORS 和 X-Frame-Options。此后,Chromium 陆续推出了 CORB、CORP、COEP、COOP,并在开发 Document-Isolation-Policy。发展方向很明确:浏览器正在对跨域边界执行越来越严格的强制,每一个新机制都需要代理基础设施去适配。
最重要的即将到来的挑战,很可能是 COEP 在主流 Web 应用中的全面落地。为了性能而使用 SharedArrayBuffer 的站点(视频编辑器、在线 IDE、协作工具)越来越多地设置 COEP: require-corp。随着 ProxyOrb 为维持兼容性而剥除这个头,需要 COEP 所启用的高性能特性的用户,会发现这些特性在代理环境中降级了。
对安全研究员而言,代理架构揭示了一件重要的事:同源策略不是防火墙,它是一个基于 URL 结构的信任模型。Web 代理利用的事实是:SOP 本质上无法区分"这两个资源真的是同源的"和"这两个资源被 URL 重写成了看起来同源的样子"。SOP 之上的整个浏览器安全栈——CORS、CORB、CORP、COEP、COOP——可以理解为一种尝试:添加比基于 URL 的源身份更健壮的防御。
理解这一点,对任何审计代理服务、设计浏览器安全策略,或评估可能通过代理访问的应用在真实世界中安全姿态的人来说,都是必不可少的。
常见问题
Web代理会绕过同源策略吗?
不完全是。Web 代理通过 URL 重写来工作:它把所有跨域 URL 转换成同源 URL,使 SOP 的跨域限制失去意义,而不是真正绕过它。从浏览器视角看,所有内容都来自代理自身的源,所以不存在跨域边界。这在架构上与绕过不同——SOP 仍然在执行其规则;代理只是把内容工程化成让这些规则永远不触发的形式。
CORS 如何影响 Web 代理服务器?
CORS 在两个层面影响代理服务器。第一,代理必须拦截 OPTIONS 预检请求并以宽松的 CORS 头响应,因为真实目标服务器的预检响应(引用目标源)无法满足浏览器对代理源的 CORS 检查。第二,代理必须剥除来自目标服务器响应的 CORS 头,并替换成引用代理源的头。Access-Control-Allow-Credentials: true 配合 Service Worker 中的 credentials: 'include',意味着代理源下的所有 Cookie 都随每次代理请求一起发送。
CORB 是什么,它对代理服务有什么影响?
跨域读取拦截(CORB)是 Chromium 的一种防御机制,当某些敏感跨域响应(HTML、JSON、XML)作为跨域子资源被加载时,阻止它们进入渲染进程。对于配置正确的 Web 代理,CORB 通常不会被触发,因为 URL 重写使所有请求都是同源的。但是,在 Service Worker 完全注册之前,某些请求可能逃脱 URL 重写而作为真正的跨域请求发出,此时 CORB 可能会拦截它们。CORB 作为 Spectre 缓解措施引入,定义在 WHATWG Fetch 规范中。
Web代理能访问 HttpOnly Cookie 吗?
不能。HttpOnly Cookie 由目标服务器设置,JavaScript 无法读取——包括运行在 Service Worker 中的 JavaScript。但是,网关在代表用户转发请求时会收到这些 Cookie,因为浏览器会在请求头中发送它们。HttpOnly 标志防止客户端 JavaScript 窃取,但不能防止代理服务器本身在传输过程中看到 Cookie 值。这类似于 HttpOnly 能防御 XSS,但无法防御服务端拦截。
从浏览器安全角度看,使用 Web 代理安全吗?
这取决于威胁模型。从浏览器视角,所有通过 Web 代理提供的内容都运行在代理的源中,这意味着某个被代理站点的 JavaScript 漏洞可能访问来自另一个被代理站点会话的数据(因为它们共享同一个源的 Cookie 存储和本地存储)。代理运营方也是一个受信任的第三方,能访问所有传输中的流量。对于在受限环境中访问非敏感内容,风险通常是可以接受的。对于携带敏感信息的已认证会话(银行、医疗、政务),用户应当理解:代理运营方在技术上有能力观察那些流量,应当只使用具备可审计无日志策略和严格运营安全实践的代理服务。
本文反映的是 ProxyOrb 在 2026 年初的架构状态。浏览器安全机制演进很快,建议读者参阅 WHATWG Fetch 标准、W3C 内容安全策略规范 和 Chromium 安全设计文档 以获取最新信息。
