ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

自制防攻击浏览环境:从0到1构建最小攻击面浏览器

自制防攻击浏览环境:从0到1构建最小攻击面浏览器

1. 为什么把"防攻击浏览环境"当成一个独立项目来做

1.1 浏览器的攻击面到底有多广

很多人对浏览器安全的认知还停留在"不乱点链接就没事",但现实根本不是这么回事。浏览器几乎是每台电脑上暴露时间最长的程序,它同时干着三件事:下载并解析不可信代码、渲染不可信内容、保存你的身份凭据。这三件事叠加在一起,意味着只要打开一个恶意页面,就可能同时触发代码执行、数据窃取和会话劫持三类风险。

我见过太多所谓"裸奔"的浏览环境:默认开着一堆第三方插件、登录态长期不过期、缓存和指纹数据越攒越多、DNS解析完全裸奔、JavaScript对所有站点无条件放行。这等于把自家大门钥匙复制给了每一个网页访问者。恶意脚本能通过广告联盟、评论区、图片外链甚至看似无害的字体文件进入页面,再借助内核漏洞尝试提权。只要某一个环节失控,后面就是一连串连锁反应。

做这个自制防攻击浏览环境项目,就是为了把默认信任改为默认拒绝。既然浏览器本身不可能做到零漏洞,那我们要做的就是在它外面加一套可控制、可审计、可重置的安全约束,把攻击面压到最小,而不是指望某一个软件永远不出事。

1.2 自制不等于重复造轮子

"自制Web浏览器"这句话容易把人吓住,以为要从解析HTML标签开始写。实际上,现代浏览器引擎的复杂程度根本不是个人或小团队能复刻的。真正可行且值得做的,是在成熟开源内核的基础上,用一层自研外壳接管安全策略、网络入口、会话管理、代码执行开关和系统权限调用。我习惯把它理解成"给一匹马力很足的马装上全套缰绳和护具",马是现成的,但骑手能不能控制方向,决定权在你。

所以项目的从0到1路径是这样的:先选定一套可自由定制的开源内核作为渲染底座,然后通过外壳程序注入全局策略,在页面内容进入渲染流程之前就完成过滤和许可校验。所有敏感操作都由本地控制模块统一签名下发,浏览器自身不自作主张去加载额外组件。这套思路的好处是,不管内核哪天爆出新漏洞,只要策略层还生效,攻击者能实际利用的路径就少了好几条。

1.3 这个项目到底适合谁

我自己做这个项目的初衷,是为了处理一些风险偏高的访问需求:测试链接、打开来源不明的附件、临时登录第三方后台等。它并不打算替代日常娱乐浏览,但非常适合这几类人参考:

  • 平时需要频繁访问外部链接的安全测试和运维人员;
  • 对个人隐私数据敏感、不想被站点长期追踪的重度用户;
  • 需要在共享或公用设备上临时提供一个相对干净浏览环境的人;
  • 纯粹对浏览器安全机制好奇,想弄清楚JS开关、站点隔离、会话隔离到底能起到什么作用的学习者。

有一点必须先说清楚:没有哪个环境能保证100%防住所有攻击。这个项目追求的是把攻击成本抬高、攻击路径缩小、攻击后的损失降到可接受范围。把预期摆正了,后面每一步配置才有意义。

2. 项目环境与核心组件选型

2.1 内核选型:决定攻击面的底层因素

市面上你能接触到的浏览器内核,本质上就是两大体系:一脉由某个重量级商业浏览器开源分支演变而来,另一脉是传统的老牌开源渲染引擎。两者各有特点,但在这个项目里我更看重的是策略可注入能力和启动参数灵活度。如果你选的内核不支持在启动时明确指定数据目录、禁用自动更新、关闭后台推送,那后续所有安全控制都会打折扣。

实际选型时我的判断标准排序是这样的:

  1. 是否能完全离线运行,不偷偷连接开发商服务器;
  2. 是否支持命令行级策略覆盖,而不是每次用界面手动点;
  3. 是否允许为不同访问场景切换独立配置文件和缓存目录;
  4. 版本更新节奏和漏洞修复速度是否跟得上。

综合考虑后,我选了某开源内核系作为渲染底座,主要是因为它对自动化配置和运行沙箱的支持最完整。请注意,我特意强调"开源内核系"而不是"某个现成浏览器全家桶",因为商业浏览器往往会强行绑定账号系统、云同步、自动更新服务,这些功能在防攻击环境里都是额外的暴露面。

2.2 外壳容器:把内核装进可控沙箱

外壳容器是真正意义上"自制"的部分,它的职责包括:解析启动参数、加载策略文件、分配独立数据目录、限制文件读写范围、对接系统防火墙。你可以把外壳做成一个很薄的桌面入口程序,也可以直接做成一套命令行启动脚本。我建议先用脚本跑通流程,后面有需要再封装成图形界面。

外壳容器不要做得太重,更不要从这里引入自己的第三方依赖。我的原则是:所有涉及网络请求的环节必须经过策略模块;所有涉及本地写入的环节必须落在隔离目录里;所有涉及登录态的会话数据都必须是可一键销毁的。用MATERIALIZED话讲,外壳是门卫,它不是主人,但它有权限决定谁可以进。

2.3 策略注入与网络过滤双保险

策略注入解决的是"浏览器内部允许做什么"的问题,网络过滤解决的是"哪些流量允许进出"的问题。两者缺一不可。浏览器策略管的是JavaScript执行、弹窗、自动下载、通知权限这些动作;网络过滤管的是DNS解析、证书校验和连接目标白名单。

我在这层采用了本地DNS过滤服务加域名白名单黑名单的方式,所有DNS查询都走加密通道,防止本地网络中有人做域名劫持或流量嗅探。需要注意,这里的过滤配置不要跟"加速链路"混淆,它做的是减法而不是加法,核心目标是减少对外暴露,而不是增加什么额外功能。

2.4 项目目录规划参考

在一开始规划清楚目录,后面维护会轻松很多。下面是我这个项目的目录布局参考:

路径用途说明
内核发行包目录存放选定开源内核的固定版本,禁止自动更新
策略配置目录存放全局策略文件、域名黑白名单、脚本开关配置
独立数据目录每个浏览场景单独分配,保存会话、缓存、书签
临时交换目录用于下载文件的中转区,定期强制清理
日志审计目录记录访问行为、被拦截请求和异常事件,方便回溯

这种划分方式就像是给浏览器装了独立的储物柜,每个柜子用完就能整个搬走或者焚毁,不会和系统其他区域纠缠。

3. 从0到1搭建全流程

3.1 第一步:固定内核版本并做完整性校验

首先去开源内核官网下载固定版本,下载后不要急着运行。先记录下发布包的哈希值和签名信息,以后每次启动脚本都要校验一遍当前目录里的文件是否被替换过。这一步能防住供应链攻击——如果内核文件本身被植入恶意代码,后面所有策略都白搭。

我这里给出一段启动脚本的检查逻辑:

# 内核目录完整性校验 expected_hash="这里填写首次验证后的哈希值" actual_hash=$(sha256sum ./core/bin/runtime | awk '{print $1}') if [ "$actual_hash" != "$expected_hash" ]; then echo "内核文件校验失败,拒绝启动" exit 1 fi

校验这步看着笨重,但确实能挡掉一部分低级攻击。内核文件一旦被篡改,检测到哈希异常时系统会拒绝运行,攻击者想通过替换可执行文件来维持权限的目的就直接落空了。

3.2 第二步:创建全局安全策略文件

策略文件是整套环境的中枢。我采用简化配置格式来管理,启动时由外壳程序读入并逐条生效。参考内容如下:

# 核心执行策略 javascript.allow = false javascript.allow_hsts_only = false popups.allow = false modals.allow = false notifications.allow = false geolocation.allow = false camera.allow = false microphone.allow = false # 网络策略 network.https_only = true network.dns_over_https = true network.block_third_party_cookies = true network.allow_localhost = false # 存储策略 storage.cache_size_limit_mb = 50 storage.persistent_quota_mb = 0 storage.sync_disabled = true # 浏览行为策略 ui.autofill.enabled = false ui.suggestions.enabled = false ui.translation.enabled = false ui.telemetry.enabled = false

注意这里network.allow_localhost = false的意思是不允许网页脚本访问本机可能开放的调试端口或本地服务,防止恶意页面借浏览器做跳板扫描局域网。如果确实需要访问本地管理后台,应该单独开一个高信任配置场景,不要混用。

3.3 第三步:设定域名白名单与黑名单

策略文件管住了浏览器行为,但还管不住"用户到底能访问哪些域名"。这一步相当于给浏览器上了门禁。我维护了一份白名单列表,默认情况下所有域名都不放行,只有明确加入白名单的站点才允许打开。

# 白名单示例 allow: - domain: "可信站点A.example" reason: "日常办公入口" js: "restricted" third_party_cookie: false - domain: "某个需要完整功能的站点.example" reason: "必须登录后台" js: "allow" third_party_cookie: true session_duration: "30m" # 黑名单示例 block: - "*.ads.example" - "*.tracker.example" - "example-block列出的域名"

这里要强调一个细节:白名单站点本身也会被继续分级。大多数站点只给受限JS权限,只有极少数确实需要复杂交互的站点才开完整功能,并且严格控制会话有效期。你在白名单里看到"js: allow",其实已经是最顶格的授权了,绝不能把整份白名单都默认成这个级别。

3.4 第四步:配置隔离数据目录与自动清理

浏览器运行时的缓存、Cookie、LocalStorage都在数据目录里,如果不限制,这里面会越积越多,还成了攻击者提取用户行为画像和数据关联的富矿。这里有一个非常实用的做法:给数据目录挂到内存文件系统上,重启即清空。

# 把会话目录挂到临时内存空间 mount -t tmpfs -o size=300M tmpfs ./session_data # 启动浏览器时指定该目录作为用户数据目录 ./core/bin/runtime --user-data-dir=./session_data \ --disable-sync \ --disable-breakpad \ --disable-component-update

参数中我特意禁用了崩溃报告和组件更新服务。崩溃报告会向外发送内存数据,组件更新则有可能绕过固定版本策略,这两个都必须断干净。

3.5 第五步:验证环境配置是否生效

配置写对了不等于真正生效。我最常用的验证方法是开着抓包工具和进程监控,用一组测试站点逐项检查策略状态:

  • 某个默认关闭JS的站点,应该能看到主文档加载但没有任何脚本请求;
  • 一个已知会挂第三方Cookie的站点,应该只能看到第一方请求;
  • 访问一个明文HTTP链接时,应该被强制跳转到HTTPS或直接拦截;
  • 手动打开开发者工具试图触发远程调试端口,应该被拒绝;
  • 关闭浏览器后检查数据目录,确认没有残留临时文件。

验证完成后别急着收工,把整个配置目录打包做一个基线存档,以后的每次配置变更都跟这个基线比对差异。这样一旦哪里出了状况,能立刻定位是哪条策略导致的。

4. 核心安全机制拆解

4.1 默认关闭JavaScript为什么是硬道理

JavaScript是整个浏览器安全模型里风险最高的一块。它在沙箱里运行,却能发起网络请求、操作DOM、读取Cookie、检测宿主指纹。历史上相当多的漏洞利用都是由某个JS特性或引擎漏洞触发。所以我在这套环境里对几乎所有站点默认禁JS,只有白名单中标注了"restricted"或"allow"的站点例外。

禁用JS会导致很多现代网站无法使用,这是最直接的代价。但你要分清楚"无法使用"和"无法安全使用"之间的关系。一个后台管理系统如果必须要JS才能完成表单提交,那就让它在受控会话里开JS,同时通过短时效会话和独立数据目录来对冲风险。而纯粹的内容阅读站、文档站、下载页,完全没有必要执行脚本,但很多用户根本不思考这一点。

我自己在测试中遇到过最典型的一个场景:一个看起来是银行官网的钓鱼页面,在禁用JS的情况下只剩空壳和表单,其真实诉求是加载远程混淆脚本做键盘记录。策略拦下JS后,页面所有违规行为都暴露出异常网络请求。这就是默认禁JS的防护价值——它把脚本层面攻击里的绝大部分直接挡在门外。

4.2 站点隔离与权限分级

站点隔离听起来高深,本质就是不要让A站点的恶意代码拿到B站点的数据和权限。在自制环境里,我通过两方面实现。一方面是内核自带的跨进程隔离机制,让每个标签页或每个站点域占用独立渲染进程,一个页面崩溃或沦陷不至于影响整个浏览器。另一方面是我自己在策略层做的场景隔离——把"日常查阅"、"登录后台"、"打开可疑链接"分成三个独立场景,每个场景配不同的白名单、数据目录和网络规则。

举个例子,日常查阅场景的目录里只存临时缓存,权限最小;登录后台场景的目录里有合法会话,但会记录所有按键和输入行为用于审计;可疑链接场景的目录甚至会被分配到一个一次性目录,浏览器关闭后整目录直接焚毁。这样一来,即使你不小心在某恶意页面里点击了下载,想通过持久化手段把自己粘在系统上,难度也会成倍增加。

4.3 HTTPS强制与证书校验

加密连接这件事很多人有个误区,以为地址栏带把锁就绝对安全。实际上,证书校验如果做得不严,中间人可以通过篡改证书链来解密流量。这套环境里我开了HTTPS-only模式,所有明文HTTP请求一律拦截或升级,同时把DNS解析切换到加密通道,避免在域名解析阶段被污染。

证书校验的加强有两个层面:一是禁止页面自行忽略证书错误,二是维护一份可信证书固定列表,允许特定域名只信任固定的证书指纹,防止攻击者伪造证书链条。

# 证书固定参考格式 [cert_pinning] trusted_domain="某后台系统的域名" pinned_hash="该域名的证书公钥哈希"

这个设置只适合你自己的受控域名,不要对全网域名做固定,否则证书更新策略很容易导致大批站点无法访问。我在实际项目中只对内部系统域名做了固定,对外网统一走标准证书校验。

4.4 数据残留与本地指纹清理

很多浏览器安全方案只防网络攻击,忽略了本地残留。Cookie删了,但缓存目录里还有缩略图;会话清了,但IndexedDB还有数据;甚至崩溃日志里都可能存在页面内容片段。攻击者一旦拿到本机权限,这些残留就是你隐私泄露的最佳线索。

我的清理策略分成三档。实时清理:每次页面关闭后立刻清除对应站点的Cookie和DOM存储。定期清理:每个场景关闭后清空对应目录、光缓存、DNS缓存、崩溃报告。深度清理:定时对整个用户数据目录做粉碎性覆写删除,然后在内存文件系统中重新建立空目录。不要手动一个个去删缓存文件,写一个清理脚本挂到场景退出事件上,才能做到无感且稳定。

5. 常见问题与排查实录

5.1 误伤问题:白名单站点无法登录

这是最常遇到的坑。某天我打开一个内部系统,页面一直提示"登录失败",其他机器上都正常。扒了半天开发者工具,发现登录接口本身一切正常,但从接口返回的会话Cookie一直被网络策略当成第三方Cookie拦截。原来这个站点用了一个独立的认证子域名,Cookie是从认证域名设置、再在业务域名下读取的,而我的策略把所有跨域名Cookie都一刀切了。

解决方式是在白名单配置里为这个站点单独明确允许Cookie范围,而不是整体放开第三方Cookie。注意看关键点:逐站点控制让你能只放行自家认证域,其他广告联盟和追踪器照样拦死。判断标准是"这个跨域请求是不是页面正常功能所必需的",如果只是某些行为追踪服务,那直接拦掉反而是优化。

5.2 问题:页面样式和字体全乱

禁用JS之后,很多页面会变成纯文字版,这在我的预期内。但有时候连排版都彻底乱了,这就涉及CSS加载的问题。某些CSS文件被当作跨域资源被拦截,或者字体库域名不在白名单里,导致渲染成品变成豆腐块。

解决方法是把CSS和字体资源单独加白,但这里有个诀窍,不要把所有CDN域名都放行,而是看页面实际加载了哪些域名,然后按照"最小可用"原则加白。我给自己的原则是:无功能干扰的静态资源可以放行,但所有JS、追踪、广告域名绝对不会出现在静态资源白名单里。这么处理后,虽然页面还是偏素,但阅读体验和安全性都得到了照顾。

5.3 问题:下载文件被静默拦截

有段时间我发现浏览器下载某些PDF时不时就没有任何提示地失败,排查了一圈才发现是文件类型扩展名判断的锅。策略里默认禁止直接执行可执行文件,同时也在拦截一部分被标记为高风险的文件类型,但有个别正常文件也被误伤。

解决办法是调整下载策略为"下载后先隔离校验,再转出到指定目录",而不是完全禁止下载。下载文件一律先落到中转目录,由本地脚本检查文件名后缀、文件头签名和大小是否匹配,确认安全后才复制到用户目录。这里面有个附加好处,所有进出文件都在本地过了一道签名识别,遇到明显伪造的伪装文件时能第一时间发现。

5.4 从一次攻防演练看配置失效

最值得警惕的不是配置写错,而是"你以为配置生效了,实际上它早就失效了"。我在一次内部演练中发现,攻击者拿到浏览器外壳所在目录的写权限后,往配置目录里放了一个同名覆盖文件,直接关闭了脚本拦截策略。因为我的启动脚本只校验了内核文件,没有校验策略配置文件的签名和哈希,导致策略级防护被轻松绕过。

这次教训让我把所有策略配置文件全部加上了签名逻辑。每次启动时先校验签名,任何没有经过本地私钥签名的策略变更都会被拒绝加载。配置文件本身也设置为只读和不可修改属性,普通页面脚本即使利用漏洞拿到了临时文件写权限,也无法对配置目录做持久改动。你要记住一个原则:安全环境里,凡是直接决定安全策略的文件,都必须有独立的完整性保护,否则整个项目就是"沙地上盖楼"。

6. 踩坑经验与进阶建议

6.1 长期维护比初始搭建更重要

这个项目我维护了很长时间,最大的感受是:一次性搭建完成并不难,难的是细水长流地更新维护。内核不更新,新漏洞拦不住;策略文件常年不审计,某些权限就悄悄从"临时放行"变成了"永久默认"。后来我给自己定了两条硬规矩:每四周检查一次内核版本更新和公开的曝露漏洞情报;每次新增白名单都必须写明理由和有效期,过期自动凭证失效,需要重新申请。

还有一个维护经验也很有价值:一定要保留完整的变更日志。单独一张表记录每次策略调整的原因、时间、审批人和影响范围。很多安全事件回溯时都需要查看某个白名单是什么时候加的、为什么加、是否已经成了攻击入口,没有日志的话,你会被自己几个月前的配置坑得很惨。

6.2 我自己总结的四条底线经验

  1. 不要在防攻击环境里登录高价值业务账号。就算做了会话隔离和短期清理,风险仍然存在,高价值账号永远要在更严格的环境里使用。
  2. 不要把临时为了测试开的JS权限忘了收回来。所有临时授权都要设自动过期机制,不要依赖自觉。
  3. 不要相信浏览器自带的所有密码管理功能。在这套环境里密码一律交给外部独立保管工具处理,浏览器本身不保存任何表单凭据。
  4. 不要做"一刀切"的所谓安全方案。不同站点需要不同权限组合,要建立一个更像交通灯的分类模型,而不是把所有站点统统拦死或统统放行。

6.3 这个项目还能继续扩展的方向

如果你已经把这套流程跑通了,可以继续尝试三个扩展方向。一是把浏览器渲染放进隔离容器里,通过容器网络策略进一步降低本机受攻击面。二是加一层行为监控,记录异常的网络请求频率和进程行为,用规则引擎做威胁告警。三是把手动白名单升级成半自动分类模型,让新域名的授权能基于威胁情报自动做初步决策。

安全环境没有终点。每当你认为自己已经堵住了所有口子,就会在实战中又发现一条意想不到的路径。但这个从0到1的过程本身是必要的——亲手搭过一遍安全策略的人,才能真正理解浏览器里哪些开关是在保护你,哪些只是在安慰你。这些理解,比任何一份"万能安全配置"都更值钱。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进