如何防止CDN后面的源站IP地址暴露,比如Cloudflare?

How to prevent origin server IP address behind CDN from exposed, like Cloudflare?

我使用 Cloudflare/Google Cloud Platform 作为 CDN,如何隐藏我的服务器 IP 以防止被扫描器检测到?

IP白名单、hostname/port更改、OpenSSL/SNI补丁、website/backend伪造、header/client证书授权等方法可以帮助您的服务器免受检测等

简而言之:像扫描仪一样思考,你会没事的。

我也在my blog发布了这个答案,看看有没有兴趣。

在开始详细介绍之前,如果您需要完全保护您的服务器,远远不够我在这里介绍的事情。 安全遵循Liebig's barrel;任何轻微的疏忽都会导致不可预知的后果。简而言之,您需要负责您的安全。我在这里写的唯一一件事是关于如何防止网络服务器的 IP 泄漏。如果有一个被忽视的地方,比如应用程序中的设计错误导致 IP 泄漏,这将无济于事。

一般来说,找到你的原始节点的方法是像普通用户一样通过请求扫描每个可能的IP,并通过过滤结果找到目标。在大多数情况下,您可以通过设置 IP 白名单来阻止它们。但这取决于。您可能不知道 CDN 节点用于请求您的原始服务器的 IP,或者它们正在更改。使用此策略可能会导致服务中断。

大纲

  • IP 白名单
  • 更改hostname/listen端口
  • 防止漫无目的的批量扫描导致证书泄露
    • 原服务器的域名信息不会被输入数据库
    • 如果可能,更改网络服务器侦听的端口
  • 伪装成其他 real-existing websites/CDN 节点提供虚假信息
  • 通过伪装成其他人来防止未经授权的访问 self-handcrafting websites/returning null
    • 需要配合CDN提供的其他规定
    • 客户端证书认证也是一种不常见的方式
  • 结论

如有疑惑,可先查看结论中的流程图,再继续阅读。

策略

假设 Debian/Ubuntu 为 OS,Nginx 作为网络服务器。

IP 白名单

其实防止原服务器IP泄露最直接有效的方法就是设置IP白名单。 如果你能这样做,你就应该这样做。但是,请记住以下几点:

  1. 如果CDN提供商没有提供正在使用的IP列表,请勿使用此策略,否则可能会出现服务中断
  2. 如果在请求原始服务器时使用 HTTPS 作为方案,您应该使用 iptables 而不是 Nginx 的 build-in access module,否则搜索者仍然可以通过检测证书找到您的服务器SNI;
  3. 如果使用 Cloudflare 作为 CDN,只应用 IP 白名单 可能会让搜索者有机会绕过 Cloudflare 的保护 并让他们找到您的原始 IP 地址。

如果您使用的是 iptables,请记得安装 iptables-persistent,否则您可能会在重启后丢失您的过滤规则:

apt-get install iptables-persistent

丢弃来自未列入白名单的 IP 的请求的示例:

更改hostname/listen端口

通常,目标扫描器会扫描所有具有标准端口(http/80、https/443)且您的网站公开 domain/hostname 的 IP。所以如果你能改变它们,通常就可以了。


您可以自定义您的来源 hostname/domain 以供 CDN 节点请求,以防止搜索者通过主机名检测您的来源服务器 IP


很少有 CDN 供应商支持自定义请求源站端口

但是,如果您以某种方式让搜索者知道您的主机名或您使用的 IP 运行ges,您的源服务器就有暴露的风险。所以,保重。

防止证书 SNI 泄漏补丁

拒绝 SSL 握手的目的是防止证书的 SNI 信息从漫无目的的批量扫描中泄漏(或者可以很容易地被认为是域信息)。搜索者可以在此基础上建立一个website-IP关系数据库,以便在漫无目的的批量扫描后快速搜索该特征。

域信息包含在证书中,可以用来识别哪些网站是 运行ning(虽然它们实际上可能不是 运行):

如果你的Nginx版本高于1.19.4,你可以直接使用ssl_reject_handshake feature to prevent SNI info leak. Otherwise, you will need to apply the strict-sni patch.

N.B。此措施仅在您希望使用 HTTPS 作为 CDN 节点请求原始服务器的方案时才有效。如果您只倾向于使用 HTTP 作为请求的方案,您可以简单地 return 444; 在 deault 服务器块,无需继续阅读或略过这部分内容。

ssl_reject_handshake的配置(Nginx ≥ 1.19.4)

ssl_reject_handshake、默认块和普通块的配置涉及两部分:

server { # Default block returns null for SSL requests with wrong hostname
    listen 443 ssl;
    ssl_reject_handshake on;
}

server { # With the correct hostname, server will process requests
    listen  443 ssl;
    server_name test.com;
    ssl_certificate test.com.crt;
    ssl_certificate_key test.com.key;
}

如果使用 Nginx 1.19.3 或以下版本,如果您的 Nginx 版本在 1.19.4 之前,您可以使用 sni-strict patch instead. This patch is developed by Hakase, which can return a true empty response 来处理无效请求。

安装sni-strict补丁的步骤(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

然后,在release page.

下载你需要的OpenSSL版本

下载存储库openssl-patch:

git clone https://git.hakase.app/Hakase/openssl-patch.git

根据您之前选择的OpenSSL版本,将目录切换到OpenSSL代码所在的目录,然后用相关补丁给OpenSSL打补丁:

cd openssl
patch -p1 < ../openssl-patch/openssl-equal-1.1.1d_ciphers.patch

Note from developer: OpenSSL 3.x has many API changes, and this patch is no longer useful. (Chacha20 and Equal Preference patch) It is recommended using version 1.1.x whenever possible.

下载Nginx package您需要的版本。
解压Nginx包,切换目录到Nginx,给Nginx打补丁:

cd nginx/
curl https://raw.githubusercontent.com/hakasenyang/openssl-patch/master/nginx_strict-sni_1.15.10.patch | patch -p1

在配置参数中指定 OpenSSL 目录:

./configure --with-http_ssl_module --with-openssl=/root/openssl

N.B。在实际操作中,这些论点远不能使网站按预期工作,您需要根据需要添加所需内容。例如,如果你想让你的网站使用http/2协议部署,则需要添加参数--with-http_v2_module,否则将无法构建模块。

如果您倾向于将您的服务器伪装成其他 real-existing 网站以进行 漫无目的的批量扫描,打算向扫描仪提供虚假信息而不是 null,您还可以添加额外的这里的参数:

./configure --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-http_ssl_module --with-openssl=/root/openssl

P.S。这部分是指大纲中的“伪装成其他real-existingwebsites/CDN节点提供虚假信息”,仅针对漫无目的的扫描仪提供虚假信息,以及对于瞄准扫描,这很难。如果你只想向未经授权的客户展示虚假网站,如手工制作虚假网站,保留代理等(以及 return 空信息给漫无目的的扫描仪),你应该跳过这部分,或者只添加这些参数以进行提前.

配置完成后,构建并安装 Nginx。
make && make install
安装完成。
为了方便起见,我更喜欢在那之后也做这些:

ln -s /usr/lib/nginx/modules/ /usr/share/nginx
ln -s /usr/share/nginx/sbin/nginx /usr/sbin

cat > /lib/systemd/system/nginx.service <<-EOF
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

systemctl enable nginx

sni-strict 补丁的配置(Nginx ≤ 1.19.3)

配置与ssl_reject_handshake类似。有3个元素需要配置:

  1. 控制选项
  2. 假(默认)服务器块
  3. 普通服务器块
http {
    # control options
    strict_sni on;
    strict_sni_header on;

    # fake server block
    server {
        server_name  localhost;
        listen       80;
        listen       443 ssl default_server; # "default_server" is necessary
        ssl_certificate /root/cert.crt; # Can be any certificate here
        ssl_certificate_key /root/cert.key; # Can be any certificate here

        location / {
            return 444;
        }
    }

    # normal server blocks
    server {
        server_name  normal_domain.tld;
        listen       80;
        listen       443 ssl;
        ssl_certificate /root/cert.crt; # Your real certificate here
        ssl_certificate_key /root/cert/cert.key; # Your real certificate here

        location / {
            echo "Hello World!";   
        }
    }
}

现在,aimless 批量扫描器无法知道您在此服务器上运行正在访问哪个网站,除非他们已经知道并使用主机名扫描您的服务器,这称为 aimed 扫描仪。

P.S。 return 444; 意味着 return 在涉及 HTTP(而非 HTTPS)请求时几乎没有任何意义。如果 strict-sni 未修补,当客户端尝试建立 TLS 连接时,认证信息仍将 returned。

N.B。设置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
  CApath: /etc/ssl/certs
* 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
> 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 intact
curl: (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
  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 0
curl: (35) error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name

以防万一,您应该知道在使用目标主机名请求时仍会 return 编辑认证信息。之后甚至您已经配置了客户端检查规则(如:HTTP header 检查等)。这也是为什么这只能防止漫无目的的扫描:只有当攻击者不知道你在这台服务器上运行正在访问什么网站时,它才会起作用。为了应对目标扫描,作为原始节点,我强烈建议尽可能更改主机名。

使用错误的主机名请求:(如果主机名错误,则不会return编辑证书信息)

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 0
curl: (35) error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name

使用正确的主机名请求:(只有主机名正确,证书信息才会被 returned)

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
* 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
> 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
< Connection: keep-alive
* Connection #0 to host normal_domain.tld left intact

P.S。如果你知道已知漫无目的的扫描器使用的 IP 运行ge,你也可以使用 iptables 来阻止它们,作为另一个次要的安全保护措施。比如Censys扫描仪的IP运行ge如下:

74.120.14.0/24
192.35.168.0/23
162.142.125.0/24
167.248.133.0/24

伪装成其他 real-existing websites/CDN 节点提供虚假信息

通过这个策略,你可以给漫无目的的扫描器一些虚假信息,让他们用虚假信息建立数据库。你可能想强加给扫描器你的服务器是 CDN 服务器;你可能还想在里面结合你的真实站点来混淆目标扫描器,使其无法分辨它检测到的服务器是原始服务器还是 CDN 节点等
我个人不太愿意使用这个策略,因为它需要我考虑很多因素,比如真正的CDN节点使用哪个IDC提供商(和我的网站托管在同一个IDC),ASN (Autonomous System Number)它的IP使用,它打开的端口,CDN 添加的 HTTP header 信息等,以确保搜索者会觉得 co融合。太麻烦了。

N.B。如果可能,您应该将 HTTPS 设置为 CDN 节点请求源站的唯一方案。 否则,您需要注意 HTTP 端口上的行为。 例如您要假装的目标 server/website 总是将 http/80 请求重定向到 https/443端口,但您忘记将 http/80 端口上的网站请求转为 https/443.

P.S。事实上,将服务器伪装成 Cloudflare 的 CDN 服务器并不是一个坏但不是好的决定。因为即使我们可以在官方网站上找到 Cloudflare's IP range,这让您可能会认为 Cloudflare 只会将这些 IP 用于 CDN 节点,但有 一些 currently-existing 服务器实际上是 运行ning Cloudflare 的 CDN 节点应用程序,其 IP 未包含在 IP 列表中(或者它们 运行ning forward-proxy 就像我接下来要写的那样)。曾几何时,我 运行 扫描并发现一些服务器没有使用 Cloudflare 的 IP,它们正在执行上述操作。因此,伪装成 Cloudflare 的 CDN 服务器是一回事:您实际上不需要 have/use Cloudflare 的 IP。
然而,这也不是一回事,因为您必须为您的真实网站使用您的 own-created(包括 self-signed 或不包括)证书。众所周知,大多数 Cloudflare 用户使用由 Cloudflare 签名的证书。如果您确实想将您的服务器伪装成 Cloudflare 的服务器,请考虑这样做的目的。

配置

P.S。如果您不知道如何安装 ngx_stream_module,请查看为 Nginx 1.19.3 或以下版本安装 sni-strict 补丁的步骤。亲戚在。
配置中有3个要点:

  1. Feigning/default 块用于 http 块中的端口 http/80;
  2. Feigning/default 阻塞流块中的端口 https/443;
  3. 将您的真实 domain/website 路由到后端的块。

配置示例:

load_module "modules/ngx_stream_module.so";

http{ # Design the http block by yourself
    server {
        listen 80 default_server;
        server_name localhost;
        location / {
            proxy_pass http://104.27.184.146:80; # Feign as Cloudflare's CDN node
            proxy_set_header Host $host;
        }
    }
    server {
        listen 80;
        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)
        location / {
            proxy_pass http://127.0.0.1:8080; # Your backend
            proxy_set_header Host $host;
        }
    }
}

stream{
    map $ssl_preread_server_name $name {
        yourwebsite.com website-upstream; # Your real website's route
        default cloudflare; # Default route
    }
    upstream cloudflare {
        server 104.27.184.146:443; # Cloudflare IP
    }
    upstream website-upstream {server 127.0.0.1:8080;} # Your real website's backend
    server {
        listen 443;
        proxy_pass $name;
        proxy_ssl_name $ssl_preread_server_name;
        proxy_ssl_protocols TLSv1.2 TLSv1.3;
        ssl_preread on;
    }
}

结果

会return其他网站real-existing证书的内容:

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
* 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

(伪装成功,部分结果省略)

通过伪装成其他人来防止未经授权的访问 self-handcrafting websites/returning null

在开始本节之前,您应该知道此策略只能在 CDN 节点可以 return 与普通用户不同的情况下使用。这是一个例子:

HTTP header 用于在 GCP 中请求源服务器的设置
HTTPheader校验是一种常见的授权请求是否来自CDN的方式

P.S。 GCP(Google 云平台)的 HTTP 负载平衡服务提供了一个选项,用于设置 GCP CDN 节点在源服务器从 GCP CDN 节点接收数据时应提供的请求 header[^1]。这使得源站可以知道来自normal/spiteful个客户端的CDN节点请求。

[^1]: 虽然 GCP 加载 balancing/CDN 服务仅接受 GCP VM 实例作为后端,但机制是相同的。

P.S。在某些产品中,一些工程师想在请求源站调试时添加一些header,但不是作为特性,这意味着它不会出现在他们产品的文档中(例如CDN.net),客服人员也不承认。如果您想发现在您使用的CDN 产品中的header 中是否包含特殊的header,编写一个简单的脚本来转储您收到的所有header 将是一个不错的选择。这里就不详述了。

配置识字,不用解释

配置如果要return null:

server {
    listen       80;
    server_name  yourweb.site;

    if ($http_auth_tag != "here_is_the_credential") {
        return 444;
    }
    location / {
        echo "Hello World!";
    }
}

如果要return伪装的配置website/backend:

server {
    listen 80;
    server_name yourweb.site;

    if ($http_auth_tag != "here_is_the_credential") {
        return @fake;
    }
    location / {
        echo "Hello World!";
    }
    location @fake {
        root /var/www/fakesite/; # Highly recommend to build a hand-crafting fake website by yourself
    }
}

P.S。如果您倾向于在 https/443 端口中配置这些,我建议您使用 未知域 self-sign 证书。使用具有 公开域 的真实证书可能会让扫描器轻松找到您的源服务器。 Nginx 允许您在不匹配 SNI 信息的情况下使用证书 server_name
N.B。有些人可能会考虑将真实证书与公开域的子域一起使用,并且最有可能使用 Let's Encrypt 来获得免费证书。如果你关心 Certificate Transparency, which can tell what certificates you have within the specific domain. Especially, Let's Encrypt submits all certificates it issues to CT logs. (Reference: Original, Archive.ph)
就更好了 如果想查看自己的证书是否登录到CT日志,可以访问crt.sh.
如果你不能判断你要申请证书的CA是否将它颁发的所有证书提交到CT日志,你最好self-sign证书。

self-sign命令如下:

cat > csrconfig.txt <<-EOF
[ req ]
default_md=sha256
prompt=no
req_extensions=req_ext
distinguished_name=req_distinguished_name
[ req_distinguished_name ]
commonName=yeet.com
countryName=SG
[ req_ext ]
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=critical,serverAuth,clientAuth
subjectAltName=@alt_names
[ alt_names ]
DNS.0=yeet.com
EOF

cat > certconfig.txt <<-EOF
[ req ]
default_md=sha256
prompt=no
req_extensions=req_ext
distinguished_name=req_distinguished_name
[ req_distinguished_name ]
commonName=yeet.com
countryName=SG
[ req_ext ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=critical,serverAuth,clientAuth
subjectAltName=@alt_names
[ alt_names ]
DNS.0=yeet.com
EOF

openssl genpkey -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out cert.key
openssl req -new -nodes -key cert.key -config csrconfig.txt -out cert.csr
openssl req -x509 -nodes -in cert.csr -days 365 -key cert.key -config certconfig.txt -extensions req_ext -out cert.pem

考虑到有些读者可能会使用我上面写的命令生成CSR文件,可以用来申请真正的证书,我保留字段countryName(有些CA需要在接收时存在该字段CSR 文件)。如果你不需要它,你可以模拟删除它。

N.B。 Self-sign 证书可能会增加 Cloudflare 中 MITM (man-in-the-middle attack), unless the underlying facilities are credible, or CDN provider does support requests with provided client certificate, aka Authenticated Origin Pulls 的风险。

在 Cloudflare 中启用“Authenticated Origin Pulls”

Client Certificates check也是授权请求是否来自CDN节点的方式。只有很少的 CDN 提供商支持使用客户端证书进行请求。无论哪个提供商具有此功能,您的服务器上的配置都是可能的。这是示例:

server {
    listen 443;
    ssl_certificate cert.crt;
    ssl_certificate_key cert.key;

    server_name yourdomain.com;

    ssl_client_certificate cloudflare.crt;
    ssl_verify_client on;

    error_page 495 496 = @444; # For specifying the return instead of giving the default return while the error is related to the client certificate auth error

    location @444 {return 444;}

    location / {
        echo "Hello World!";
    }
}

面对client certificate errors

会return null

P.S。伪造其他websites/backends也是可以的,只需模仿“HTTP header检查”部分中的那个即可。
N.B。无论您想使用什么方法,请注意默认值 return。如果请求无效,请将默认 return 设置为与 return 相同。

将默认 return 设置为 null 作为无效请求的 return:

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:80
curl: (52) Empty reply from server

curl -k https://127.0.0.1:443   
curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)

结论

简单来说,为了保护您的源站IP不被检测到,您可以:

  1. 尽可能设置 IP 白名单
  2. 如果可能,possible/Change 监听端口
  3. ,请更改源服务器上网站的主机名
  4. 为不匹配的主机名设置默认 return
  5. 为匹配的主机名设置授权方法
  6. 想想如果你是扫描器本身,你会如何看待服务器行为

整个过程可以大致画成这样: