中国大陆实行 Web 服务备案制度,要求所有服务器在大陆境内且通过域名使用标准80/443 端口的 Web 服务都需要向通管局备案。但到了执行层面就会出现各种问题。比如这哥们反馈1说自己的 NAS 因为使用 5000 端口被联通公司通报整改,要求下线 Web 服务。讲道理使用 5000 端口不需要备案,但很多时候没有道理可讲。只能我们自己小心行事,想办法降低家用 Web 服务被通报的概率。

从分享的图片上看,应该是此人的 NAS 服务被人举报到了通信管理局,或者是被通管局的系统扫描到了 Web 服务。然后通管局联系了联通公司,联通公司最后「不得已」通报处理:

因该客户为宽带用户,所以其 IP 地址为联通公司动态分配,其网络活动存在一定不确定性,给监管造成很大困难。

这里用了「不得已」是我觉得联通公司如果不是接到了通管局的指令,大概率是会睁一只眼闭一只眼。只要你不做 PCDN,基本不会影响联通的利益。所以我们要想办法减少服务被通管局扫到的可能。

根据通报内容,联通确认该用户使用未备案CN域名5000/5001端口Web 方式提供NAS管理服务。这里 debuf 叠满。

首先他用了CN域名做DDNS。CN域名监控出了名的严苛,要求严格实名,如果不是 Web 服务要求设在境内,估计大部分人都不会选择 CN 域名。现在基本可以把 CN 域名跟备案划等号。未备案的 CN 域名就显得很扎眼,通管局顺手扫瞄一下也在情理之中。毕竟你在他们辖区做坏事对领导也不利。

所以第一条建议就是不要使用 CN 域名。如果一定要用,先完成备案,再使用合适的子域名。

其次他用了 5000/5001 端口,这是群晖系统默认的端口。虽然群晖硬件不便宜,但人家软件做的好,就算不买他家设备,也有很多人刷「黑群晖」,所以 5000/5001 端口用的就很多了。自然也就被重点关注。

所以第二条建议就是避免使用常见端口。80/443/8080自然是不可能用了。很多人建议用高位端口,这不是问题的本质。本质是你不要用大家都用的端口。用的越多,越被关照。最好是从10000-65535中间随机选一个使用。

但无论怎么选,你的端口还是可能被扫描到。如果被扫描到,又该如何降低被识别的概率呢?这就需要 Nginx 相关知识了。

第一,禁止使用明文 HTTP 协议。已经是老生常谈了。传统的实践是同时监听两个端口,比如 5000 端口跑明文 HTTP 协议,5001 跑 HTTPS 协议。稍好一点的是在 5000 端口上做重定向,将所有明文请求都 301 到 HTTPS 端口。但这个重定还是标准 HTTP 协议呀,特征过于明显。

所以我建议直接禁用明文 HTTP 协议端口,只对外提供 HTTPS 服务

第二,如果有人通过明文协议请求 HTTPS 端口会有什么呢?会不会被检测到有 Web 服务?

大家不妨用 curl 访问自己的 Web 服务试试,使用 HTTP 协议但端口指定为 HTTPS 服务。如果服务端用的是 Nginx 则会收到如下响应内容:

<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx</center>
</body>
</html>

这简直就是此地无银三百两嘛~

通过明文协议尝试请求 HTTPS 服务在 Nginx 中有分配特殊的 497 状态码2。如果发生该报错,我们希望 Nginx 直接关闭连接,不返回任何响应。这需要用到另外一个非标状态码 4443🤦‍♂️

综合两种状态码,我们需要在 server 中增加如下配置:

error_page 497 @close;

location @close {
    return 444;
}

使用error_page指令为497状态码设置虚拟路径@close,Nginx在处理的@close时发现是返回444状态码,于是直接关闭连接。

这个时候你再用 curl 访问对应的端口就会收到如下报错:

curl http://example.zz.ac:5678
curl: (52) Empty reply from server

到这里还是不够~如果别人不知道你的域名,直接通过 IP 访问,还是会返回 HTTP 信息。我们希望只有使用正确的域名访问才返回结果,使用 IP 或者别的域名访问统统关闭连接。

这就需要创建两个 server 配置:

server {
        listen 5678 ssl;
        listen [::]:5678 ssl;

        server_name example.zz.ac;

        ssl_certificate ...;
        ssl_certificate_key ..;

        error_page 497 @close;

        location @close {
                return 444;
        }
        ...
}
server {
        listen 5678 ssl default_server;;
        listen [::]:5678 ssl default_server;;

        server_name _;

        ssl_certificate ...;
        ssl_certificate_key ..;

        return 444;
}

第一个 server 使用支持域名访问,配合前面说的 497 + 444 技术加固。使用 IP 地址或其他域名访问就会命中第二个 server,也就是 default_server,该服务无条件返回 444 状态码关闭连接。

到这里就完成了 Nginx 对服务扫描全面加固工作。调用方只有在明确知道我方域名和端口的前提下才能访问到 Web 内容,否则要么端口不通,要么 Nginx 直接关闭连接不返回内容。从而降低被识别的概率。

最后我们可以反过来想想通管局会怎么做。如果你是程序员,领导要你来实现这个功能,你会怎么做呢?

其实这个功能最好是由联通来做。他们可以在边缘设备上部署包检测工具,对分析报文中的 SNI信息,发现域名后再检测是否有备案,没有备案就屏蔽。如果真的部署了,这是一个无解的方案。然而,这个方案非常昂贵,实际用家宽对提供 Web 服务的用户寥寥无几,上深度检测有点杀鸡用牛刀了。我认为联通没有动力去搞。但面子工程还是要的,比如封禁80/44 端口,顶多再加上8080/5000/5001这种用的比较多的,算是对上下有个交待。

但如果通管局领导执意要你开发呢?一种可能的办法获得本辖区所有用户注册的CN域名信息,然后尝试扫描常见端口,发现有问题再联系运营商配合整改。理论上所有CN域名都需要实名注册,通管局应该有办法查到辖区内人员注册的域名。非国内注册的域名就没有这个问题。

具体扫描估计得用 curl 或者各语言相关的 HTTP 库了。算法顶多是尝试发起 GET 请求,如果有返回内容再做进一步处理。而我们的服务直接关闭连接了,估计检测方会认为没有 Web 服务,继续扫描下一个端口。

以上是我的猜测,但估计实际也不会有太大出入。本文介绍的加固措施应该有效。欢迎大家尝试并分享自己的经验。


  1. https://www.chiphell.com/forum.php?mod=viewthread&tid=2734503↩︎

  2. https://nginx.org/en/docs/http/ngx_http_ssl_module.html↩︎

  3. https://nginx.org/en/docs/http/request_processing.html↩︎