【nginx】nginx 配置避免 IP 访问时证书暴露域名
本文转载自:https://zinglix.xyz/2021/10/04/nginx-ssl-reject-handshake/
原文
TL;DR
nginx 配置避免 IP 访问时证书暴露域名
利用 nginx 1.19.4
后的新特性 ssl_reject_handshake on;
,将其置于默认访问时配置中,IP 访问时会终止 TLS 握手,也就不会暴露域名了。
使用如下命令查看你的 nginx 版本
1 | # nginx -v |
细说
CDN 是建站时常用的工具,在自己的主机外面套一层 CDN 是常见操作,一般这样认为自己的主机就安全了,有人来攻击也会先到 CDN 服务器,攻击者根本无法获取到自己主机的 IP,但事实真的是这样吗?
我们先来看看一般配置后会出现什么问题。
1 | server { |
上面是一个很常用的 nginx 配置,HTTP 访问全部重定向到 HTTPS 的 443 端口上,没有配置过的域名返回 444 终止连接。
好了,现在尝试用 IP 和 HTTPS 访问你的网站,你应该能够看到预想中访问失败、证书无效等连接失败的提示。
但是!注意下浏览器左上角提示的不安全,点开查看证书信息,你就会发现你的域名其实随着证书发送了过来。此时如果你是攻击者,那么其实就可以知道该域名背后的源主机 IP 就是这个。
上图即为用 IP 访问后,依旧能看到证书内容。这是因为返回 444 是 HTTP 层面的事情,意味着到达这一步下层的 TLS 握手已经完成。证书不被信任是一回事,但说明已经拿到了服务器的证书。
CDN 确实避免了直接 DNS 查询暴露 IP 的问题,但攻击者通过扫描全网 IP,用上述方式依旧可以知道每个 IP 对应的域名是什么,这也是为什么很多站长用了 CDN 后并且反复更换 IP 却依旧被攻击者迅速找到 IP 的原因。
Censys 就一直在干这件事,全网扫描 IP 并找到其对应的域名
那该怎么办呢?
问题根源出在 client 在 TLS 握手时发送了 ClientHello 后,nginx 在 ServerHello 中带着含有域名的默认证书返回了,因为 nginx 期望可以完成握手,这可能可以算是 nginx 的一个缺陷。
如果你不熟悉 TLS 握手流程,那么可以看看 这篇文章
笨办法
既然 nginx 默认提供了带有域名的证书,那么想不暴露也很简单,提供一个不含有正确域名的证书即可。
nginx 设置中 HTTPS 访问如果没有设置证书,那么就会报错。但反正 IP 访问也不需要提供服务,那么直接自签一个 IP 证书,或者随便一个域名的证书都可。当然,如果能搞定合法的 IP 证书也不是不行。
搞定证书后,添加一个配置,让 IP 访问返回错误证书就完事了。
1 | server { |
好方法
这种方法还得自己搞个证书,如果服务器多每个都得这么搞也挺麻烦的,好在这个问题 nginx 这已经有了很完美的解决方案。
ClientHello 中是带着 SNI 的,所以其实握手阶段是可以知道访问的域名是否合法的,nginx 1.19.4 中添加了一个新的配置项 ssl_reject_handshake
用于拒绝握手,也就不会提供证书。
使用方法也很简单,将原本默认配置中的 return 444
替换成 ssl_reject_handshake on
即可。
1 | server { |
配置后,再尝试 IP 访问,会发现浏览器报了 ERR_SSL_UNRECOGNIZED_NAME_ALERT
的错误,也看不到证书信息,目标达成!
其实还没完
上述方法是通过 ClientHello 中的 SNI 确定访问是否合法的,那如果 SNI 就是正确的域名呢?
这种场景发生于攻击者已经确定要攻击某个域名,那么他就可以将带着该域名的握手信息遍历所有 IP,握手成功就找到,这样访问其实与正常访问并无区别,唯一解决方法就是白名单只允许 CDN 服务器访问。
例如攻击者用 hosts 直接硬写 IP,将域名强行指向某个 IP
或者用这种方式
curl https://example.com --resolve 'example.com:443:172.17.54.18'
如下,在 nginx 里面的 location 配置添加上 allow 的 IP 段,只允许 CDN 运营商的 IP 访问你的服务,就能避免绕过 CDN 造成的攻击
1 | location / { |
上述 IP 段只能向 CDN 服务提供商询问,一般文档中都是有相关信息的。
慕雪的测试
上面这篇文章我第一次看的时候还没有理解他说的是什么内容。后来测试了一下,明白了。
说明
以我的服务器举例,我是 centos 7.2
的服务器,直接用 yum 安装的 nginx,版本 nginx/1.20.1
,配置路径是 /etc/nginx
;
在默认情况下,你会有个 nginx.conf
,和 /etc/nginx/conf.d
里面的用户配置文件
1 | /etc/nginx |
在 nginx.conf
里面除了加载用户配置文件,还会有一个默认的 server,指向一个静态文件路径。
1 | # 用户配置文件 |
在我的服务器上,这个路径里面是如下内容
1 | [root@bt-7274:/etc/nginx]# ls /usr/share/nginx/html |
此时直接在浏览器访问你的 ip,会展示这个默认路径里面的 index.html
,是 centos 的一个介绍页面。
但是,这并不代表你当前没有解析到任何用户自定义文件!nginx 默认情况下会使用第一个用户自定义 conf 来作为 ip 访问的结果(这是因为对用户自定义 conf 的 include 是在 defualt server 之前的,你可以理解为用户自定义文件会像 C 语言的头文件一样在 nginx.conf
中被展开)
当前实际上是访问了 /etc/nginx/conf.d
里面按字典排序的第一个用户 conf 配置!
1 | /etc/nginx |
为什么在我这里依旧展示了默认的静态文件路径呢?是因为我的第一个配置文件 a.conf
中没有配置 location /
,全都是其他路径(比如 /a/
)的反代!所以 nginx 就往后采用了最末尾的 default server 里面提供的默认静态文件。
而浏览器链接左侧的红色不安全
就告诉我们,当前其实收到了一个 ssl 证书,这便是上面原文中提到的 ip访问会因为ssl证书泄漏域名
的问题。
我们可以点击不安全
提示,再点击右上角那个带徽章的小按钮,查看当前收到的证书
如下图,当前收到的这证书,正是我的 /etc/nginx/conf.d
中第一个用户配置里面的 ssl 证书;内部包含了该证书对应的域名,我们的域名因此泄漏!
思路回顾
再来缕一缕思路
- nginx 会先加载用户配置文件,末尾才是默认指向
/usr/share/nginx/html
静态路径的配置 - 当你使用 ip 访问当前服务器,nginx 会给浏览器发送
/etc/nginx/conf.d
中按字典序排在第一位的用户配置文件中的 ssl 证书(即上图所示证书) - 恶意访问人员可以通过遍历访问所有 IP 地址,当访问你的服务器 IP 地址时,他拿到一个 ssl 证书,其中包括了一个域名 A;
- 假设你的域名 A 是按
域名A->CDN->服务器IP
来进行解析的,此时恶意访问人员就通过这个 ssl 证书直接得到了域名A->服务器IP
或者CDN域名->服务器IP
的对应关系,完全绕过了 CDN 的服务器; - CDN 域名可以通过
ping 域名A
直接得到,同样也是得到了域名A->服务器IP
的映射关系,知道了你的源站 IP; - 此时他就可以通过修改 hosts 强制让
域名A
指向服务器IP
,绕过 CDN 直接攻击你的源站;
套了 CDN 还暴露源站 IP 肯定不是我们想要的结果,所以我们需要解决这个问题!
解决办法
在 /etc/nginx/conf.d
中直接添加一个 a.conf
,让其排序在字典序的第一位,里面写入如下内容,其中 server_name _
的含义是除了我们配置过的域名外的其他访问
1 | server { |
配置后直接重启 nginx,没有报错就是 ok 了
1 | [root@bt-7274:/etc/nginx/conf.d]# systemctl restart nginx |
此时直接访问就会报错 ssl 的 alert 了,但是 edge 中估计是因为缓存的问题,依旧能看到证书
换火狐看一下,无法连接,没有证书,目标达成!
这里顺带贴一下火狐中一个正常 ssl 网站会显示成什么样子。如下是京东官网,在锁的按钮里面能看到证书的颁发者,而上图修改完毕配置文件后的测试中没有看到证书颁发者,即我们的证书并没有泄漏,目的达成。
另外,我试了试我另外一个服务器使用的 1panel 安装的 OpenResty,这个比较好,在默认情况下直接访问 IP 地址返回的是 404,且没有暴露证书。不需要自己额外做配置了。
- 最新
- 最热
- 最早
- 作者
点击重新获取 | 打开控制台