服务器部署了nginx,nginx配置了多个不同域名的server,分别是www.example.com、static.example.com,还有一个 server_name _ 的默认server。
然后使用curl测试https的时候,发现在访问www.example.com域名时,nginx响应的ssl证书竟然是默认server的,这显然是不正确的,之后经过抓包发现了问题。
原因是nginx默认开启了SNI,如果客户端在ssl握手阶段未携带server_name就会去找默认server,然后使用默认server的ssl证书来响应,nginx没有可以匹配成功的server,只能去找默认server了。下面通过实战还原问题现象。
服务器nginx配置共两个server:默认server(server_name _)和www.example.com。
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_certificate "/etc/nginx/ssl/default_server_rsa.crt";
ssl_certificate_key "/etc/nginx/ssl/default_server_rsa.key";
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:P-256;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:HIGH:!NULL:!aNULL:!MD5:!3DES:!DES:!ADH:!RC4:!DH:!DHE:!EXP;
location / {
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate "/etc/nginx/ssl/www_server_rsa.crt";
ssl_certificate_key "/etc/nginx/ssl/www_server_rsa.key";
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:P-256;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:HIGH:!NULL:!aNULL:!MD5:!3DES:!DES:!ADH:!RC4:!DH:!DHE:!EXP;
location / {
}
}
首先看一下两个server的ssl证书内容,可以使用openssl命令查看,留意Subject参数。
default_server_rsa.crt的CN是Default Server,www_server_rsa.crt的CN是example.com。
root@k8s-master1:/etc/nginx/ssl# openssl x509 -text -noout -in default_server_rsa.crt
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
5b:75:14:0a:a5:f8:97:da:3b:eb:8d:09:e1:84:e5:67:cb:94:e2:42
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = CN, O = People's Republic of China, CN = Default CA
Validity
Not Before: Oct 29 07:22:33 2022 GMT
Not After : Oct 26 07:22:33 2032 GMT
Subject: C = CN, O = People's Republic of China, CN = Default Server
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:ee:aa:43:3b:44:08:f5:7e:62:35:f3:35:ac:ba:
...
16:41:6e:ba:06:9f:9e:d9:03:d1:27:42:33:28:af:
9c:91
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Non Repudiation, Key Encipherment
Signature Algorithm: sha256WithRSAEncryption
27:07:ad:67:18:e2:4d:9c:48:14:c8:eb:f5:6c:2b:a1:7a:95:
...
77:48:03:3c:e6:cc:1d:d2:e5:31:32:bf:4d:6e:a3:df:e3:89:
d7:e5:4e:cb
root@k8s-master1:/etc/nginx/ssl#
root@k8s-master1:/etc/nginx/ssl# openssl x509 -text -noout -in www_server_rsa.crt
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
2c:a4:0b:32:9d:ed:2c:e4:98:a3:44:79:dd:c8:8a:a1:5a:16:e0:12
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = CN, O = People's Republic of China, CN = China CA
Validity
Not Before: Oct 28 17:12:27 2022 GMT
Not After : Oct 25 17:12:27 2032 GMT
Subject: C = CN, O = People's Republic of China, CN = example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:bc:7f:3e:ac:aa:ab:c6:02:74:22:fd:6c:61:43:
...
02:38:16:66:69:9c:22:22:ac:96:f3:35:48:61:49:
7c:25
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:example.com, DNS:www.example.com
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Non Repudiation, Key Encipherment
Signature Algorithm: sha256WithRSAEncryption
3c:a9:80:3d:b0:7f:f7:c9:e7:eb:1c:06:bb:52:5b:57:cc:15:
...
96:5c:b4:0c:a5:99:30:26:98:6b:ea:16:c2:bb:88:88:a8:20:
9f:19:a5:d0
root@k8s-master1:/etc/nginx/ssl#
异常问题可以使用curl复现,发起两个请求,然后看一下有什么区别。
[root@centos ~]# curl -skvo /dev/null https://www.example.com -H "Host: www.example.com" --resolve www.example.com:443:192.168.3.47
* Added www.example.com:443:192.168.3.47 to DNS cache
* About to connect() to www.example.com port 443 (#0)
* Trying 192.168.3.47...
* Connected to www.example.com (192.168.3.47) port 443 (#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=example.com,O=People's Republic of China,C=CN
* start date: Oct 28 17:12:27 2022 GMT
* expire date: Oct 25 17:12:27 2032 GMT
* common name: example.com
* issuer: CN=China CA,O=People's Republic of China,C=CN
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Accept: */*
> Host: www.example.com
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 29 Oct 2022 11:09:33 GMT
< Content-Type: text/html
< Content-Length: 612
< Last-Modified: Tue, 21 Apr 2020 14:09:01 GMT
< Connection: keep-alive
< ETag: "5e9efe7d-264"
< Accept-Ranges: bytes
<
{ [data not shown]
* Connection #0 to host www.example.com left intact
[root@centos ~]#
[root@centos ~]#
[root@centos ~]# curl -skvo /dev/null https://192.168.3.47 -H "Host: www.example.com" --resolve www.example.com:443:192.168.3.47
* Added www.example.com:443:192.168.3.47 to DNS cache
* About to connect() to 192.168.3.47 port 443 (#0)
* Trying 192.168.3.47...
* Connected to 192.168.3.47 (192.168.3.47) port 443 (#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=Default Server,O=People's Republic of China,C=CN
* start date: Oct 29 07:22:33 2022 GMT
* expire date: Oct 26 07:22:33 2032 GMT
* common name: Default Server
* issuer: CN=Default CA,O=People's Republic of China,C=CN
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Accept: */*
> Host: www.example.com
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 29 Oct 2022 11:09:40 GMT
< Content-Type: text/html
< Content-Length: 612
< Last-Modified: Tue, 21 Apr 2020 14:09:01 GMT
< Connection: keep-alive
< ETag: "5e9efe7d-264"
< Accept-Ranges: bytes
<
{ [data not shown]
* Connection #0 to host 192.168.3.47 left intact
[root@centos ~]#
第一个请求响应的ssl证书是正确的,第二个请求响应的ssl证书是错的。注意看两个请求ssl握手期间的subject内容,第一个请求中的CN是example.com,这是正确的ssl证书,第二个请求中的CN是Default Server,这个是错的,也说明了访问www.example.com,nginx使用了默认server的ssl证书来响应。
从curl的返回看不出有什么异常,那么就只能通过抓包了。
通过抓包分析,发现第一个请求在ssl握手时客户端有携带server_name参数,而第二个请求则是未携带server_name参数,然后nginx就响应默认server的证书。
对于这种情况,客户端在发起请求时必须指定ssl的server_name参数,否则得到的证书就有可能是错误的,例如ssl双向认证也可能有异常。一般支持SNI功能的客户端都是可以配置server_name的。
问题解决之后,不禁感慨。怪不得在调试业务时老是出现问题,通过抓包才发现原来是这东西惹的祸。
另外如果用curl访问https请求,推荐用这条命令。
curl -skvo /dev/null https://www.example.com -H "Host: www.example.com" --resolve www.example.com:443:192.168.3.47
网友评论