Chromium 页面权限设计深度解析:从普通网页到特权内置页面

张开发
2026/4/9 8:36:20 15 分钟阅读

分享文章

Chromium 页面权限设计深度解析:从普通网页到特权内置页面
引言权限是一切安全的基础Chromium 作为当今最复杂的多进程浏览器架构之一其安全性高度依赖于精细化的权限设计。不同类型的页面——普通网页、扩展程序、内置 WebUI——面临着完全不同的信任假设和安全需求。理解这些权限设计的差异不仅是修复 IPC 通信问题的关键更是深入理解 Chromium 安全架构的必经之路。一、三种页面的信任模型在深入技术细节之前我们先明确一个核心概念信任等级决定了权限范围。页面类型信任来源初始信任等级权限获取方式普通网页无互联网来源零信任运行时请求用户授权Extension 页面用户主动安装中等信任manifest.json 预声明WebUI 页面浏览器内置完全信任编译时静态绑定这个信任模型的差异直接决定了每种页面能做什么、不能做什么。二、普通网页最小权限原则的典范2.1 设计哲学默认不可信普通网页来自互联网可以是任何人编写的任意代码。Chromium 的设计假设是所有网页都可能是恶意的。基于这一假设普通网页被置于最严格的沙箱中只拥有完成基本浏览任务所需的最小权限集。2.2 权限矩阵text┌─────────────────────────────────────────────────────────────┐ │ 普通网页权限矩阵 │ ├─────────────────────────────────────────────────────────────┤ │ ✅ 无需授权即可做 │ │ • DOM 操作、CSS 样式控制 │ │ • 发送 HTTP/HTTPS 请求受同源策略限制 │ │ • Cookie、LocalStorage、SessionStorage │ │ • 定时器、动画、事件处理 │ ├─────────────────────────────────────────────────────────────┤ │ 必须获得用户授权 │ │ • 地理位置Geolocation API │ │ • 通知Notifications API │ │ • 摄像头/麦克风MediaDevices API │ │ • 剪贴板读取Clipboard API - read │ │ • 文件系统访问File System Access API │ ├─────────────────────────────────────────────────────────────┤ │ ❌ 完全无法访问 │ │ • 本地文件系统除用户明确选择上传的文件外 │ │ • 系统 API注册表、进程管理、硬件访问 │ │ • 其他标签页内容受同源策略限制 │ │ • 浏览器内部设置和扩展 API │ └─────────────────────────────────────────────────────────────┘2.3 沙箱实现机制Chromium 为普通网页实施了操作系统级别的沙箱Windows 平台cpp// 渲染进程运行在低完整性级别Low Integrity Level // 代码位置sandbox/win/src/process_mitigations.cc void ApplyProcessMitigationsToCurrentProcess() { // 设置进程为低完整性级别 SetTokenIntegrityLevel(token, LOW_INTEGRITY_LEVEL); // 限制窗口消息发送 SetProcessMitigationPolicy(ProcessSystemCallDisablePolicy, ...); // 禁用 Win32k 系统调用Win32k lockdown SetProcessMitigationPolicy(ProcessDisableWin32kSystemCalls, ...); }Linux 平台cpp// 使用 seccomp-bpf 限制系统调用 // 代码位置sandbox/linux/seccomp-bpf/sandbox_bpf.cc class RendererSandboxPolicy : public SandboxBPF::Policy { ResultExpr EvaluateSyscall(int sysno) const override { switch (sysno) { case __NR_read: case __NR_write: case __NR_mmap: return Allow(); // 允许必要的系统调用 default: return Trap(SandboxTrapHandler, nullptr); // 其他调用触发崩溃 } } };2.4 权限请求流程当网页需要敏感权限时会触发以下流程javascript// 网页代码 navigator.geolocation.getCurrentPosition(successCallback, errorCallback);text┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 网页 JS │───▶│ 渲染进程 │───▶│ 浏览器进程│───▶│ 用户 │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ 调用 API │ │ │ │──────────────▶│ │ │ │ │ IPC 请求 │ │ │ │───────────────▶│ │ │ │ │ 显示权限弹窗 │ │ │ │───────────────▶│ │ │ │ │ │ │ │ 用户允许/拒绝 │ │ │ │◀───────────────│ │ │ 授权结果返回 │ │ │ │◀───────────────│ │ │ 回调执行 │ │ │ │◀──────────────│ │ │关键设计授权决定由浏览器进程做出渲染进程无权自行批准任何敏感操作。2.5 权限撤销用户可随时通过站点信息面板点击地址栏左侧图标撤销已授予的权限cpp// 代码位置chrome/browser/permissions/permission_manager.cc void PermissionManager::RevokePermission( const PermissionRequestID id, const GURL requesting_origin, PermissionType permission) { // 清除存储的授权状态 permission_context-ResetPermission(requesting_origin, requesting_origin); // 通知渲染进程权限已失效 render_frame_host-GetRenderViewHost()-Send( new PermissionRevokedMsg(routing_id, permission_type)); }三、Extension 页面声明式权限模型3.1 设计哲学用户信任但需透明扩展程序Extension由用户主动安装因此获得了比普通网页更高的初始信任。但为防止恶意扩展Chromium 采用了声明式权限模型——扩展必须在安装前明确声明所需的所有权限。3.2 Manifest 权限声明json{ manifest_version: 3, name: 我的扩展, permissions: [ tabs, // 操作标签页 bookmarks, // 访问书签 storage, // 使用存储 notifications // 发送通知 ], host_permissions: [ https://*.google.com/*, // 访问 Google 服务 https://api.github.com/* // 访问 GitHub API ], optional_permissions: [ clipboardWrite // 可选权限运行时请求 ] }3.3 组件权限分级Extension 的不同组件拥有不同的权限等级text┌─────────────────────────────────────────────────────────────────┐ │ Extension 组件权限层级 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Background (Service Worker) - 最高权限 │ │ │ │ • 可访问所有已声明的 API │ │ │ │ • 无跨域限制针对 host_permissions 中的域名 │ │ │ │ • 可长期运行 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Popup / Options Page - 中等权限 │ │ │ │ • 可访问大部分 APIruntime、tabs、storage │ │ │ │ • 受跨域限制但仍比普通网页宽松 │ │ │ │ • 生命周期与窗口绑定 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Content Script - 受限权限 │ │ │ │ • 只能访问当前页面的 DOM │ │ │ │ • 有限的 Extension APIruntime.sendMessage 等 │ │ │ │ • 可与页面 JS 隔离执行 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘3.4 权限检查机制cpp// 代码位置extensions/browser/api/api_resource_manager.cc bool ExtensionAPICallChecker::IsPermissionGranted( const Extension* extension, const std::string api_name) { // 1. 检查扩展是否拥有该 API 的权限 if (!extension-permissions_data()-HasAPIPermission(api_name)) { return false; } // 2. 对于 host_permissions检查目标 URL 是否匹配 if (api_name tabs target_url.is_valid()) { if (!extension-permissions_data()-CanAccessPage(target_url)) { return false; } } // 3. 检查当前上下文background/popup/content_script if (!IsAPIAvailableInContext(api_name, current_context)) { return false; } return true; }3.5 安全边界内容脚本隔离Content Script 虽然注入到网页中但与网页 JavaScript 运行在隔离的 JavaScript 堆中javascript// 网页中的代码无法访问 Content Script 的对象 // 页面 JS console.log(typeof chrome); // undefined如果页面未注入 // Content Script console.log(typeof chrome); // object // 两者通过消息通信而非共享对象 // Content Script 发送消息 chrome.runtime.sendMessage({type: GET_DATA}); // 页面 JS 无法直接调用 Content Script 的函数四、WebUI 页面特权内置页面4.1 设计哲学浏览器控制面板WebUI 页面如chrome://settings、chrome://history是 Chromium 内置的可信管理界面。它们拥有最高权限可以访问浏览器核心服务。4.2 特权能力cpp// WebUI 页面的特权示例 class SettingsUI : public content::WebUIController { public: explicit SettingsUI(content::WebUI* web_ui) { // 特权1注册可直接修改浏览器设置的回调 web_ui-RegisterMessageCallback( setPref, base::BindRepeating(SettingsUI::HandleSetPref)); // 特权2主动执行任意 JavaScript web_ui-CallJavascriptFunctionUnsafe( updateUI, base::Value(untrusted data)); } private: void HandleSetPref(const base::Value::List args) { // 特权3直接访问 Profile 和 Preference 系统 Profile* profile Profile::FromWebUI(web_ui()); PrefService* prefs profile-GetPrefs(); prefs-Set(my.pref, args[0]); // 直接修改浏览器设置 // 特权4可访问浏览器内部服务 Browser* browser chrome::FindBrowserWithWebContents( web_ui()-GetWebContents()); browser-window()-Close(); // 关闭浏览器窗口 } };4.3 安全防护多层限制尽管 WebUI 拥有高权限Chromium 仍有多层防护机制第一层URL 白名单cpp// 代码位置chrome/common/webui_url_constants.cc // 只有注册的 WebUI 主机名才能获得特权绑定 const char kChromeUISettingsHost[] settings; const char kChromeUIHistoryHost[] history; const char kChromeUIExtensionsHost[] extensions; // 未注册的主机名无法获得 WebUI 绑定 // 例如 chrome://fake-page 只会加载普通网页第二层资源打包cpp// 所有 WebUI 资源编译时打包在 .pak 文件中 // 代码位置chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc void RegisterWebUIControllerFactory() { // 只有内置资源才能被加载 factory-AddWebUIController(settings::mojom::kUrl, SettingsUI::Create); // 无法从网络加载 WebUI 资源防止中间人攻击 }第三层C 后端验证cpp// 即使是 WebUI敏感操作仍需二次验证 void ClearBrowsingDataHandler::HandleClearData(const base::Value::List args) { // 检查用户是否登录防止访客模式清除主账户数据 if (!profile_-IsRegularProfile()) { RejectJavascriptCallback(args[0], base::Value(Operation not allowed)); return; } // 执行清除操作 browsing_data_remover-Remove(...); }4.4 为什么 WebUI 不使用 Extension IPC这是一个关键设计决策对比维度WebUI 机制Extension IPC 机制通信方式chrome.send()→WebUIControllerExtensionHostMsg_*→ExtensionTabHelper权限验证编译时绑定 C 后端验证运行时权限检查 manifest 验证适用场景浏览器内置管理界面用户安装的第三方扩展Helper 依赖无需挂载需要ExternalApisTabHelper等核心原因WebUI 和 Extension 是两个独立的安全边界混用会导致权限混乱和潜在的安全漏洞。五、实战你遇到的问题再分析5.1 问题现象在chrome://startup-guide/控制台执行代码期望触发ExternalApisTabHelper::OnMessageReceived但消息始终收不到。5.2 根因权限与通信机制不匹配text发送端期望 接收端实际 ┌─────────────────┐ ┌─────────────────┐ │ WebUI 页面 │ │ Extension IPC │ │ chrome://... │ │ 接收器 │ │ │ │ │ │ 发送 Extension │ │ 需要 Helper │ │ IPC 消息 │ ─────────▶ │ 挂载才能接收 │ └─────────────────┘ └─────────────────┘ │ ▼ ┌─────────────────┐ │ WebUI 页面没有 │ │ 挂载 Helper │ │ ❌ 消息被丢弃 │ └─────────────────┘问题本质chrome://startup-guide/是 WebUI 页面应使用chrome.send()通信ExternalApisTabHelper只为普通标签页和 Extension 页面自动挂载WebUI 页面的 WebContents 没有这个 HelperExtension IPC 消息没有接收者5.3 正确修复cpp// 方案 A使用 WebUI 原生机制✅ 推荐 class StartupGuideUI : public content::WebUIController { public: explicit StartupGuideUI(content::WebUI* web_ui) { web_ui-RegisterMessageCallback( setPref, base::BindRepeating(StartupGuideUI::HandleSetPref)); } }; // JS 端调用 chrome.send(setPref, [myPref, true]);为什么这是正确的因为 WebUI 页面的权限设计本就期望通过WebUIController进行通信而非 Extension IPC 通道。六、权限设计对比总结6.1 核心差异表维度普通网页Extension 页面WebUI 页面信任等级零信任用户授权信任编译时信任权限模型运行时按需请求声明式manifest静态绑定沙箱强度操作系统级沙箱Chrome API 限制最弱/无用户可撤销✅ 是✅ 是❌ 否内置系统调用限制严格限制约150个通过 API 间接访问无限制跨域策略同源策略 CORS可声明跨域权限无限制本地文件访问❌有限需声明✅ 资源文件适用场景普通网页浏览功能扩展浏览器管理界面6.2 选择指南什么时候用哪种通信机制text需要实现的功能 │ ├─ 普通网页与浏览器交互 │ └─ 使用 Permission API 用户授权弹窗 │ ├─ 扩展功能用户安装 │ ├─ 需要持续运行 → Background Service Worker │ ├─ 需要 UI → Popup / Options Page │ └─ 需要注入网页 → Content Script │ └─ 通信方式Extension IPC │ └─ 浏览器内置功能chrome:// └─ 创建 WebUIController 子类 └─ 通信方式chrome.send() / RegisterMessageCallback七、安全启示Chromium 的页面权限设计遵循几个重要原则纵深防御即使一种防护被突破其他防护仍然生效最小权限每个组件只获得完成任务所需的最小权限权限分离不同类型的页面运行在不同隔离级别用户透明权限请求和授予过程对用户清晰可见理解这些设计原则不仅能解决具体的开发问题更能帮助我们在自己的软件设计中做出更好的安全决策。

更多文章