信任一个明文传输的 [流量决策文件]{b="b" solid="solid"} ,本身就是信任链上最脆弱的一环
```——LeisureLinux
公司/家里的终端要上网,几乎都绕不开一个叫 [PAC]{b="b" solid="solid"} 的文件。它替浏览器和系统决定"哪些直连、哪些走代理"。但这个文件默认裸奔在 HTTP 上------被劫持、被篡改,就是一次教科书级的中间人攻击。
今天我把 [wpad PAC 服务器搬上了 HTTPS]{b="b" solid="solid"} ,用 Let\'s Encrypt 自动续期,还做到全程不把内网拓扑暴露给公网。这里有内网 DNS 和没有内网 DNS(家用路由器兜底)两条路都能落地,全过程复盘给你。
本文看点
01
内网域名 + 公信 CA 证书的两全解法
02
DNS-01 验证为何是纯内网域名的利器
03
acme.sh 自动续期实现永久无人值守
01
WHAT IS PAC
### PAC 是什么?为什么它必须安全?
[PAC(Proxy Auto-Config)]{b="b" solid="solid"} 是一段 JavaScript,浏览器通过 [FindProxyForURL(url, host)]{mono="mono" px="px"} 返回 DIRECT 或 PROXY 决定流量走向。它通常由内网一台超轻量 nginx 提供,域名就叫 [wpad]{b="b" solid="solid"} (配合自动发现协议,见第五章)。
风险点: PAC 是流量路径的"总开关" 。如果它被 MITM 篡改成 [PROXY attacker:8080]{mono="mono" px="px"} ,整个内网流量都会被导进攻击者的隧道------密码、cookie、内网页面全裸奔。更阴险的是,篡改可以只针对特定目标(比如把某域名改成 DIRECT 再抓包)。
「信任一个明文传输的信任决策文件,本身就是信任链上最脆弱的一环。」
02
THE DILEMMA
### 经典困境:内网要安全,但又不想暴露内网
给 PAC 上 TLS 并不难,难在两件事:
1
[证书从哪来?]{b="b" solid="solid"} 内网没有合法 CA,自签证书会让每台终端弹警告、加例外------运维噩梦,等于没上。
2
要不要把 wpad 绑到公网?完全不用。 [wpad.mydomain.com 是纯内网域名]{b="b" solid="solid"} :公网权威 DNS 上根本没有这条记录,只有内网 DNS 能解析到 192.168.x.x。
于是我的解法很干脆: **内网域名 + 公信 CA 证书,两全其美。**
[
域名只在内网解析]{px="px"}
公网看不到,天然"藏匿"。
[
DNS-01 签发公信证书]{px="px"}
绑定这个内网域名,终端验证通过、零警告。
[
PAC 文件只在内网可达]{px="px"}
文件确实明写着内网代理地址/IP/端口——"不暴露"靠的不是文件里没写,而是公网拿不到这份文件。
证明给 CA 看的是"域名控制权",喂给终端的是"内网地址"。两者并不冲突。
03
CERTIFICATE
### 证书怎么申请:acme.sh + aliyun DNS 插件
先装 [acme.sh]{b="b" solid="solid"} (会同时注册自动续签的 cron,见第六节):
```bash
curl https://get.acme.sh | sh -s email=you@example.com
接着准备 DNS 验证需要的认证信息。acme.sh 用云厂商的凭证来操作 DNS、自动写 TXT 记录。以阿里云为例:把 AccessKey ID 和 AccessKey Secret 填进去( [用只给 DNS 权限的最小化子账号更稳妥]{b="b" solid="solid"} ):
. . . bash
export Ali_Key=\"你的AccessKeyID\"
export Ali_Secret=\"你的AccessKeySecret\"
我本机是直接复用已配置好的 aliyun CLI 凭证,从 \~/.aliyun/config.json 里把 AK/Secret 提取出来喂给 acme.sh 的 dns_ali 插件------一个凭证两处用,不用再手工拷一遍密钥。存凭证推荐放 \~/.acme.sh/ali.env 并 chmod 600,别写死在脚本里。
然后申请, [关键是用 --dns dns_ali]{f97316="f97316" solid="solid"} (走 DNS-01 验证,不是 HTTP-01):
. . . bash
acme.sh --issue --dns dns_ali -d wpad.mydomain.com
dns_ali 插件会自动调阿里云 DNS API,在权威区加一条 [_acme-challenge.wpad.mydomain.com]{mono="mono" px="px"} 的 TXT 记录,等认证服务器验证通过后清理掉。全程不需要在公网放任何 A 记录,也不需要开 80/443 端口------ **这正是给纯内网域名签证书的关键。**
签发结果:ZeroSSL ECC DV 证书, [CN=wpad.mydomain.com]{b="b" solid="solid"} ,有效期 90 天,由 ARI(ACME Renewal Information,续期信息扩展,见文末名词解释)自动安排续签窗口。
为什么必须是 DNS-01?
[ HTTP-01]{px="px"}
需要 80 端口放验证文件、且要求该域名可从公网访问——内网域名根本做不到,直接用不了。
[ DNS-01]{px="px"}
只需在持有该域名的权威 DNS(公共权威区,如云厂商)加一条 TXT 记录——不需要 wpad 在公网有 A 记录,也不需要开任何端口。
一句话, [签证书只需要域名的 DNS 控制权]{b="b" solid="solid"} ,跟它是不是内网域名毫无冲突------这就是 DNS-01 的核心威力:能凭空为一条只在内网解析的域名签出合法公信证书。
04
NGINX CONFIG
Nginx 双面配置:http 兼容 + https 主服务
nginx 这里的"双面", [不是公网/内网两套域名,而是内网主域名的 http 与 https 两个入口]{b="b" solid="solid"} 。核心原则:内网主域名走 HTTPS 强制,内网短名 wpad / wpad.lan 走 HTTP 豁免------短名没证书,挂到 443 会因证书不匹配直接 握手失败 。
. . . nginx · 80
server {
listen 80;
server_name wpad wpad.lan wpad.mydomain.com;
location = /wpad.dat {
default_type application/x-ns-proxy-autoconfig;
charset utf-8; expires 30m;
}
location / { return 301 https://\$host\$request_uri; }
}
. . . nginx · 443
server {
listen 443 ssl;
server_name wpad.mydomain.com;
ssl_certificate /etc/nginx/ssl/wpad.crt;
ssl_certificate_key /etc/nginx/ssl/wpad.key;
location = /wpad.dat {
default_type application/x-ns-proxy-autoconfig;
}
}
那些 application/x-ns-proxy-autoconfig 是 PAC 的关键 MIME——类型不对很多客户端直接拒绝解析,这是最容易踩的坑。
05
AUTO DISCOVERY
wpad 是怎么被自动发现的?
终端拿到 PAC 有两种方式:
1
[显式配置]{b="b" solid="solid"} :在浏览器/系统代理设置里填 wpad.lan/wpad.dat 或 wpad.mydomain.com/wpad.dat。最可控。
2
[自动发现(WPAD 协议)]{b="b" solid="solid"} :终端按 DNS 后缀或 DHCP 选项自动去猜 PAC 地址。这也是为什么服务器名必须叫 wpad、文件必须叫 wpad.dat------协议是硬编码的,改名就失灵。
要让自动发现生效,还必须满足两个前提:
[ DHCP 下发搜索后缀]{px="px"}
下发默认 DNS 搜索后缀 mydomain.com,终端才会去猜 wpad.mydomain.com 这个完整域名。
[ 内网能解析 wpad 域名]{px="px"}
有自建 Unbound 加一条本地记录;没内网 DNS 时,在家用路由器的 DHCP 设置里写静态 DNS/Hosts 条目即可,路由器对内网统一下发。
三者咬合成一条链路: [DHCP 给搜索域 → 终端拼出 wpad 域名 → 内网解析到 192.168.x.x → 就近加密取 PAC]{b="b" solid="solid"} 。少一环,自动发现就断。
自动发现的具体握手细节可另开一篇。记住结论:名字、MIME、搜索域是 WPAD 的生命线,内网解析是它的地基。
06
AUTO RENEW
证书自动续期:装一次,永久自愈
acme.sh 安装时自动注册 cron,定时调用 [acme.sh --cron]{mono="mono" px="px"} 。它 [不是每次都申请]{b="b" solid="solid"} ,而是先检查距到期还剩多少天(默认提前 30 天,靠 ARI 定位精确窗口),没到就静默退出;到点才真正发起 DNS-01 申请,成功后自动安装:
acme.sh --install-cert -d wpad.mydomain.com \\
--key-file /etc/nginx/ssl/wpad.key \\
--fullchain-file /etc/nginx/ssl/wpad.crt \\
--reloadcmd \"systemctl reload nginx\"
--reloadcmd 是关键------ [续签成功那一刻自动 reload nginx]{b="b" solid="solid"} ,证书无缝切换,全程无人值守。cron 设成一天两次、工作日即可;以 90 天有效期算,一天一次都绰绰有余。别照抄"每 6 小时申请"的教程------那只是空跑检查,纯属浪费。
07
GLOSSARY
名词解释
[ PAC / wpad]{px="px"}
Proxy Auto-Config,一段 JavaScript,浏览器通过 FindProxyForURL(url, host) 决定每个域名是直连还是走代理。wpad 是这套协议约定俗成的服务器主机名。
[ MITM 中间人攻击]{px="px"}
攻击者插入在客户端与服务器之间,截获甚至篡改双向流量。明文传输的 PAC 一旦被 MITM,等于把整个代理路径交给攻击者。
[ DNS-01 验证]{px="px"}
ACME 的域名所有权验证方式,在权威 DNS 加一条 _acme-challenge.\<域名> 的 TXT 记录。不需要公网 A 记录,也不需开端口——给纯内网域名签证书的利器。
[ ARI 续期信息]{px="px"}
ACME Renewal Information,CA 通过它主动告知客户端精确的续期窗口。简单说:不用自己数日子,服务器会"喊"你回来续。
[ DNS 分离 / Split-Horizon]{px="px"}
同一域名在不同 DNS 视角下解析到不同地址。wpad.mydomain.com 只在内网解析到 192.168.x.x,公网权威区没有这条记录——既拿得到证书,又不暴露内网。
∞
EPILOGUE
写在最后
内网安全不是非黑即白: [既要可信,又不想暴露]{b="b" solid="solid"} 。HTTPS PAC 这套思路------纯内网域名 + DNS-01 公信证书 + PAC 敏感清单只在内网可达------在"信任"与"藏匿"之间找到了平衡。
「安全的内网,是被信任但读不穿的。」
END
我是 LeisureLinux,热衷于分享 Linux 底层架构与内网安全的实战观察。
如果你觉得今天这篇有收获,欢迎 点赞、在看、转发 三连,我们下篇见。
从动态挑战到声明式授权:ACME 协议的零信任演进与 DNS-PERSIST-01 实践 告别"不安全"警告!3分钟读懂 ACME 协议,让你的网站免费自动续期 Let\'s Encrypt 在交叉签名根证书事件后暂停证书签发 从 SNI 泄露到 ECH 全链路加密:OpenSSL 4.0 强制驱动的 DNS 架构演进 全球统一执行!2029年TLS证书有效期锁死47天 【首发】Cloudflare 高危 0-day 曝出:一个"隐形"路径,竟能让全球 WAF 防护瞬间失效!