ice*_*box 1 security webserver cdn cloudflare
我使用 Cloudflare/Google Cloud Platform 作为 CDN,如何隐藏我的服务器 IP 不被扫描仪检测到?
有一些方法可以帮助您的服务器免受检测,例如IP白名单、主机名/端口更改、OpenSSL/SNI补丁、网站/后端伪造、标头/客户端证书授权等。
\n简而言之:像扫描仪一样思考,一切都会好起来的。
\n我也在我的博客中发布了这个答案,如果您感兴趣的话,请查看。
\n在开始详细介绍之前,如果您需要完全保护您的服务器,那么仅执行我在这里介绍的操作是远远不够的。安全遵循李比希的桶;任何轻微的疏忽都会导致不可预测的后果。简而言之,您需要负责您的安全。我在这里唯一写的是如何防止网络服务器的 IP 泄漏。如果有一个被忽视的地方,比如应用程序中的设计错误导致IP泄漏,这将无济于事。
\n一般来说,找到你的原始节点的方法是像普通用户一样通过请求扫描每一个可能的IP,并通过过滤结果来找到目标。大多数情况下,您可以通过设置IP白名单来阻止它们。但这取决于情况。您可能不知道 CDN 节点用于请求原始服务器的 IP,或者它们正在更改。使用此策略可能会导致服务中断。
\n如果您感到困惑,可以先查看结论中的流程图,然后再继续阅读。
\n假设 Debian/Ubuntu 作为操作系统,Nginx 作为 Web 服务器。
\n事实上,防止原始服务器IP泄露最直接、最有效的方法就是设置IP白名单。如果你能这样做,你就应该这样做。但是,请记住以下几点:
\n如果您使用 iptables,请记住安装 iptables-persistent,否则如果重新启动,您可能会丢失过滤规则:
\napt-get install iptables-persistent\nRun Code Online (Sandbox Code Playgroud)\n删除来自未列入白名单的 IP 的请求的示例:
\n
一般来说,目标扫描仪将使用您网站的公开域名/主机名扫描所有具有标准端口(http/80、https/443)的 IP。因此,如果您可以更改它们,通常就可以了。
\n
\n您可以自定义您的源主机名/域名供CDN节点请求,以防止搜索者通过主机名检测到您的源服务器IP

\n很少有 CDN 提供商支持对源服务器的请求自定义端口
但是,如果您以某种方式让搜索者知道您的主机名或您使用的 IP 范围,您的源服务器就有暴露的风险。所以,一定要关心。
\n拒绝 SSL 握手的目的是防止证书的 SNI 信息(或者可以很容易地被认为是域信息)在无目的的批量扫描中泄漏。搜索者可以在此基础上建立网站-IP关系数据库,以便在漫无目的的批量扫描后在特征中快速搜索。
\n证书中包含域信息,可用于确认哪些网站正在运行(尽管它们可能并未实际运行):
\n
如果您的 Nginx 版本高于 1.19.4,您可以简单地使用ssl_reject_handshake功能来防止 SNI 信息泄漏。否则,您将需要应用strict-sni 补丁。
\n注意:只有当您想使用HTTPS作为 CDN 节点请求原始服务器的方案时,此措施才有效。如果您只倾向于使用HTTP作为请求方案,则只需return 444;在默认服务器块中即可,无需继续阅读或略过这部分。
ssl_reject_handshake、默认块和普通块的配置涉及两部分:
\nserver { # Default block returns null for SSL requests with wrong hostname\n listen 443 ssl;\n ssl_reject_handshake on;\n}\n\nserver { # With the correct hostname, server will process requests\n listen 443 ssl;\n server_name test.com;\n ssl_certificate test.com.crt;\n ssl_certificate_key test.com.key;\n}\nRun Code Online (Sandbox Code Playgroud)\n如果使用 Nginx 1.19.3 或更低版本,可以使用sni-strict 补丁代替。该补丁由Hakase开发,如果您的 Nginx 版本在 1.19.4 之前,它可以针对无效请求返回 true 空响应。
\n首先,安装必要的包:
\napt-get install git curl gcc libpcre3-dev software-properties-common \\\nbuild-essential libssl-dev zlib1g-dev libxslt1-dev libgd-dev libperl-dev\nRun Code Online (Sandbox Code Playgroud)\n然后,在发布页面下载您需要的OpenSSL版本。
\n下载存储库openssl-patch:
\ngit clone https://git.hakase.app/Hakase/openssl-patch.git\nRun Code Online (Sandbox Code Playgroud)\n根据您之前选择的OpenSSL版本,将目录切换到OpenSSL代码所在目录,然后使用相关补丁修补OpenSSL:
\ncd openssl\npatch -p1 < ../openssl-patch/openssl-equal-1.1.1d_ciphers.patch\nRun Code Online (Sandbox Code Playgroud)\n\n\n开发人员注意:OpenSSL 3.x 有许多 API 更改,并且此补丁不再有用。(Chacha20 和 Equal Preference 补丁)\n建议尽可能使用版本 1.1.x。
\n
下载您需要的版本的Nginx 软件包。
\n解压Nginx包,切换目录到Nginx,给Nginx打补丁:
cd nginx/\ncurl https://raw.githubusercontent.com/hakasenyang/openssl-patch/master/nginx_strict-sni_1.15.10.patch | patch -p1\nRun Code Online (Sandbox Code Playgroud)\n在配置参数中指定 OpenSSL 目录:
\n./configure --with-http_ssl_module --with-openssl=/root/openssl\nRun Code Online (Sandbox Code Playgroud)\n注意:在实际操作中,这些论点远远不能让网站按预期工作,您需要添加您需要的内容。例如,如果您希望您的网站使用http/2协议部署,则--with-http_v2_module需要添加参数,否则模块将无法构建。
如果您倾向于将您的服务器伪装成其他真实存在的网站进行漫无目的的批量扫描,打算向扫描仪提供错误信息而不是 null,您还可以在此处添加额外的参数:
\n./configure --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-http_ssl_module --with-openssl=/root/openssl\nRun Code Online (Sandbox Code Playgroud)\nPS这部分大体上指的是“冒充其他真实存在的网站/CDN节点来提供虚假信息”,只是为了给漫无目的的扫描器提供虚假信息,这对于有针对性的扫描来说很难起太大作用。如果您只想向未经授权的客户显示虚假网站,例如手工制作虚假网站、制作预留代理等(并向漫无目的的扫描器返回空信息),您应该跳过这部分,或者仅添加这些参数以供提前使用。
\n配置完成后,构建并安装 Nginx。
\n make && make install
\n安装完成。
\n为了方便起见,我更喜欢在之后执行这些操作:
ln -s /usr/lib/nginx/modules/ /usr/share/nginx\nln -s /usr/share/nginx/sbin/nginx /usr/sbin\n\ncat > /lib/systemd/system/nginx.service <<-EOF\n[Unit]\nDescription=The NGINX HTTP and reverse proxy server\nAfter=syslog.target network.target remote-fs.target nss-lookup.target\n\n[Service]\nType=forking\nPIDFile=/run/nginx.pid\nExecStartPre=/usr/sbin/nginx -t\nExecStart=/usr/sbin/nginx\nExecReload=/bin/kill -s HUP $MAINPID\nExecStop=/bin/kill -s QUIT $MAINPID\nPrivateTmp=true\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsystemctl enable nginx\nRun Code Online (Sandbox Code Playgroud)\n配置类似于ssl_reject_handshake。需要配置 3 个元素:
\nhttp {\n # control options\n strict_sni on;\n strict_sni_header on;\n\n # fake server block\n server {\n server_name localhost;\n listen 80;\n listen 443 ssl default_server; # "default_server" is necessary\n ssl_certificate /root/cert.crt; # Can be any certificate here\n ssl_certificate_key /root/cert.key; # Can be any certificate here\n\n location / {\n return 444;\n }\n }\n\n # normal server blocks\n server {\n server_name normal_domain.tld;\n listen 80;\n listen 443 ssl;\n ssl_certificate /root/cert.crt; # Your real certificate here\n ssl_certificate_key /root/cert/cert.key; # Your real certificate here\n\n location / {\n echo "Hello World!"; \n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n现在,无目标的批量扫描仪无法知道您在此服务器上运行的网站,除非他们已经知道并使用主机名扫描您的服务器,这称为目标扫描仪。
\nPSreturn 444;意味着当涉及到 HTTP(而不是 HTTPS)请求时,实际上不返回任何内容。如果 strict-sni 没有打补丁,客户端尝试建立 TLS 连接时仍然会返回认证信息。
注意:strict_sni on;设置后,CDN 节点需要向 SNI 请求,否则会失败。参见:proxy_ssl_name。
当选项打开时,您可以看到证书信息被隐藏。\n之前:
\ncurl -v -k https://35.186.1.1\n* Rebuilt URL to: https://35.186.1.1/\n* Trying 35.186.1.1...\n* TCP_NODELAY set\n* Connected to 35.186.1.1 (35.186.1.1) port 443 (#0)\n* ALPN, offering h2\n* ALPN, offering http/1.1\n CApath: /etc/ssl/certs\n* TLSv1.2 (IN), TLS handshake, Certificate (11):\n* TLSv1.2 (IN), TLS handshake, Server key exchange (12):\n* TLSv1.2 (IN), TLS handshake, Server finished (14):\n* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):\n* TLSv1.2 (OUT), TLS change cipher, Client hello (1):\n* TLSv1.2 (OUT), TLS handshake, Finished (20):\n* TLSv1.2 (IN), TLS handshake, Finished (20):\n* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384\n* ALPN, server accepted to use http/1.1\n* Server certificate:\n* subject: CN=normal_domain.tld\n* start date: Nov 15 05:41:39 2019 GMT\n* expire date: Nov 14 05:41:39 2020 GMT\n* issuer: CN=normal_domain.tld\n> GET / HTTP/1.1\n> Host: 35.186.1.1\n> User-Agent: curl/7.58.0\n> Accept: */*\n* Empty reply from server\n* Connection #0 to host 35.186.1.1 left intact\ncurl: (52) Empty reply from server\nRun Code Online (Sandbox Code Playgroud)\n后:
\ncurl -v -k https://35.186.1.1\n* Rebuilt URL to: https://35.186.1.1/\n* Trying 35.186.1.1...\n* TCP_NODELAY set\n* Connected to 35.186.1.1 (35.186.1.1) port 443 (#0)\n* ALPN, offering h2\n* ALPN, offering http/1.1\n CApath: /etc/ssl/certs\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS alert, Server hello (2):\n* error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name\n* stopped the pause stream!\n* Closing connection 0\ncurl: (35) error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name\nRun Code Online (Sandbox Code Playgroud)\n以防万一,您应该知道在使用目标主机名请求时仍会返回认证信息。即使您已经配置了客户端检查规则(例如:HTTP 标头检查等)。这也是为什么这只能防止漫无目的的扫描:它仅在攻击者不知道您在此服务器上运行的网站时才有效。为了应对目标扫描,作为原始节点,我强烈建议如果可能的话更改主机名。
\n主机名错误的请求:(如果主机名错误,则不会返回证书信息)
\ncurl -v -k --resolve wrong_domain.tld:443:35.186.1.1 https://wrong_domain.tld\n* Added wrong_domain.tld:443:35.186.1.1 to DNS cache\n* Rebuilt URL to: https://wrong_domain.tld/\n* Hostname wrong_domain.tld was found in DNS cache\n* Trying 35.186.1.1...\n* TCP_NODELAY set\n* Connected to wrong_domain.tld (35.186.1.1) port 443 (#0)\n* ALPN, offering h2\n* ALPN, offering http/1.1\n* successfully set certificate verify locations:\n* CAfile: /etc/ssl/certs/ca-certificates.crt\n CApath: /etc/ssl/certs\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS alert, Server hello (2):\n* error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name\n* stopped the pause stream!\n* Closing connection 0\ncurl: (35) error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name\nRun Code Online (Sandbox Code Playgroud)\n使用正确的主机名请求:(只有主机名正确,才会返回证书信息)
\ncurl -v -k --resolve normal_domain.tld:443:35.186.1.1 https://normal_domain.tld\n* Added normal_domain.tld:443:35.186.1.1 to DNS cache\n* Rebuilt URL to: https://normal_domain.tld/\n* Hostname normal_domain.tld was found in DNS cache\n* Trying 35.186.1.1...\n* TCP_NODELAY set\n* Connected to normal_domain.tld (35.186.1.1) port 443 (#0)\n* ALPN, offering h2\n* ALPN, offering http/1.1\n* successfully set certificate verify locations:\n* CAfile: /etc/ssl/certs/ca-certificates.crt\n CApath: /etc/ssl/certs\n* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384\n* ALPN, server accepted to use http/1.1\n* Server certificate:\n* subject: CN=normal_domain.tld\n* start date: Nov 15 05:41:39 2019 GMT\n* expire date: Nov 14 05:41:39 2020 GMT\n* issuer: CN=normal_domain.tld\n> GET / HTTP/1.1\n> Host: normal_domain.tld\n> User-Agent: curl/7.58.0\n> Accept: */*\n< HTTP/1.1 200 OK\n< Server: nginx/1.17.5\n< Date: Fri, 15 Nov 2019 05:53:19 GMT\n< Content-Type: text/plain\n< Connection: keep-alive\n* Connection #0 to host normal_domain.tld left intact\nRun Code Online (Sandbox Code Playgroud)\nPS 如果您知道已知的漫无目的地扫描仪使用的 IP 范围,您也可以使用 iptables 来阻止它们,作为另一种较小的安全保护措施。例如Censys扫描仪的IP范围如下:
\n74.120.14.0/24\n192.35.168.0/23\n162.142.125.0/24\n167.248.133.0/24\nRun Code Online (Sandbox Code Playgroud)\n通过这种策略,您可以向漫无目的的扫描仪提供一些虚假信息,让他们用虚假信息建立数据库。您可能想要强行扫描您的服务器是 CDN 服务器;你可能还想在里面结合你的真实站点来迷惑目标扫描仪,使其无法区分它检测到的服务器是原始服务器还是CDN节点等。
\n我个人不太愿意使用这种策略,因为它需要我考虑很多因素,比如真正的 CDN 节点将使用哪个 IDC 提供商(并将我的网站托管在同一个 IDC 上)、其 IP 使用的ASN(自治系统编号)、其打开的端口、添加的 HTTP 标头信息CDN等,确保搜索者会感到困惑。这很烦人。
注意:如果可能,您应该将 HTTPS 设置为 CDN 节点请求源服务器的唯一方案。否则,您需要关心 HTTP 端口上的行为。例如,您想要假装的目标服务器/网站总是将 http/80 请求重定向到 https/443 端口,但您忘记将 http/80 端口上的网站请求转向 https/443。
\nPS 事实上,将服务器冒充为 Cloudflare 的 CDN 服务器并不是一个坏但也不好的决定。因为尽管我们可以在官方网站上找到Cloudflare 的 IP 范围,这使得您可能会认为 Cloudflare 只会将这些 IP 用于 CDN 节点,但目前存在的一些服务器实际上正在运行 Cloudflare 的 CDN 节点应用程序,其 IP 未包含在 IP 列表中(或者它们正在运行转发代理,就像我接下来要写的那样)。曾几何时,我进行了一次扫描,发现一些服务器没有使用 Cloudflare 的 IP,而这些服务器正在执行上述操作。因此,冒充 Cloudflare 的 CDN 服务器是一回事:您实际上不需要拥有/使用 Cloudflare 的 IP。
\n但是,这也不是问题,因为您必须为您的真实网站使用自己创建的(包括自签名或非自签名)证书。众所周知,大多数 Cloudflare 用户都使用 Cloudflare 签名的证书。如果您确实想将您的服务器冒充为 Cloudflare,请考虑您这样做的目的。
PS 如果您不知道如何安装 ngx_stream_module,请查看为 Nginx 1.19.3 或更低版本安装 sni-strict 补丁的步骤。亲戚在那儿。
\n配置要点有3点:
配置示例:
\nload_module "modules/ngx_stream_module.so";\n\nhttp{ # Design the http block by yourself\n server {\n listen 80 default_server;\n server_name localhost;\n location / {\n proxy_pass http://104.27.184.146:80; # Feign as Cloudflare\'s CDN node\n proxy_set_header Host $host;\n }\n }\n server {\n listen 80;\n server_name yourwebsite.com; # If you set https as the only scheme for CDN nodes requesting your origin server, you should not configure the block of your real website in the http{} block, aka here (except that the listen address is "localhost" instead of the public network IP)\n location / {\n proxy_pass http://127.0.0.1:8080; # Your backend\n proxy_set_header Host $host;\n }\n }\n}\n\nstream{\n map $ssl_preread_server_name $name {\n yourwebsite.com website-upstream; # Your real website\'s route\n default cloudflare; # Default route\n }\n upstream cloudflare {\n server 104.27.184.146:443; # Cloudflare IP\n }\n upstream website-upstream {server 127.0.0.1:8080;} # Your real website\'s backend\n server {\n listen 443;\n proxy_pass $name;\n proxy_ssl_name $ssl_preread_server_name;\n proxy_ssl_protocols TLSv1.2 TLSv1.3;\n ssl_preread on;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n它将返回带有其他网站真实存在的证书的内容:
\ncurl -I -v --resolve www.cloudflare.com:443:127.0.0.1 https://www.cloudflare.com/\n\n* Expire in 0 ms for 6 (transfer 0x55f3f0ae0f50)\n* Added www.cloudflare.com:443:127.0.0.1 to DNS cache\n* Hostname www.cloudflare.com was found in DNS cache\n* Trying 127.0.0.1...\n* TCP_NODELAY set\n* Expire in 200 ms for 4 (transfer 0x55f3f0ae0f50)\n* Connected to www.cloudflare.com (127.0.0.1) port 443 (#0)\n* ALPN, offering h2\n* ALPN, offering http/1.1\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS handshake, Server hello (2):\n* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):\n* TLSv1.3 (IN), TLS handshake, Certificate (11):\n* TLSv1.3 (IN), TLS handshake, CERT verify (15):\n* TLSv1.3 (IN), TLS handshake, Finished (20):\n* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):\n* TLSv1.3 (OUT), TLS handshake, Finished (20):\n* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384\n* ALPN, server accepted to use h2\n* Server certificate:\n* subject: businessCategory=Private Organization; jurisdictionC=US; jurisdictionST=Delaware; serialNumber=4710875; C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=cloudflare.com\n* start date: Oct 30 00:00:00 2018 GMT\n* expire date: Nov 3 12:00:00 2020 GMT\n* subjectAltName: host "www.cloudflare.com" matched cert\'s "www.cloudflare.com"\n* issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=DigiCert ECC Extended Validation Server CA\n* SSL certificate verify ok.\n* Using HTTP2, server supports multi-use\n* Connection state changed (HTTP/2 confirmed)\n* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0\n* Using Stream ID: 1 (easy handle 0x55f3f0ae0f50)\n> HEAD / HTTP/2\n> Host: www.cloudflare.com\n> User-Agent: curl/7.64.0\n> Accept: */*\n* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):\n* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):\n* old SSL session ID is stale, removing\n* Connection state changed (MAX_CONCURRENT_STREAMS == 256)!\n< HTTP/2 200 \nHTTP/2 200 \n< date: Tue, 06 Oct 2020 06:26:50 GMT\n* Connection #0 to host www.cloudflare.com left intact\nRun Code Online (Sandbox Code Playgroud)\n(成功冒充真实网站,部分结果省略)
\n在开始本节之前,您应该知道此策略只能在 CDN 节点返回与普通用户不同的内容时使用。这是一个示例:
\n
\n GCP 中请求源服务器的 HTTP 标头设置
\n HTTP 标头检查是授权请求是否来自 CDN 的常用方法。
PS GCP(Google Cloud Platform)的 HTTP 负载平衡服务提供了一个选项来设置 GCP CDN 节点在源服务器从 GCP CDN 节点接收数据时应提供的请求标头[^1]。这使得源服务器可以知道来自正常/恶意客户端的 CDN 节点请求。
\n[^1]:虽然 GCP 负载均衡/CDN 服务仅接受 GCP 虚拟机实例作为后端,但机制是相同的。
\nPS 在某些产品中,有些工程师希望在请求源站进行调试时添加一些 header,但不是作为一个功能,这意味着它不会出现在他们的产品(例如 CDN.net)的文档中,客户服务人员也没有得到认可。如果您想发现您使用的 CDN 产品中是否包含特殊标头,那么编写一个简单的脚本来转储您收到的所有标头将是一个不错的选择。这里就不详细说了。
\n配置都是识字的,无需解释。
\n如果想返回null则配置:
\nserver {\n listen 80;\n server_name yourweb.site;\n\n if ($http_auth_tag != "here_is_the_credential") {\n return 444;\n }\n location / {\n echo "Hello World!";\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n如果您想返回假网站/后端,请配置:
\nserver {\n listen 80;\n server_name yourweb.site;\n\n if ($http_auth_tag != "here_is_the_credential") {\n return @fake;\n }\n location / {\n echo "Hello World!";\n }\n location @fake {\n root /var/www/fakesite/; # Highly recommend to build a hand-crafting fake website by yourself\n }\n}\nRun Code Online (Sandbox Code Playgroud)\nPS 如果您倾向于在 https/443 端口中配置这些,我建议您使用未知域进行自签名证书。使用具有公开域的真实证书可以让扫描仪轻松找到您的源服务器。Nginx允许您使用证书,而无需将 SNI 信息与 server_name 匹配。
\n注意 有些人可能会考虑将真实证书与公开域的子域一起使用,并且最有可能使用 Let\'s Encrypt 来获取免费证书。如果您关心证书透明度,那就最好了,它可以告诉您在特定域内拥有哪些证书。特别是,Let's Encrypt 将其颁发的所有证书提交到 CT 日志。(参考: Original , Archive.ph )
\n如果你想查看你的证书是否被记录到 CT 日志中,可以访问crt.sh。
\n如果您无法判断您要申请证书的 CA 是否将其颁发的所有证书提交到 CT 日志中,您最好采用自签名证书。
自签名命令如下:
\ncat > csrconfig.txt <<-EOF\n[ req ]\ndefault_md=sha256\nprompt=no\nreq_extensions=req_ext\ndistinguished_name=req_distinguished_name\n[ req_distinguished_name ]\ncommonName=yeet.co