时间:2023-10-27 01:30:01 | 来源:网站运营
时间:2023-10-27 01:30:01 来源:网站运营
cdn怎么不暴露源站?:若是作为刚入门的网站管理员,你或许知道使用 CDN 来避免暴露源站 IP。同时,你或许会采取一些常用措施,诸如:修改源站的 Hostname,对非法请求不返回或返回无关/迷惑类信息。server { listen 443 ssl; server_name localhost; ssl_certificate /root/cert.pem; ssl_certificate_key /root/cert.key; location / { return 444; }}
然而证书信息依旧会被返回:curl -k https://127.0.0.1 -v* Trying 127.0.0.1:443...* TCP_NODELAY set* Connected to 127.0.0.1 (127.0.0.1) 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, Change cipher spec (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=127.0.0.1* start date: Feb 12 07:35:46 2020 GMT* expire date: Feb 13 07:35:46 2020 GMT* issuer: CN=127.0.0.1* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.> GET / HTTP/1.1> Host: 127.0.0.1> User-Agent: curl/7.58.0> Accept: */*> * Empty reply from server* Connection #0 to host 127.0.0.1 left intactcurl: (52) Empty reply from server
在开始回答前,首先要说明一下:这个回答我最初是以英文的形式发布在自己的博客中,同时也在 Stack Overflow 中做了存档。你可能会觉得这个回答有点“翻译腔”,但确实是原创回答,请不要误会。如果你觉得本回答对你有所帮助,请帮忙 Upvote 一下,感谢。另外,如果你需要完全地保护你的服务器 IP 不被泄露,仅仅是做我在本文中提到的部分是远远不够的。安全遵循短板理论[1]。任何疏忽都会导致不可预计的的损失。所以,你需要为自己的安全负责。我在此仅仅是介绍防止源站 IP 泄露的一些方法。如果出现诸如软件设计错误导致的 IP 泄露,本文并不能帮你修正那些错误。
apt-get install iptables-persistent
return 444;
,并且这一小段中的其它部分你可以直接跳过,或仅仅时略读即可。server { # 如果使用了错误的 Hostname,SSL 握手会被拒绝 listen 443 ssl; ssl_reject_handshake on;}server { # 对于携带正确 Hostname 的请求,服务器会继续做后续处理 listen 443 ssl; server_name example.com; ssl_certificate example.com.crt; ssl_certificate_key example.com.key;}
这个方法仅适用于 Nginx 大于等于 1.19.4 的情况。否则要想达到阻止 SNI 信息泄露的目的,你需要安装strict-sni 补丁。这个补丁是由来自南韩的 PHP 开发者 Hakase 开发的,该补丁可以使 Nginx 在 1.19.3 之前的实例针对非法请求真正地空返回。apt-get install git curl gcc libpcre3-dev software-properties-common /build-essential libssl-dev zlib1g-dev libxslt1-dev libgd-dev libperl-dev
然后,在 OpenSSL 的发布页中下载你想使用的版本。 git clone https://git.hakase.app/Hakase/openssl-patch.git
基于你之前选择的 OpenSSL 版本,先切换至 OpenSSL 源码的目录,然后为 OpenSSL 打上相应版本的补丁:cd opensslpatch -p1 < ../openssl-patch/openssl-equal-1.1.1d_ciphers.patch
来自开发者的备注:OpenSSL 3.x 版本有很多 API 上的改动,对于这些 OpenSSL 版本来说,这个补丁不再有用。(特指:Chacha20 和 Equal Preference 补丁)在条件允许的情况下,推荐使用 OpenSSL 1.1.x。下载你所需版本的 Nginx 安装包。
cd nginx/curl https://raw.githubusercontent.com/hakasenyang/openssl-patch/master/nginx_strict-sni_1.15.10.patch | patch -p1
在 Nginx 的配置指令中指定 OpenSSL 的目录: ./configure --with-http_ssl_module --with-openssl=/root/openssl
重要:在实际的实践中,仅使用这些参数并不能真正使网站如预期一样运行,你需要同时添加你想要和你需要的参数。例如:如果你想要你的网站支持 http/2 协议,则需要添加 --with-http_v2_module
参数。它不会主动自己把自己编译进去。 ./configure --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-http_ssl_module --with-openssl=/root/openssl
注:这部分是指代概要部分中「通过伪造成其它真实存在的网站/CDN 节点来混淆视听」的这一节,目的仅仅是给予发起非指向性扫描的人假信息。对于指向性扫描,它很难很好地达到目的。如果你只是想要对非授权客户端返回一个假网站,例如:手工制作的假网站、设置反向代理等等(同时也对非指向性扫描器返回空结果),你应当跳过这个部分,或者仅仅将这些参数视作「以待后用」添加。 make && make install
ln -s /usr/lib/nginx/modules/ /usr/share/nginxln -s /usr/share/nginx/sbin/nginx /usr/sbincat > /lib/systemd/system/nginx.service <<-EOF[Unit]Description=The NGINX HTTP and reverse proxy serverAfter=syslog.target network.target remote-fs.target nss-lookup.target[Service]Type=forkingPIDFile=/run/nginx.pidExecStartPre=/usr/sbin/nginx -tExecStart=/usr/sbin/nginxExecReload=/bin/kill -s HUP $MAINPIDExecStop=/bin/kill -s QUIT $MAINPIDPrivateTmp=true[Install]WantedBy=multi-user.targetEOFsystemctl enable nginx
http { # 控制开关 strict_sni on; strict_sni_header on; # 假的(默认)server 块 server { server_name localhost; listen 80; listen 443 ssl default_server; # "default_server" 需要被写在这里 ssl_certificate /root/cert.crt; # 可以为任意证书 ssl_certificate_key /root/cert.key; # 可以为任意证书 location / { return 444; } } # 常规 server 块 server { server_name normal_domain.tld; listen 80; listen 443 ssl; ssl_certificate /root/cert.crt; # 你的真实证书 ssl_certificate_key /root/cert/cert.key; # 你的真实证书 location / { echo "Hello World!"; } }}
现在,非指向性扫描器不再能获知你在这台服务器上运行什么网站了,除非是被指向性扫描,也就是对方知道你的 Hostname 的情况。 return 444;
意味着在返回 HTTP(并非 HTTPS)请求时就是字面意义的什么都不返回。如果没有打上 openssl-patch 补丁,当客户端尝试建立 TLS 链接时,证书信息仍会被返回。strict_sni on;
后,CDN 节点若在请求源站时不携带 SNI 信息,将会导致请求失败。参见 proxy_ssl_name. curl -v -k https://35.186.1.1* Rebuilt URL to: https://35.186.1.1/* Trying 35.186.1.1...* TCP_NODELAY set* Connected to 35.186.1.1 (35.186.1.1) 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=normal_domain.tld* start date: Nov 15 05:41:39 2019 GMT* expire date: Nov 14 05:41:39 2020 GMT* issuer: CN=normal_domain.tld* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.> GET / HTTP/1.1> Host: 35.186.1.1> User-Agent: curl/7.58.0> Accept: */*> * Empty reply from server* Connection #0 to host 35.186.1.1 left intactcurl: (52) Empty reply from server
启用后:curl -v -k https://35.186.1.1* Rebuilt URL to: https://35.186.1.1/* Trying 35.186.1.1...* TCP_NODELAY set* Connected to 35.186.1.1 (35.186.1.1) 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 alert, Server hello (2):* error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name* stopped the pause stream!* Closing connection 0curl: (35) error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name
谨防不知道,你应当了解:无论你后续怎么配置客户端校验规则(如 HTTP 头信息校验等),客户端在请求时携带目标/部署在服务端的 Hostname 时,证书信息仍会被返回。这是因为它的作用仅仅是预防非指向性扫描:这是建立在攻击者保护知道这台服务器上运行着什么网站的前提上的。如需应对指向性扫描,我强烈建议在条件允许的情况下修改在源站服务器上部署的 Hostname。 curl -v -k --resolve wrong_domain.tld:443:35.186.1.1 https://wrong_domain.tld* Added wrong_domain.tld:443:35.186.1.1 to DNS cache* Rebuilt URL to: https://wrong_domain.tld/* Hostname wrong_domain.tld was found in DNS cache* Trying 35.186.1.1...* TCP_NODELAY set* Connected to wrong_domain.tld (35.186.1.1) 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 alert, Server hello (2):* error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name* stopped the pause stream!* Closing connection 0curl: (35) error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name
使用正确的 Hostname 请求时的结果:(仅在 Hostname 正确的情况下,证书信息才会被返回) curl -v -k --resolve normal_domain.tld:443:35.186.1.1 https://normal_domain.tld* Added normal_domain.tld:443:35.186.1.1 to DNS cache* Rebuilt URL to: https://normal_domain.tld/* Hostname normal_domain.tld was found in DNS cache* Trying 35.186.1.1...* TCP_NODELAY set* Connected to normal_domain.tld (35.186.1.1) 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=normal_domain.tld* start date: Nov 15 05:41:39 2019 GMT* expire date: Nov 14 05:41:39 2020 GMT* issuer: CN=normal_domain.tld* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.> GET / HTTP/1.1> Host: normal_domain.tld> User-Agent: curl/7.58.0> Accept: */*> < HTTP/1.1 200 OK< Server: nginx/1.17.5< Date: Fri, 15 Nov 2019 05:53:19 GMT< Content-Type: text/plain< Transfer-Encoding: chunked< Connection: keep-alive< abc* Connection #0 to host normal_domain.tld left intact
注:如果你知道已知的非指向性扫描器的 IP 范围,你可以将它们全部拦截,权当是再上一层保险。这里给出 Censys 扫描器的 IP 范围:192.35.168.0/2374.120.14.0/24167.248.133.0/24162.142.125.0/24
load_module "modules/ngx_stream_module.so";http{ # 给自己 DIY 下 http 块 server { listen 80 default_server; server_name localhost; location / { proxy_pass http://104.27.184.146:80; # 伪装成 Cloudflare CDN 节点服务器 proxy_set_header Host $host; } } server { listen 80; server_name yourwebsite.com; # 如果你设置 https 作为唯一的回源协议,你不应该在 http{} 块中配置关于你真实域名的块,就像这里(除非你是监听在 localhost 而不是公网 IP) location / { proxy_pass http://127.0.0.1:8080; # 你的后端地址 proxy_set_header Host $host; } }}stream{ map $ssl_preread_server_name $name { yourwebsite.com website-upstream; # 你的真实网站路由 default cloudflare; # 默认路由 } upstream cloudflare { server 104.27.184.146:443; # Cloudflare 的 IP } upstream website-upstream {server 127.0.0.1:8080;} # 你的真实网站后端 server { listen 443; proxy_pass $name; proxy_ssl_name $ssl_preread_server_name; proxy_ssl_protocols TLSv1.2 TLSv1.3; ssl_preread on; }}
curl -I -v --resolve www.cloudflare.com:443:127.0.0.1 https://www.cloudflare.com/* Expire in 0 ms for 6 (transfer 0x55f3f0ae0f50)* Added www.cloudflare.com:443:127.0.0.1 to DNS cache* Hostname www.cloudflare.com was found in DNS cache* Trying 127.0.0.1...* TCP_NODELAY set* Expire in 200 ms for 4 (transfer 0x55f3f0ae0f50)* Connected to www.cloudflare.com (127.0.0.1) port 443 (#0)* ALPN, offering h2* ALPN, offering http/1.1* successfully set certificate verify locations:* CAfile: none CApath: /etc/ssl/certs* TLSv1.3 (OUT), TLS handshake, Client hello (1):* TLSv1.3 (IN), TLS handshake, Server hello (2):* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):* TLSv1.3 (IN), TLS handshake, Certificate (11):* TLSv1.3 (IN), TLS handshake, CERT verify (15):* TLSv1.3 (IN), TLS handshake, Finished (20):* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):* TLSv1.3 (OUT), TLS handshake, Finished (20):* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384* ALPN, server accepted to use h2* ALPN, offering http/1.1* successfully set certificate verify locations:* CAfile: none CApath: /etc/ssl/certs* TLSv1.3 (OUT), TLS handshake, Client hello (1):* TLSv1.3 (IN), TLS handshake, Server hello (2):* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):* TLSv1.3 (IN), TLS handshake, Certificate (11):* TLSv1.3 (IN), TLS handshake, CERT verify (15):* TLSv1.3 (IN), TLS handshake, Finished (20):* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):* TLSv1.3 (OUT), TLS handshake, Finished (20):* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384* ALPN, server accepted to use h2* Server certificate:* subject: businessCategory=Private Organization; jurisdictionC=US; jurisdictionST=Delaware; serialNumber=4710875; C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=cloudflare.com* start date: Oct 30 00:00:00 2018 GMT* expire date: Nov 3 12:00:00 2020 GMT* subjectAltName: host "www.cloudflare.com" matched cert's "www.cloudflare.com"* issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=DigiCert ECC Extended Validation Server CA* SSL certificate verify ok.* Using HTTP2, server supports multi-use* Connection state changed (HTTP/2 confirmed)* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0* Using Stream ID: 1 (easy handle 0x55f3f0ae0f50)> HEAD / HTTP/2> Host: www.cloudflare.com> User-Agent: curl/7.64.0> Accept: */*> * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):* old SSL session ID is stale, removing* Connection state changed (MAX_CONCURRENT_STREAMS == 256)!< HTTP/2 200 HTTP/2 200 < date: Tue, 06 Oct 2020 06:26:50 GMT* Connection #0 to host www.cloudflare.com left intact
(成功伪装成其它网站,部分结果已省略) server { listen 80; server_name yourdomain.com; if ($http_auth_tag != "here_is_the_credential") { return 444; } location / { echo "Hello World!"; }}
若想返回虚假的网站/后端时的配置文件: server { listen 80; server_name yourdomain.com; if ($http_auth_tag != "here_is_the_credential") { return @fake; } location / { echo "Hello World!"; } location @fake { root /var/www/fakewebsite/; # 强烈建议自己 DIY 一个假站点 }}
注:如果你倾向于在 https/443 端口上配置这些,我推荐你使用未知域名自签证书。使用真实且域名为公共暴露的域名可能会让攻击者更容易找到你的源站。Nginx 允许你在 SNI 信息不匹配 server_name 的情况下使用证书。cat > csrconfig.txt <<-EOF[ req ]default_md = sha256prompt = noreq_extensions = req_extdistinguished_name = req_distinguished_name[ req_distinguished_name ]commonName = yeet.comcountryName = SG[ req_ext ]keyUsage=critical,digitalSignature,keyEnciphermentextendedKeyUsage=critical,serverAuth,clientAuthsubjectAltName = @alt_names[ alt_names ]DNS.0 = yeet.comEOFcat > certconfig.txt <<-EOF[ req ]default_md = sha256prompt = noreq_extensions = req_extdistinguished_name = req_distinguished_name[ req_distinguished_name ]commonName = yeet.comcountryName = SG[ req_ext ]subjectKeyIdentifier = hashauthorityKeyIdentifier = keyid:always,issuerkeyUsage=critical,digitalSignature,keyEnciphermentextendedKeyUsage=critical,serverAuth,clientAuthsubjectAltName = @alt_names[ alt_names ]DNS.0 = yeet.comEOFopenssl genpkey -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out cert.keyopenssl req -new -nodes -key cert.key -config csrconfig.txt -out cert.csropenssl req -x509 -nodes -in cert.csr -days 365 -key cert.key -config certconfig.txt -extensions req_ext -out cert.pem
考虑到有人可能会拿这些指令生成 csr 用于申请真实证书,我保留了国家的字段(部分 CA 在接收 csr 文件时要求这个字段需要存在),如果不需要可以自行删除。server { listen 443; ssl_certificate /etc/nginx/certs/cert.crt; ssl_certificate_key /etc/nginx/certs/cert.key; server_name yourdomain.com; ssl_client_certificate /etc/nginx/certs/cloudflare.crt; ssl_verify_client on; error_page 495 496 = @444; # 用于在出现客户端证书校验相关错误时,用自行指定的内容替代默认的错误返回信息 location @444 {return 444;} location / { echo "Hello World!"; }}
此配置将会在遇到客户端证书错误时不作返回 server { listen 80 default_server; listen 443 ssl default_server; ssl_certificate /etc/nginx/certs/cert.crt; ssl_certificate_key /etc/nginx/certs/cert.key; server_name localhost; location / { return 444; }}
curl http://127.0.0.1:80curl: (52) Empty reply from servercurl -k https://127.0.0.1:443 curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
关键词:暴露