【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,且没有暴露证书。不需要自己额外做配置了。