去年写了一篇 域前置 相关文章 原文 https://www.freebuf.com/articles/web/271046.html ,最后的 CDN厂商如何禁止域前置方面没有写清楚。最近又看了一下,补充一下nginx相关配置。
从一个例子入手# {#从一个例子入手}
有如下nginx配置:
server {
listen 443 default_server ssl;
ssl_reject_handshake on;
return 200 "ok$ssl_server_name$host$server_name" ;
}
server {
listen 443 ssl;
server_name www.* ;
ssl_certificate /etc/nginx/conf.d/a.cert;
ssl_certificate_key /etc/nginx/conf.d/a.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
return 200 "not ok$ssl_server_name$host$server_name";
}
有如下访问:
curl https://www.baidu.com:10443 --resolve www.baidu.com:10443:127.0.0.1 -k -H "host:baidu.com" -v
curl https://www.baidu.com:10443 --resolve www.baidu.com:10443:127.0.0.1 -k -H "host:baidu.com" -v
* Added www.baidu.com:10443:127.0.0.1 to DNS cache
* About to connect() to www.baidu.com port 10443 (#0)
* Trying 127.0.0.1...
* Connected to www.baidu.com (127.0.0.1) port 10443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate:
* subject: CN=www.baidu.cn,O="BeiJing Baidu Netcom Science Technology Co., Ltd",postalCode=,ST=Beijing,C=CN
* start date: Dec 03 10:10:33 2022 GMT
* expire date: Nov 30 10:10:33 2032 GMT
* common name: www.baidu.cn
* issuer: CN=www.baidu.cn,O="BeiJing Baidu Netcom Science Technology Co., Ltd",postalCode=,ST=Beijing,C=CN
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Accept: */*
> host:baidu.com
>
< HTTP/1.1 200 OK
< Server: nginx/1.22.0
< Date: Sat, 03 Dec 2022 11:34:51 GMT
< Content-Type: application/octet-stream
< Content-Length: 24
< Connection: keep-alive
<
* Connection #0 to host www.baidu.com left intact
okwww.baidu.combaidu.com
tls握手阶段使用了第二个server的配置,但最后访问到的依然是default_server
,因已经过了SSL握手阶段,也没有走到ssl_reject_handshake on
。
为什么会这样?# {#为什么会这样}
http://nginx.org/en/docs/http/server_names.html 中描述的相关匹配逻辑如下:
First, a connection is created in a default server context. Then, the server name can be determined in the following request processing stages, each involved in server configuration selection:
- during SSL handshake, in advance, according to SNI
- after processing the request line
- after processing the
Host
header field- if the server name was not determined after processing the request line or from the
Host
header field, nginx will use the empty name as the server name.
在ssl握手过程中,server_name字段来自于sni,也就是www.baidu.com
。在获取到header中的Host
后,server_name又重新赋值为了baidu.com
。正因为这里server_name取值的重复性,所以导致了域前置的产生。
解决方案# {#解决方案}
nginx 1.7.0 引入的变量 可以获取到 sni 的值,
$ssl_server_name returns the server name requested through [SNI](http://en.wikipedia.org/wiki/Server_Name_Indication) (1.7.0);
所以需要在每个server block里面添加以下逻辑:
if ($ssl_server_name != $host){
return 444; #具体返回什么无所谓,看具体业务安排。
}
完整配置如下:
server {
listen 80 default_server;
listen 443 default_server ssl;
ssl_reject_handshake on;
return 444;
}
server {
listen 80;
listen 443 ssl;
server_name www.domain.com ;
ssl_certificate /etc/nginx/conf.d/a.cert;
ssl_certificate_key /etc/nginx/conf.d/a.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
if ($ssl_server_name != $host){
return 444;
}
#巴拉巴拉
}
server {
listen 80;
listen 443 ssl;
server_name qqq.domain.com ;
ssl_certificate /etc/nginx/conf.d/a.cert;
ssl_certificate_key /etc/nginx/conf.d/a.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
if ($ssl_server_name != $host){
return 444;
}
#巴拉巴拉
}
设置default_server的同时也可以解决使用IP直接访问时导致的默认证书泄露问题。
设置以后有可能导致无sni的反向代理无法正常访问,需要为原有的proxy_pass
设置sni:
proxy_pass https://backend;
proxy_ssl_name $host;
proxy_ssl_server_name on;