/  

使用CDN后的源站IP泄漏解决方法

TL;DR

利用 NGINX 1.19.4 后的新特性 ssl_reject_handshake on;,将其置于默认访问时配置中,IP 访问时会终止 TLS 握手,也就不会暴露域名了。

细说

CDN 是建站时常用的工具,在自己的主机外面套一层 CDN 是常见操作,一般这样认为自己的主机就安全了,有人来攻击也会先到 CDN 服务器,攻击者根本无法获取到自己主机的 IP,但事实真的是这样吗?

我们先来看看一般配置后会出现什么问题。

server {
    listen 80;
    listen [::]:80;
    server_name example.com;

    location / {
        return 301 https://$server_name$request_uri;
    }
}

上面是一个很常用的 NGINX 配置,HTTP 访问全部重定向到 HTTPS 的 443 端口上,没有配置过的域名返回 444 终止连接。

好了,现在尝试用 IP 和 HTTPS 访问你的网站,你应该能够看到预想中访问失败、证书无效等连接失败的提示。

但是! 注意下浏览器左上角提示的不安全,点开查看证书信息,你就会发现你的域名其实随着证书发送了过来。此时如果你是攻击者,那么其实就可以知道该域名背后的源主机 IP 就是这个。

如果你 Nginx 上有这种配置,运行下面命令就可以获取你的域名了(假设你的IP是 1.2.3.4)

curl -v -k http://1.2.3.4

或者直接浏览器输入 http://1.2.3.4 也会跳转到你的域名。解决办法是,直接删除上面 80 端口配置,只保留 443 端口配置。也就是将 HTTPS 作为唯一的回源协议。

Nginx 上开启的端口越少,暴露的风险也就越低,这是显而易见的,就比如 Cloudflare 建站的用户,只需要开启 443 端口就够了。这里又有人问了,443 端口也可以用类似命令扫描呀!对,下面一条命令不止暴露域名,连证书都可以暴露:

curl -v -k https://1.2.3.4

curl -v -k https://1.2.3.4
* Rebuilt URL to: https://1.2.3.4/
*   Trying 1.2.3.4...
* TCP_NODELAY set
* Connected to 1.2.3.4 (1.2.3.4) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=example.com
*  start date: Nov 15 05:41:39 2019 GMT
*  expire date: Nov 14 05:41:39 2020 GMT
*  issuer: CN=example.com
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> GET / HTTP/1.1
> Host: 1.2.3.4
> User-Agent: curl/7.58.0
> Accept: */*
> 
* Empty reply from server
* Connection #0 to host 1.2.3.4 left intact
curl: (52) Empty reply from server

CDN 确实避免了直接 DNS 查询暴露 IP 的问题,但攻击者通过扫描全网 IP,用上述方式依旧可以知道每个 IP 对应的域名是什么,这也是为什么很多站长用了 CDN 后并且反复更换 IP 却依旧被攻击者迅速找到 IP 的原因。

Censys 就一直在干这件事,全网扫描 IP 并找到其对应的域名

那该怎么办呢?

问题根源出在 client 在 TLS 握手时发送了 ClientHello 后,NGINX 在 ServerHello 中带着含有域名的默认证书返回了,因为 NGINX 期望可以完成握手,这可能可以算是 NGINX 的一个缺陷。

#新添加的443端口块,如果使用了错误的 Hostname,SSL 握手会被拒绝
server {
    listen 443 ssl default_server;
    #如果有IPv6地址需加入下面这行,否则不用下面这行
    listen [::]:443 ssl default_server;
    ssl_reject_handshake on;
}

#常规的443端口,包含正确的域名和证书。对于携带正确 Hostname 的请求,服务器会继续做后续处理
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com;
    ssl_certificate example.com.crt;
    ssl_certificate_key example.com.key;
}

其实还没完

上述方法是通过 ClientHello 中的 SNI 确定访问是否合法的,那如果 SNI 就是正确的域名呢?

这种场景发生于攻击者已经确定要攻击某个域名,那么他就可以将带着该域名的握手信息遍历所有 IP,握手成功就找到,这样访问其实与正常访问并无区别,唯一解决方法就是白名单只允许 CDN 服务器访问。

例如 hosts 直接将硬写 IP,将域名强行指向某个 IP

或者用这种方式 curl https://example.com --resolve 'example.com:443:172.17.54.18'

1
2
3
4
5
location / {
    allow   172.1.2.0/24;
    allow   1.2.3.4/32;
    deny    all;
}

上述 IP 段只能向 CDN 提供商询问,一般文档中都是有相关信息的。


标题:使用CDN后的源站IP泄漏解决方法
作者:qbs
地址:https://www.xiaohongyan.cn/articles/2023/04/11/1681182511350.html
声明:博客上的所内容均可免费使用,可注明归属,注明作者或网址这种行为值得赞赏。