证书透明度CT揭秘:子域名如何暴露,攻击面如何收敛
很多做站的朋友都有过这样的经历:网站还在内测阶段,预发布用的子域名刚申请完证书没几天,居然就有人顺着域名摸到了你的后台页面,甚至拿到了开发环境的登录入口。第一反应是“内鬼泄露了DNS解析记录”,查了一圈却发现,问题出在一个叫证书透明度(Certificate Transparency,CT)的机制上。
这不是什么神秘的后门,而是现代HTTPS体系里一个公开的“登记账本”。只要你的域名签过一张公开受信任的SSL/TLS证书,哪怕网站还没上线、DNS还没正式对外解析,这个域名的信息就已经被写进CT日志,任何人都能查到。对安全测试人员来说,CT是子域名收集、攻击面探测的第一站;对站点管理员来说,它是“还没上线就被盯上”的元凶。这篇就围绕证书透明度CT展开,讲讲它为什么存在、怎么被利用、以及你该怎么自查和防守。
先说清楚,这里的CT跟医疗CT、工业CT没有半点关系。CT是Certificate Transparency的缩写,我们聊的是基于RFC 6962的一套公开证书审计体系。下面我从原理开始,一条一条把它拆明白。
1. 证书透明度CT到底是个啥:为什么它成了攻击者的“子域名情报库”
1.1 CT诞生的初衷:解决CA乱发证书的问题
很多人以为HTTPS证书是“加密”用的,其实证书的核心价值是“信任”。浏览器收到一个证书,它得确认这张证书确实是某个受信任的CA(证书颁发机构)签发的,然后才放心地在地址栏里显示小锁。这有点像你住酒店,前台只认工牌——工牌是真是假,取决于发行机构是否可信。
问题在于,CA也不是永远可信。2011年,荷兰的CA机构DigiNotar遭到入侵,攻击者冒用了Google等知名域名的证书,在伊朗地区实施中间人攻击。这件事之后,整个行业都在反思一个问题:CA可以随便签证书,签了多少、签给谁,外界一无所知,这是不是太危险了?
Google给出的答案是“透明化”:所有公开受信任的CA签发的证书,必须登记到一个公共日志里。这就是Certificate Transparency的由来。从2012年提出草案到后来的RFC 6962,再到Chrome浏览器强制校验SCT,这套机制用了几年时间把“CA乱签”这条路堵上了。
1.2 CT日志记录了什么:证书、域名、时间点,全都跑不掉
CT的核心机制并不复杂。CA在给域名example.com签发证书之前,先把证书的“预证书”(precertificate)提交给CT日志服务器。日志服务器收到后,会返回一条签了名的证明,叫SCT(Signed Certificate Timestamp)。CA拿到SCT之后,把它嵌进最终签发的证书里。浏览器在验证证书时,会花很短的时间去确认这个SCT是否有效。
关键在哪?CT日志是“只追加、不可删除”的。每一张提交过的证书记录,不管它是七天短命证书还是三年长期证书,不管它有没有过期、有没有被吊销,都会被永久保存在日志里。这就意味着,只要你为某个域名签过一张公开受信任的证书,这个域名的名字、签发时间、证书指纹、申请的CA,就全部成了公开数据。
用大白话讲,CT日志就像车管所的新车登记公告。你一买车,车牌号、车型、车主信息就被挂到了网上公示区。车还没开上路,但看公示的人已经知道你有这辆车了。攻击者只是把“看公示”这件事自动化了而已。
2. 攻击者怎么查到你的子域名:两条查询路径和一段能跑的脚本
2.1 证书搜索网站:输入域名,几秒钟看到全部关联子域名
现在公开的CT日志查询入口非常多,最常用的是crt.sh。这是由Let's Encrypt社区维护的一个查询平台,底层直接查CT日志数据库。用法很简单,在地址栏输入example.com,页面会把你这个域名下所有签发过的证书记录全部列出来,包括证书签给谁、包含哪些域名、证书有效期是从什么时候开始的。
这个“包含哪些域名”是核心。一张标准的SSL证书里,有一个字段叫SAN(Subject Alternative Name,主题备用名称),它记录了这张证书所有覆盖的域名。常见的做法是一个证书里塞几十个域名:www.example.com、mail.example.com、staging.example.com、delivery.example.com……只要证书签发过,SAN里的每一个域名都会被CT日志记录下来。
类似的查询平台还有Certificate Search、Google的透明度报告、Censys、SecurityTrails,它们背后连的都是同一套CT日志数据,区别只是界面和API封装的差异。实际操作里我会搭配两个用:crt.sh查历史全量数据,Certificate Search查最新签发记录和更好的结果准确性,因为crt.sh的数据库偶尔会抽风。
2.2 用Python拉取CT数据:5分钟搓一个子域名收集脚本
查询网站适合人肉浏览,但如果你要批量收集、定期监控,还是得调API。crt.sh提供了JSON接口,返回结构比较简单。下面这个脚本是我常用的,逻辑就四步:发起查询、解析返回、拼接子域名、去重输出。
注意几个细节。第一,name_value字段在多域名证书里往往是多行的,不能直接当成一个域名处理,一定要split('\n')。第二,通配符证书返回的可能是*.example.com,要先去星号再收集。第三,crt.sh的查询用的是SQL的模糊匹配逻辑,你搜example.com可能匹配到fake-example.com之类的相近域名,所以脚本里最后用line.endswith(domain)做一个后缀过滤,更稳妥。
如果想省事,也可以直接上现成工具。subfinder就是一个典型的子域名收集工具,它内置了几十个数据源,CT日志数据源是默认开启的。
指定-sources crtsh,certspotter的意思是只走证书透明度相关源,速度比全源扫描快很多,结果也基本能覆盖“通过CT暴露的子域名”。你只需要一个目标域名,它就能把证书日志里出现过的、仍然有效的域名记录全部拉下来。
2.3 为什么CT比CTF爆破更“致命”
传统的主机发现方式里,有一招叫DNS爆破,拿字典去猜:ftp.example.com、test.example.com、vpn.example.com……至少要猜中命名习惯才行。而CT根本不需要猜——证书的SAN字段本身就是事实清单。你主动申请过证书的每一个子域名,都等于亲手在公告栏上写了名字。
这就形成了一个不对称局面:防守方不知道哪些子域名被记录了,攻击方却能批量拉取全量数据。我做过不少次安全评估,几乎每次刚拿到目标域名,五分钟内就能在crt.sh上看到一堆staging、jenkins、gitlab、dev这类子域名,这些都是运维人员为了测试方便申请了正式证书的成果。它们往往比生产系统脆弱得多。
3. 还没上线就被发现:预发布阶段的三个“漏风”动作
3.1 为了测试环境“图省事”申请正式证书
回到最开始的场景:网站还没上线,子域名怎么就被发现了?绝大多数情况下,人肉排查最后都会落到同一个答案——测试环境签了正式证书。
想一想这个流程:开发完成了,测试环境搭好了,需要让QA同事通过HTTPS访问接口文档、业务后台。最省事的办法是什么?去申请一张公开信任的免费证书或者云厂商的一键签发证书,几十秒就装好了,浏览器不会再弹警告。问题恰恰出在这“省事”上——公开CA签发的证书,100%要进CT日志,因为现在的浏览器强制要求SCT,不走这步,证书就不被信任。
所以,你为staging.example.com申请证书的那一瞬间,不管你网站有没有上线、域名有没有对外解析、服务器有没有开防火墙,这个名字已经写进公共日志了。攻击者的自动化脚本每分钟都在扫CT日志的新增记录,你的子域名几乎是“出生即公开”。
3.2 通配符证书带来的“一颗核弹”
很多团队为了避免反复申请证书,直接搞一张*.example.com的通配符证书。通配符证书的CT日志记录里只会显示*.example.com,并不会暴露你实际创建了什么子域名,这一点确实有掩护作用。但反过来想,攻击者看到通配符证书的第一反应是什么?他会认为“这家站点子域名数量不小,业务模块多,可探测面大”,然后更用力地去扫DNS记录。
更麻烦的是,通配符证书一旦泄露,它覆盖的整个域下的所有服务都处于风险中。CT日志不会区分你是给测试环境还是生产环境签的,安全策略上也千万别把“测试环境不用保护”当真——测试环境往往密码更弱、补丁更旧、客户端信任级别更高。
3.3 攻击者拿到子域名之后会做什么
很多人的疑问是:就算被看到了子域名,他打不进来不也白搭?那你就低估了这条线索的价值。攻击者拿到一串子域名后,会先做存活探测——你看CT日志发现internal.example.com,随后他会解析这个域名,发现它真的能解析到一个内网似的Web系统,那么下一步就是指纹识别、漏洞探测、弱口令尝试,几乎所有代价极低的自动化攻击都可以跑起来了。
真正的危险在于,不少预发布环境出于联调需求,会和内网互通,甚至配置了比生产环境更高的权限。做过攻防演练的人都有体会:很多时候,突破口不是那个布置了三层WAF的官网,反而是某个只做了基础鉴权的测试接口。CT日志就这样把原本藏在墙后面的测试入口,直接摆在了攻击者面前。
4. 把CT变成自查工具:三种监控方案和一块排查清单
4.1 方案一:零成本手动查询,定期用crt.sh自查
对个人站长或小团队来说,最简单的防守就是“知己知彼”。每两周打开crt.sh,输入自己的主域名,扫一眼返回的列表里有没有“不认识”的子域名。
判断记录是不是自己人签的,有一套快速办法:核对证书里的签发CA,看是不是云厂商、Let's Encrypt或者企业证书供应商;比对签发日期,看那天有没有人申请过证书;再看SAN里的域名,有没有和现有业务命名规则明显不符的。如果三者都对不上,那就要追溯一下,是不是某个离职员工申请过但没说过,或者某位同事私下折腾过。
这里有个容易忽略的地方:crt.sh搜出来的是历史全量记录,很多域名可能早已废弃但记录还在。不要看了两张2019年的证书就紧张,重点盯最近30天的新增记录即可。
4.2 方案二:脚本轮询 + 告警通知,把监控周期缩到每天
手动查询只能防住“隔两周看一眼”级别的事。如果你的域名已经出现在攻击者的重点观察名单里了,可以把查询频率提升到每天甚至每小时。我的做法是写一个简单脚本,每天定时调用crt.sh或者certspotter的接口,把返回结果和前一天做对比,一旦出现新域名就推送到企业微信群或者邮件。
certspotter提供的公共API在监控场景里很好用,可以指定包含子域名:
返回的JSON里直接带dns_names数组,比crt.sh的接口更规整。结合crontab或者GitHub Actions,就能做到每天定时巡检。我自己跑了一段时间后发现,最频繁的告警不是来自恶意的证书签发,而是来自开发同事“临时申请了一张测试证书忘报备了”——监控的目的本来就是发现你没预期到的事,有告警就说明值得关注。
4.3 方案三:开源监控工具与企业级扫描
如果体系比较复杂、子域名数量上百个,可以考虑用开源工具做持续监控。比如Cloudflare开源过一个CT监控工具watchdoggles,它会持续监听证书透明度日志流(CertStream),当出现匹配你关注域名的新证书时,通过Webhook通知到你配置的渠道。这类基于实时日志流的监控方案,理论上可以做到“证书刚落日志,你就收到告警”,跑在离线扫描前面。
中大型企业更合适的做法是把CT监控纳入常规漏洞管理流程。每年做一次全面资产盘点,把CT日志查询结果和DNS解析记录、资产管理平台里的域名清单做交叉比对,找出那些“有证书但没人登记”的域名。这基本就是一次收敛攻击面的过程,产出可以直接交给运维删记录、加授权、补登记。
4.4 自查清单:上线前用这几个问题过滤一遍
我在做安全评估时总结了一个小清单,适合每次发布测试或预发布环境前对照自查:
| 问题 | 检查办法 | 风险等级 |
|---|---|---|
| 这个子域名必须配置公网证书吗? | 能否改用自建CA或者仅内网可用 | 高 |
| 申请证书时用的是什么邮箱? | 是否使用失效的个人邮箱 | 中 |
| 证书是否绑定了过多子域名? | 检查SAN字段是否最小化 | 中 |
| 测试域名是否可被公网解析? | 对域名做DNS解析检查 | 高 |
| 是否有人近期签发过未登记证书? | 拉取最近30天CT记录比对 | 高 |
这张表的价值不在于“全”,而在于逼着你在动手之前想清楚:我到底要不要让这个域名在全世界的日志服务器里留名。很多泄露,本质上不是技术问题,而是流程漏了一步“登记”的工夫。
5. CT相关的常见误区与几个避坑经验
5.1 误区一:自建CA证书不进CT日志,是不是可以随便用?
企业内部搭建的私有CA签发证书,确实不会出现在公共CT日志里,因为CT日志的准入门槛是“公开受信任”。但这里有个前提:私有CA的根证书必须被每个浏览器信任才行,否则访问时会提示“未受信任的颁发机构”。
实际操作中,这意味着如果你给测试环境配了私有CA证书,团队内部浏览器必须预先安装根证书,否则照样看红屏。而且很多自动化测试工具、移动端App并不认你私有CA,联调时会出一堆莫名其妙的证书报错。所以“自建CA防CT暴露”这个思路,只适合小规模内网验证环境,别想着拿它替代正式证书,也不要在公开业务上走这条路,否则会让客户端信任模型变得一团糟。
5.2 误区二:证书过期了、被吊销了,CT日志里就查不到?
不。CT日志的设计原则是只追加、不删除,历史记录永久保留。哪怕你那张证书签发之后第二天就吊销了,CT日志里依然留着完整记录。
这个特性带来一个实际影响:攻击者能通过CT日志看到你“历史上有过的域名”。假设有个旧平台已经下线几年了,技术上它已经不存在了,但CT记录还写着。安全测试人员看历史数据时,会特别留意这些旧域名是否仍然可以解析、是否还挂着旧版系统、是否还能通过域名猜出内部网络架构。我的建议是,对于已经废弃的业务域名,不仅要在DNS侧删除记录,还要留意服务端是否残留孤儿服务,避免“日志里查到、DNS里居然还能解析”这种尴尬状态。
5.3 查询时的稳定性问题与几条实用技巧
crt.sh虽然好用,但它的服务器负载长期比较“感人”,高峰时段查询经常会超时或返回错误。我在实际使用中总结了几条经验:
- 请求crt.sh时一定带User-Agent,并且控制请求频率,连续快速请求很容易被限流。
- 一次查询最好只查一个域名,不要写循环一次性拉几百个域名,大概率触发超时。
- 如果crt.sh挂了,优先换Certificate Search或者certspotter,不必死磕同一个源。
- 收集子域名时,不要把CT日志当唯一数据源,配合DNS历史解析记录、GitHub代码搜索一起看,才能拼出比较完整的攻击面。
还有一个容易被忽略的小细节:CT日志只能告诉你“这个证书里写了哪些域名”,但写了的域名不代表现在还能解析、更不代表还提供服务。看到一串子域名后,记得做一次DNS解析存活检查,把“历史记录”和“当前可用资产”区分开,这既避免误报,也避免在评估报告里写一堆没用的名称。
5.4 我在实操中最想强调的一点:暴露面是管理出来的,不是删出来的
做了这么多年评估,最深的感受是CT日志本身没有善恶,纯粹是一面镜子。你签证书,它就公示,天经地义。真正的问题永远是:你签证书的时候,是否清楚这个动作等于把子域名公之于众,是否清楚这个子域名的所有上线行为、历史遗留、安全等级都暴露给了所有想看的人。
我自己在评估里习惯一上来先拉一遍CT数据,把项目每个域名当天查到的记录存成快照,两周后再拉一次做对比。新增了什么、哪个测试域名冒出来了、哪张证书绑定了奇怪的域名,一眼就能看明白。这个过程我坚持了很多年,它帮我接住了不少“上线前一天才被发现的问题”。
证书透明度CT是一个早在2012年就被提出、如今已经嵌入浏览器信任链的机制。它让TLS证书签发这件事从黑盒变成了透明公开,同时也让每个互联网站长的资产暴露面比想象中更透明。你没法阻止别人看日志,但你可以通过规范证书申请流程、配置CT监控、缩短域名暴露窗口,把自己的攻击面收得小一点,再小一点。这不难,关键是意识到“签证书”不是一个技术动作,而是一次资产公开声明。