SSL证书与ip关联探测

问题背景

SSL 证书本身不会直接 “泄露” IP,但服务器配置不当、证书公开日志、证书指纹匹配、SNI 探测这四类场景,会让攻击者通过证书信息反推出源站真实 IP,尤其在使用 CDN 隐藏源站时风险更高。

攻击者向任意公网 IP 的 443 端口发起 TLS 握手,服务器会返回 SSL 证书;证书里的CN/SAN 域名、指纹、颁发者可与目标域名关联,从而锁定该 IP 就是目标域名的源站。

四大泄露路径

  1. 直接 IP 访问返回证书
    场景:源站 443 端口开放,且未做默认站点隔离(Nginx/Apache 默认配置)。
    攻击手法:
1
2
# 向未知IP发起握手,获取证书
openssl s_client -connect 1.2.3.4:443 2>&1 | grep -E "subject=|issuer=|Verify"

结果:服务器返回默认站点证书,证书里有真实域名(CN/SAN)。攻击者批量扫 IP 段,就能建立 “IP ↔ 域名” 映射表。
典型:用 CDN 后,源站 IP 仍开放 443,且返回和 CDN 节点一样的证书 → 直接暴露。

  1. 证书透明日志(CT Logs)反查
    原理:Let’s Encrypt 等 CA 会公开所有签发证书(CT 日志),任何人可查。
    攻击手法:
    去 crt.sh、Censys、censys.io 搜目标域名,拿到证书详情(指纹、SAN、有效期)。
    再用证书指纹 / 公钥哈希,反向查哪些 IP 在使用这张证书。
    结果:直接得到源站 IP(因为源站必须部署证书才能提供 HTTPS)。

  2. 证书指纹 / 公钥匹配(绕过 CDN)
    原理:每张证书有唯一SHA256 指纹、公钥、序列号。
    攻击手法:
    先从 CDN 节点获取目标域名的证书指纹:

1
openssl s_client -connect example.com:443 | openssl x509 -noout -fingerprint -sha256

用 Censys/Shodan 等,搜索全球所有 IP 中,使用相同指纹 / 公钥的 443 端口。
结果:匹配到的 IP,大概率就是源站(CDN 节点通常用统一证书,与源站不同)。

  1. SNI 探测(精准定位)
    场景:源站做了默认站点隔离,但未拒绝带正确 SNI 的直接请求。
    攻击手法:
1
2
# 向IP发送带目标域名SNI的握手,看是否返回对应证书
openssl s_client -connect 1.2.3.4:443 -servername example.com 2>&1 | grep "subject="

结果:若返回 subject=example.com,则 1.2.3.4 就是源站 IP。

防御配置(Nginx 为主,必做)

  1. 拒绝 IP 直接访问(默认站点)
1
2
3
4
5
6
7
# 80端口:IP访问直接返回444(关闭连接)
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
1
2
3
4
5
6
7
8
9
# 443端口:拒绝所有未匹配SNI的握手(Nginx 1.19.4+)
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_reject_handshake on; # 核心:直接拒绝TLS握手,不返回任何证书
ssl_certificate /dev/null; # 占位,避免报错
ssl_certificate_key /dev/null;
}

效果:IP:443 直接被拒绝,不返回任何证书,无法被扫到。

  1. 源站仅允许 CDN IP 访问(防火墙 / 安全组)
1
2
3
# iptables 示例:仅允许CDN节点IP访问443
iptables -A INPUT -p tcp --dport 443 -s CDN_IP_RANGE -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j DROP

效果:即使知道源站 IP,非 CDN 请求也被防火墙拦截,无法建立连接。

  1. 源站与 CDN 使用不同证书
    做法:CDN 节点用一张证书,源站用另一张(不同指纹 / 公钥)。
    效果:攻击者无法通过证书指纹匹配找到源站。

  2. 关闭不必要端口、禁用泛域名解析
    源站只开放必要端口(如 443),不开放 80/22 等到公网。
    避免泛域名解析(*.example.com),防止子域名扫描泄露 IP。

接触DAT(DoubleArrayTrie)小记'

问题背景

最经典的字典树trie空间冗余太大了,设备只有64M内存实在顶不住,稍微学习了下其他字典树,DAT因为是第一个接触的,同时结构和理论很简洁,所以对它好感度较高,但这不意味着DAT是某种最优实现。

接触印象

优化冗余的一大方向就是去掉指针,将树结构映射成数组,DAT是此方向的佼佼者,DAT结构正如其名,将字典树映射成了两个数组base和check。去掉指针之后,DAT更加偏向一台记录状态的状态机,base负责记录当前状态转到下个状态的绝对数组下标,check负责记录状态转移是否有效。

其转移公式为t = base[p] + c, check[t] = p, 有部分实现将状态检查设计为 check[t] = base[p], 我还不能完全理解,可能在索引冲突时,在处理check数组时后者更加容易迁移数据。

关于DAT的一些优劣势

首先DAT的压缩率很高,base和check一个item各占用4byte,即使数组是稀疏数组,浪费的空间也极少。

DAT最擅长做的是精确匹配和前缀匹配,不擅长动态更新(实现复杂,涉及扩容和子孙数据迁移)和遍历数据(需要遍历所有可能状态)。

在构建DAT时,大部分时候还是使用静态构建,因为动态修改DAT非常容易造成索引冲突,解决冲突的实现和操作都较为复杂,同时需要注意,静态构建一般还是先构建成一颗传统字典树,然后按照排序构建DAT,因此程序的瞬时内存压力仍然存在,这方面应该存在优化方向。

自动化更新TLS证书

自动化更新TLS证书

问题背景

使用浏览器访问app域名时强制要求使用TLS加密,因此申请和域名绑定的TLS证书是必不可少的步骤。ZeroSSL,Let’s Encrypt都是免费提供证书的机构,但是申请的证书有效期仅仅只有90天,手工更新已经不太现实,定时自动化更新成为必然。

acme.sh工具实现了 acme 协议,可以从 ZeroSSL,Let’s Encrypt 等 CA 生成免费的证书,将程序运行在后台已经可以定时更新。但是现在运维部署已经全面容器化,可以直接定时启动容器更新证书,完成后释放容器,使用存储空间换取后台运行的cpu资源和内存还是划算的。

这里不详细介绍如何申请和更新证书,只专注自动化过程。

具体实现

首先需要一个有操作容器权限的账号,一个包含acme工具的镜像,创建完成的容器项目。在执行周期上,我选择在每月第一个平日的凌晨进行更新,因为在部署证书时nginx需要轮流重启

执行步骤

  • 启动容器
  • 续签证书
  • 部署证书
  • 清理容器

简单脚本示例,这里容器管理使用docker-compose,部署使用acme deploy-hook部署,实际按照集群场景具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#/bin/bash

LOG_FILE="/volume3/docker/acme/acme_renewal_$(date +'%Y-%m-%d_%H-%M-%S').log"

{
echo "=== ACME 证书自动续签脚本开始 ==="
echo "时间: $(date)"
echo ""

echo "[1/4] 启动 acme 容器..."
docker-compose -f /volume3/docker/acme/docker-compose.yml up -d
sleep 2

echo "[2/4] 续签证书 (pooi.app)..."
docker exec acme.sh acme.sh --renew --force -d pooi.app
sleep 2

echo "[3/4] 部署证书到 Synology (含通配符)..."
docker exec acme.sh acme.sh --insecure --deploy --deploy-hook synology_dsm -d pooi.app -d *.pooi.app
sleep 2

echo "[4/4] 停止 acme 容器..."
docker-compose -f /volume3/docker/acme/docker-compose.yml down

echo ""
echo "=== 脚本执行完毕 ==="
echo "时间: $(date)"
} >> "$LOG_FILE" 2>&1

echo "📝 日志已保存到: $LOG_FILE"
在windows上安装Rust

在windows上安装Rust

问题背景

在windows系统上安装Rust需要C++ 编译工具,正常使用Visual Studio安装工具将消耗大量空间和时间下载安装组件,这里提供另外一种安装流程,以最快速度在新电脑上成功安装并运行Rust代码

再次安装时发现一篇英文文档,描述如何安装 build tools 可以参考(windows-msvc)[https://rust-lang.github.io/rustup/installation/windows-msvc.html]

安装流程

  1. 使用vs_BuildTools.exe安装C++ build tools,选择安装单个组件,选择MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)和Windows 11 SDK (10.0.22621.0)
  2. Rust官网下载rustup-init.exe 进行安装
  3. 使用rustup show来验证安装的组件信息

最终效果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rustup show
Default host: x86_64-pc-windows-msvc


installed toolchains
--------------------
stable-x86_64-pc-windows-msvc (active, default)

active toolchain
----------------
name: stable-x86_64-pc-windows-msvc
active because: it's the default toolchain
installed targets:
x86_64-pc-windows-msvc

最后小小吐槽

本地编译rust代码需要一台高性能电脑,并且最好在编译的同时干些别的事 :)