NGINX 禁止 IP 访问(非标准端口)
出于安全和隐私需求,我们需要禁止直接 IP 访问。
在没有做处理的情况下,如果你直接使用 IP 地址访问一个 https 端口,那么你会发现即使无法连接(未携带 SNI 头,NGINX 不知道你要链接到哪一个域),但 NGINX 还是返回了默认服务器的 SSL 证书,这样你的 IP 地址和你的域名就暴露了(SSL 证书中)。
假如你的站点在 CDN 后,你又部署了同一个证书,那么攻击者很可能通过网络空间测绘引擎(如:https://censys.io/)找到你的源站地址。
因为网络空间测绘引擎在扫描全网所有 IPv4 地址并记录下这些信息,理论上扫描全网 IPv6 难度很大,但也不是不可能。
因此为了避免网络空间测绘引擎扫描、运营商扫描等,我们倾向于在直接 IP 访问时不返回任何内容。
注意:当你为子域名创建一个证书时,同样会暴露该子域名的存在,可以在 crt.sh 等站点上查看到几乎所有证书。
配置
在 http{} 内的第一个位置添加:
server {
listen 80 default_server default reuseport fastopen=3;
listen 443 default_server default reuseport fastopen=3 ssl;
listen 443 default_server default reuseport quic;
listen [::]:80 default_server default reuseport ipv6only=on fastopen=3;
listen [::]:443 default_server default reuseport ipv6only=on fastopen=3 ssl;
listen [::]:443 default_server default reuseport ipv6only=on quic;
return 444;
ssl_reject_handshake on;
ssl_protocols TLSv1.2 TLSv1.3;
server_name _
"~^(?=(\b|\D))(((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))(?=(\b|\D))$"
"~^([\\da-fA-F]{1,4}:){7}([\\da-fA-F]{1,4})$"
"";
}
注:listen 的选项根据你实际需求调整,后续再 listen 同一个端口时不可再带 reuseport 等选项,只可以带 ssl 或 quic。
具体选项请参考:https://nginx.org/en/docs/http/ngx_http_core_module.html#listendefault
选项(在 0.8.21 版本重命名为 default_server
)实际上等同于 default_server
;
如果你在非标准端口上运行 https,你可能会发现,访问时会出现:
400 Bad Request
The plain HTTP request was sent to HTTPS port
这个页面的错误代码其实不是 400 而是 497,这是一个 NGINX 非标准代码。
我们并不想返回这个页面,所以要这样(添加其他的比如 80 443 随便什么端口都可以):
注意:ssl_reject_handshake
需要 NGINX 1.19.4 及以上版本。
server {
listen 8443 default_server default reuseport fastopen=3 ssl;
listen 8443 default_server default reuseport quic;
listen 8995 default_server default reuseport fastopen=3 ssl;
listen 8995 default_server default reuseport quic;
listen [::]:8443 default_server default reuseport ipv6only=on fastopen=3 ssl;
listen [::]:8443 default_server default reuseport ipv6only=on quic;
listen [::]:8995 default_server default reuseport ipv6only=on fastopen=3 ssl;
listen [::]:8995 default_server default reuseport ipv6only=on quic;
return 444;
ssl_reject_handshake on;
ssl_protocols TLSv1.2 TLSv1.3;
server_name _
"~^(?=(\b|\D))(((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))(?=(\b|\D))$"
"~^([\\da-fA-F]{1,4}:){7}([\\da-fA-F]{1,4})$"
"";
error_page 497 @close;
location @close {return 444;}
}
注:listen 的选项根据你实际需求调整,后续再 listen 同一个端口时不可再带 reuseport 等选项,只可以带 ssl 或 quic。
解释
return 444
成功请求就返回 444。444 是 NGINX 的非标准代码,直接关闭连接而不发送响应标头;但 444 仍然不能完全避免扫描,这是局限性;ssl_reject_handshake on
server{} 中的 SSL 握手将被拒绝。默认情况下即使主机头不匹配,NGINX 仍然会发送证书,这样你的域名就暴露了(1.19.4 及以上版本支持);ssl_protocols TLSv1.2 TLSv1.3
测试版本 1.23.2,如果不添加这个选项,那么无法启用 TLS v1.3;server_name _
"_" 其实就是一个普通的字符串,达到不匹配任何主机头的效果。server_name 正则表达式
这个正则表达式是多一层保险,匹配所有 IPv4 地址(不知道有没有用);server_name ""
根据官方文档,服务器名称被设置为一个空字符串 "",它将匹配没有 “Host” 标头字段的请求,从版本 0.8.48 开始可以省略;error_page 497 @close; location @close {return 444;}
如果错误页面是 497,就使用 @close 页面,然后 @close 页面定向重定向返回 444;
BUG
注意,从某个版本开始(已测试 NGINX 1.23.2 + OpenSSL 3.0.1 和 NGINX 1.23.2 + OpenSSL 1.1.1o),如果不在此 Server 块中添加
ssl_protocols TLSv1.2 TLSv1.3;
有可能造成其他 Server 块中即使设置了 TLSv1.3,仍然无法启用的问题。
更进一步
如果攻击者已经确定要攻击某个域名,那么他就可以将带着该域名的正常握手信息遍历所有 IPv4。
这种情况下,攻击者的访问和正常访问没有区别,我们很难对其进行处理。
也许唯一的办法就是躲在 CDN 后,并仅允许 CDN 的 IP 段访问我们的服务器。
你可以使用防火墙或 NGINX自带功能来实现:
location / {
allow 127.0.0.0/24;
allow 169.254.1.1/32;
deny all;
deny 192.168.1.0/24;
}
为什么?什么是 SNI?
我在这篇文章中进行了一些介绍 :D
https://wiki.pha.pub/link/376
No Comments