隐藏服务到底是什么
隐藏服务是一台从不告诉任何人自己在哪里的服务器。它不会把地址写进 DNS 然后坐等连接,而是主动通过 Tor 网络向自己挑选的几个中继建立出站电路,把它们当作介绍点,再发布一份签名过的描述符,内容大致是“持有这把密钥的人,都可以通过这些中继联系到它”。知道地址的客户端会取回这份描述符,挑选第三个中继作为会合点,请求某个介绍点把见面请求转达过去。双方各自建立通往会合点的电路,谁都不会知道对方的 IP 地址,因为根本没有人告诉过它们。
真正有意思的是地址本身。一个 v3 .onion 地址由 56 个 base32 字符加上 .onion 组成,它并不是指向某把密钥的名字——它本身就是密钥:由一个 ed25519 公钥、一个校验和与一个版本字节编码而成。没有什么需要查询,也没有什么需要信任。客户端连接时取回的描述符,正是用这把密钥签名的,因此地址本身在结构上就完成了对服务的身份验证。整个过程不涉及任何证书颁发机构,没有人能为你的地址签发证书,也没有任何透明度日志记录这个服务的存在。想了解完整的握手过程,可以读一读会合规范。
| 普通网站会公开什么 | 谁能看到 | 隐藏服务改为公开什么 |
|---|---|---|
| 把域名映射到 IP 的 DNS 记录 | 任何人,永久可查,被动 DNS 还会留下历史记录 | 什么都没有——全程不涉及任何 DNS |
| 写明主机名的 TLS 证书 | 任何人,通过公开的证书透明度日志 | 什么都没有——地址本身就是密钥,不需要 CA |
| 在 443 端口应答的 IP 地址 | 任何持续扫描互联网的人 | 什么都没有——服务只发起出站电路,不监听任何公开端口 |
| 托管商与 ASN | 任何人,从地址就能查到 | 单凭地址推不出任何信息 |
| WHOIS 或注册商记录 | 任何人,而注册商会回应传票 | 什么都没有——没有注册,也没有注册商 |
上面这份清单就是隐藏服务的全部价值所在,也解释了为什么运行它的人形形色色。新闻编辑室的 SecureDrop 实例之所以做成隐藏服务,是因为线人不应该被迫信任 DNS。Debian 通过隐藏服务镜像自己的软件仓库,让更新软件包这个动作本身不会暴露成一个可见的目的地。好几家非常大型的消费级平台也发布了隐藏服务地址,好让身处审查网络中的用户在没有被封锁的 IP 掺和的情况下也能连到它们。这些都算不上什么稀奇事,它只是一种拥有不同保证的传输方式。
它能保护什么,又保护不了什么
协议给出的保证很窄,但很硬:网络层不会泄露服务运行在哪里。哪怕对手能监视互联网的大部分流量,也无法把你的地址解析到某台机器,因为根本没有解析这一步可供观察。托管商也不可能被拿着地址去问“这是哪个客户”,因为它那边根本无从知晓。扫描器找不到这个服务,因为它不在任何公开端口上应答。而那种能把普通网站从网络上冲垮的攻击,在这里根本无处瞄准——这也是为什么这个模式在DDoS 相关的讨论里出现的频率,不亚于隐私相关的讨论。
网络层之上的一切,依然要靠你自己不出错,而现实中出的问题也几乎全在这里。曾经有服务被定位,是因为应用打印出的堆栈跟踪里带着真实主机名;是因为同一台 Web 服务器在公网 IP 上应答了完全相同的页面;是因为运营者其他域名的证书装在了同一台机器上;是因为一张图片的元数据里带着 GPS 坐标;是因为这台机器发出的邮件经过了开放的互联网;也可能只是因为一个对外开放的状态接口,把服务器自己的地址列了出来。每一个案例里,Tor 都做好了自己的那部分工作,出问题的都是它之上的那个人。
所以要同时记住两件事。这套传输方式是可靠的,而且已经可靠了很多年;把它当成薄弱环节,是对这些案例真实经过的一种误读。传输层同时也是相对容易的那一半——难的那一半,是对这台机器所说、所做的每一件事都保持自律,而本文接下来大部分篇幅都在讲这个。如果你来这里是想弄清楚一台租来的服务器到底有多容易被追踪这个更大的问题,那篇文章给出的诚实答案正好可以和本文搭配着看。
把服务架起来:两条指令加一次重启
配置量小得让期待某种仪式感的人感到意外。先装好 tor 守护进程,再往 torrc 里加两条指令:一条 HiddenServiceDir,指定一个由 Tor 创建并拥有的目录;一条 HiddenServicePort,把调用者会用到的虚拟端口映射到你的应用正在监听的本地地址。重启之后,Tor 会生成一对 ed25519 密钥,并把你的地址写进该目录下的 hostname 文件。
| 指令 | 作用 | 漏写会出什么问题 |
|---|---|---|
HiddenServiceDir /var/lib/tor/site/ | Tor 保存密钥、写入地址的位置;创建时权限为 0700 | 属主不对,或者权限是全局可读,Tor 会直接拒绝启动 |
HiddenServicePort 80 127.0.0.1:8080 | 调用者请求 .onion 地址的 80 端口,Tor 转发给你的本地监听进程 | 如果指向公网地址,服务会在开放互联网上重新暴露出来 |
应用绑定到 127.0.0.1 | 只有 Tor 能连到它 | 同样的内容会在公网 IP 上应答,整套操作也就白做了 |
HiddenServiceVersion 3 | 写明版本,尽管 v3 已是唯一在用的版本 | 目前没有影响——v2 已在 2021 年从 Tor 中移除 |
最常见的失误就出在表格第三行。默认安装的 Web 服务器会监听 0.0.0.0,也就是公网 IPv4 地址,通常还包括 IPv6 地址。在它前面加一个隐藏服务之后,你其实是在两个地方提供了同一份内容,而其中一个会被互联网上每一个扫描器收录索引。只要有人比对页面哈希、favicon、ETag 或者某个特征鲜明的错误页面,几秒钟就能把两者关联起来。请只绑定到 127.0.0.1,然后用 ss -ltnp 确认一下,而不是想当然地认为没问题——确认只要五秒钟,而想当然却已经让人付出过惨痛代价。
在继续往下做之前,先把 hs_ed25519_secret_key 备份到一个加密过的、不在这台机器上的地方。这个文件不是与地址相关联,它就是地址本身。丢了它,地址就永久没了,无法恢复,也没有申诉的余地。泄露了它,别人就能架起一个应答你这个地址的服务,而且在密码学上和你毫无区别。把它当成签名密钥来对待——因为它本来就是。如果它所在的磁盘是租来的,给那块磁盘加密就是顺理成章的下一步,具体的注意事项那篇文章里都写了。
接下来,把前门彻底关上。隐藏服务完全不需要任何入站端口——它用到的每一条连接,都是自己主动发起、向外连进 Tor 网络的。你可以在防火墙上丢弃所有入站流量,服务照样正常工作。几乎没有别的东西能提供这个特性,而它也直接消灭了一整类以端口扫描开局的攻击。把自己的管理入口也放到 Tor 后面,这台机器就彻底停止回应互联网了。
v3 地址、定制前缀,以及它们带来的钓鱼问题
如果你看到的教程里提到 16 位字符的地址,说的是一个早已不存在的协议。v2 隐藏服务用的是 80 位的 RSA-1024 哈希,短到很讨喜,也弱到成了问题;而且它还是可枚举的,因为任何运行目录中继的人都能收集经过它的地址。Tor Project 在 2020 年公布了弃用时间表,在 2021 年 7 月的 0.4.6 系列中禁用了 v2,并在同年 10 月彻底移除了相关代码。v3 地址长 56 个字符,基于 ed25519 构建,也不会再通过目录中继泄露出去。
这个长度就是那份保证的代价,定制前缀地址也因此而存在。像 mkp224o 这样的工具会批量生成密钥对,只留下地址以指定前缀开头的那些。这不会削弱任何东西:你并没有在约束密钥,你只是在不断丢弃候选项,直到某一个恰好编码出你想要的字母,而留下来的那个和其他任何一个一样强。它消耗的是时间,而且代价是指数级的——每多一个字符,工作量就乘以 32,所以四个字符的前缀在笔记本电脑上瞬间就能跑出来,六七个字符要跑上几个小时,十个字符就成了一项正经工程。没有人会去暴力破解完整的 56 个字符——搜索空间之大,正是这套机制的意义所在。
真正的风险,恰恰是这个功能的镜像反面。如果一个好认的前缀能帮你的用户认出你,它同样能帮别人生成一个极其相似的地址,塞进一个钓鱼页面,因为人们实际会去核对的,往往只是自己能读出来的那八个字符。请像保护密钥指纹那样保护它:在几个互不相关的地方公开完整地址,用一把大家已经持有的密钥给它签名,如果你有明网站点,就把它放进 Onion-Location 响应头,并且明确说清楚:你绝不会只在社交媒体上宣布一个新地址。定制前缀是一个可用性特性,不是身份验证特性。
真正暴露过隐藏服务的,是这些泄露点
这一节才是真正要紧的部分。每一个公开记录在案的案例,模式都一样:网络层守住了,问题出在它之上的某个东西,指向了某台具体的机器、某个具体的人,或者某项具体的其他资产。这些问题大多平淡无奇,每一项都能在一个下午之内自查完毕,而“检查”本身就是要做的功课。
| 泄露点 | 别人怎么发现 | 怎么修 |
|---|---|---|
| 同一个应用在公网 IP 上也在应答 | 把两边都取回来,比对页面哈希、favicon、ETag 或特征鲜明的错误页面 | 只绑定 127.0.0.1;用 ss -ltnp 验证 |
| 公网地址上留着默认虚拟主机或 TLS 证书 | 互联网范围的扫描数据,可检索,按日期归档 | 公网接口上完全不监听;防火墙丢弃入站流量 |
| 服务器 banner 和框架错误页 | 读响应头,故意触发一个 500 | 屏蔽版本 banner,把调试用错误页换成静态页面 |
| 模板里写死的绝对 URL | 读 HTML:一个写死的明网主机名就够了 | 全站使用相对 URL,或使用能感知主机的 base URL |
| 第三方请求:统计分析、字体、头像、CDN | 加载页面,观察它到底去拉了些什么 | 所有资源自行托管;这里不该有任何可接受的第三方请求 |
| 图片元数据 | 读 EXIF 区块:相机序列号和 GPS 坐标 | 上传时在服务端剥离元数据,不留例外 |
| 对外开放的状态接口 | 请求 /server-status,读服务器对自己的描述 | 禁用它们,或者只绑定到回环接口 |
| 能识别主机身份的出站流量 | 离开这台机器的邮件、给自己打电话的监控探针、带着 API key 的定时任务 | 审计出站流量,该走 Tor 的都走 Tor,干脆不发邮件 |
| 从别的机器复用来的 SSH 主机密钥 | 按主机密钥指纹检索的扫描数据,会立刻把两者关联起来 | 每个实例用全新密钥;SSH 也通过隐藏服务本身来访问 |
| 时间戳与地区设置 | 日志时间戳、生成的文档,还有明显的工作时间规律 | 统一用 UTC 运行;不要让应用渲染出本地时区 |
其中两项特别值得强调,因为它们连细心的人也会栽进去。第一项是出站流量:一台从不接受连接的机器,照样可以靠主动发起连接来暴露自己。软件包更新没问题,也躲不掉;但一个把数据报告给和你名字绑在一起的仪表盘的监控探针就不行,一个从这台“本来就不该被定位”的机器上,经开放互联网发送密码重置邮件的应用也不行。第二项是 SSH 主机密钥。在多台机器上复用同一个密钥,是个看着省事的习惯,却会在两台本该毫无关联的服务器之间,留下一条永久的、可检索的密码学关联。
以旁观者的身份来做这次审计,而不是以运营者的身份。在一台从未接触过这个项目的机器上,用 Tor Browser 打开这个服务,读一遍每一个响应头,故意触发一次错误,去看一个你自己没写过的页面的源代码,再盯着网络面板,看看这个页面还从别的地方拉了什么东西。然后守在服务器这一边,完整观察它一整天都发出了什么。那一天里你查到的东西,就是本文最想说的重点。
客户端授权:只让特定的人能连上
有一种模式大多数人都不知道存在,而它恰恰是这套协议里最精巧的部分。启用 客户端授权后,你的服务发布的描述符会用一组你指定的 x25519 公钥加密。没有对应私钥的客户端连描述符都解不开,也就无从得知介绍点是谁,也就无法连接——甚至连“这里是不是真的有东西”都确认不了。你只需要把客户端的公钥,放进服务密钥旁边的 authorized_clients 目录,每个客户端一个文件,然后重启。
把这跟常见的访问限制方式比一比就清楚了。IP 白名单要求你知道用户具体在哪里,而且会让服务继续在互联网上应答,留给其他所有人去扫描、去打指纹。HTTP 基本认证会留一个登录框摆在那里,等于是在向外界宣布“这里有东西”。VPN 则要额外跑一整套系统,还带着自己的一个可以暴露出去的地址。客户端授权做的是把服务从不在名单上的所有人的视野里彻底移除——这是一种完全不同类别的属性:不是锁上一道门,而是让门根本不存在。
它天生适合用在管理面这类场景上。把公开站点放在一个普通的隐藏服务上,再把管理面板、监控仪表盘、数据库控制台和 SSH 入口分别放在各自独立的、需要授权才能访问的隐藏服务后面,那些原本会被持续探测的部署环节,就彻底找不到了。代价是密钥分发——每个客户端都要把自己的私钥装进各自的 Tor 配置——对少数几个运营者来说没问题,对面向公众的场景就不现实了。用在受众可数的地方。
延迟、冗余,以及必须诚实面对的取舍
一次隐藏服务连接要经过六个中继:客户端选三个,服务端选三个,在中间会合。这就是双方都不知道对方是谁所付出的代价,而它体现为延迟,不是带宽——首字节慢,之后的吞吐量通常没问题。围绕这一点来设计:减少往返次数,激进地做缓存,别搞一长串互相依赖的请求,也别让页面渲染前非要拉十一个子资源不可。一个在隐藏服务上体验还不错的站点,通常本来就是个搭得不错的站点。
如果你的服务本身并不需要位置匿名——比如一个大型公共平台,发布隐藏服务地址纯粹是为了让身处审查网络的用户也能连上它——Tor 提供了一种单向隐藏服务模式,在服务端只走一跳。它能把延迟大致砍半,同时也明确放弃了服务自身的匿名性,这一点配置指令的名字本身就写得明明白白。对于一个身份已知的机构来说,这是正确答案;但对任何把自己的位置当作保护对象的人来说,这恰恰是错误答案。要么刻意选择它,要么完全不用。
| 关注点 | 方案 | 代价 | 适用场景 |
|---|---|---|---|
| 延迟 | 标准六跳服务 | 首字节慢,吞吐量正常 | 始终适用,除非服务确实不需要匿名性 |
| 延迟 | 单向隐藏服务(服务端只走一跳) | 彻底放弃服务自身的位置匿名性 | 已知机构为受审查用户发布地址 |
| 冗余 | 跨多个后端使用 OnionBalance | 每个后端都要跑一个管理守护进程并处理密钥 | 任何在后端重建期间也必须保持在线的服务 |
| 从明网站点被发现 | Onion-Location 响应头 | 公开、刻意地把两者关联起来 | 两者被关联起来没问题,而且你希望获得这部分流量 |
| 限制受众 | 客户端授权 | 要给每个客户端分发密钥 | 管理面板、内部工具,受众数量可控 |
冗余这件事值得专门说一句,因为朴素的做法根本行不通。你不能简单地把密钥材料复制到第二台机器上然后两边同时跑——两个服务为同一个地址发布描述符,会在目录上互相打架,客户端落到哪一边完全不可预测。OnionBalance 存在的意义正是这个:一个前端实例持有公开地址,发布的描述符指向若干后端服务各自的介绍点,每个后端都有自己的密钥。后端可以一个一个地重建或迁移,地址本身始终不变。
让隐藏服务和一个普通站点并存
很多隐藏服务,其实是某个早已公开存在的东西多开的一道后门,这和一个位置本身就要保密的服务,是完全不同的两个项目。动手写任何配置之前,先想清楚自己在做哪一种,因为这两者想要的东西正好相反。如果目的是为一个大家本来就知道是你在运营的站点提供抗审查能力,那把两者关联起来正是这个功能的意义所在:在明网站点上发布 Onion-Location 响应头,Tor Browser 就会自动把隐藏服务地址推荐给访客。如果目的是不让任何人知道服务运行在哪里,那两者之间的每一条关联都是一处泄露,正确的数量是零。
真正害人的是各种“半吊子”做法。用一台机器、一个数据库、一个会话 cookie 域名和一套上传文件同时跑两边,同时告诉自己这两拨受众是分开的——一次配置失误就会让这层区分彻底崩掉,而且往往要等到别人发现了你才会知道。如果两者都必须存在、又必须互不关联,那就应该是两套部署、跑在两台机器上、用两套凭据,而这份运维上的额外成本,正是你想要的那个属性本身的代价。
如果你确实要把两者关联起来,有一个实操细节要注意:会话和 cookie 一定要按主机分别限定作用域。一个用户在明网站点登录之后,再从隐藏服务地址进来,应该是一个全新的会话,而不是共享的同一个——否则你等于搭了一套机制,能把这两次访问关联起来,让任何看得到其中一边的人都能顺藤摸瓜。另外,给隐藏服务地址配上它自己的规范链接,这样网站就不会“好心”地把特意避开明网主机的 Tor Browser 用户,又重定向回那个明网主机。
托管这一侧真正要做对的事
这里的要求相当特殊,这也是为什么一家什么都做的通用型主机商往往并不合适。你需要一条畅通无阻、能连进 Tor 网络的出站路径,因为这是你的服务唯一会发起的连接类型;有些服务商会直接过滤 Tor 的目录流量和中继流量,而你会以“服务永远发布不出描述符”的方式发现这一点。你还需要一家在可接受使用政策里白纸黑字写明 Tor 立场、而不是靠沉默带过的服务商,因为沉默往往会在某天有人投诉一件毫不相干的事情时,变成一封终止服务的邮件。你不需要固定 IP,不需要域名,不需要证书,也不需要任何入站端口——这意味着大多数主机商拿来当卖点的东西,在这里根本用不上。
投诉画像反而是个意外之喜。出口节点代表陌生人向开放互联网发起连接,因此会招来投诉——这是这门生意本身的代价,也是为什么出口节点需要一家立场写得清清楚楚的服务商。隐藏服务恰恰相反:每一条连接都是从 Tor 网络方向入站的,它从不代表任何人联系开放互联网,因此几乎不会产生任何滥用邮件。把它跟旁边的中继比,负载都要更“安静”,跟一个公开的 Web 服务器比就更不用说了。
在 BitVPS 上,Tor 白纸黑字被允许——中继、网桥、出口节点和隐藏服务一视同仁,出口节点的情形也写在了滥用页面上,而不是含糊带过。Tor 主机页面讲的是中继的规格选择;隐藏服务的负载要轻得多。实际选型上,一台 13.50 美元的 Growth,4 vCPU、8 GB 内存、不限流量上行,跑一个正经的隐藏服务外加它背后的应用绰绰有余;一台 8.50 美元的 Starter 拿来跑一个小型服务也够用。机房位置要按法律环境来选,而不是按延迟来选:六跳之后,机房离你近还是远,差别已经小到几乎感觉不出来。
网络页面公开了 ASN 和对等互联信息,这一点在这里之所以重要,是出于一个间接的原因。你并没有在暴露地址,所以通常那种要在意服务商网络质量的理由并不存在——但一家愿意公开写清楚自己基础设施的服务商,同样也是一家把“有人来问某个客户的情况时我们会怎么做”写清楚了的服务商,而这才是你真正要买的东西。
底线在哪里
这一点值得直说,因为这项技术背负的名声,和它实际承载的流量并不相符。隐藏服务是一种拥有特定隐私属性的传输方式,运行它的人绝大多数都再普通不过:接收爆料的新闻编辑室、不想记录“谁在什么时候拉取了哪次更新”的软件仓库、即时通讯和自建项目、身处普通互联网被过滤的国家的人,还有单纯不希望自己的管理界面能被扫描到的管理员。在绝大多数司法辖区运行一个隐藏服务都是合法的,这本身也说明不了你在托管什么内容。
它不是规则的例外。一家对版权投诉不予理会的服务商——我们正是如此,那篇文章讲得很清楚——照样会对儿童性虐待内容和可信的人身威胁采取行动,而且动作很快:这两类内容我们公开承诺的处理窗口是四小时,其余情况是四十八小时。这不是一份宽松政策里留的口子,这就是政策本身,任何传输方式都改变不了它。可接受使用政策并不长,在动手之前读一遍,比出事之后再读要划算得多。
还有一点也该说实话:匿名性是一个系统属性,不是一件能买来的商品。地址隐藏的是机器;它藏不住一条支付轨迹,藏不住一个复用的密码,藏不住你的写作风格,藏不住一个你多年前用同一个邮箱注册的域名,也藏不住一张带着你自己文件系统路径的截图。如果威胁模型是认真的,传输层反而是其中最容易的一部分,也是你该花最少时间的一部分。更完整的操作手册讲清楚了这条链路上剩下的部分,“子弹级托管”的解释也很适合用来解一解笼罩在这整个话题上的营销话术的毒。