为什么需要业务隐身

部署完本文所述的业务隐身方案后,在腾讯云一台普通服务器半个月收到了来自 224 个不同 ip 的访问请求

在网络世界中,各种匿名爬虫的数量之大超乎想象,它们不知疲倦的搜集着每一台主机的信息

一旦被它们发现漏洞或后门,被用来当肉鸡挖矿事小,数据泄漏也不是不能忍,

可万一它们搞什么违法活动是真的会殃及自身的。

对于业务方来说,我们很难做到时刻保持警惕,也难以时刻跟进最新安全资讯

一个简单而直接的需求就出现了:不让陌生人发现我们的系统

如何设计业务隐身

我们期望的业务隐身模块最好能满足以下特点

  • 非侵入式
    • 不需要对原业务系统做太大改造
    • 不影响正常业务逻辑
  • 成本可控
    • 研发改造成本
    • 运维成本
    • 额外性能消耗
  • 通用透明
    • 不需要对每个业务系统做针对式改造
    • 改造后逻辑对具体业务透明

设计方案

首先考虑通用性,如果需要方案对任意后端服务生效,就必须作用在通信链路上

通过中间件去验证请求方身份,然后再允许此次通信,否则需要具备阻断能力

高配方案

linux 内核提供了一个通用的包过滤框架 netfilter。

在流量包经过内核协议栈的时候会触发预先注册在 netfilter 的 hook 点。

  • NF_IP_PRE_ROUTING
  • NF_IP_LOCAL_IN
  • NF_IP_FORWARD
  • NF_IP_LOCAL_OUT
  • NF_IP_POST_ROUTING

这里不多赘述细节了,因为一篇文章根本讲不完。

总之通过 netfilter 我们可以实现很多非常灵活的包校验逻辑,比如

  1. 区分通信端口和认证端口

认证端口负责完成一系列用户登录逻辑

通信端口则是正常的业务处理逻辑,如果用户 ip 没有认证过则拦截流量

  1. tcp/udp 内容校验

若客户端发过来的包里不符合特定格式,或不带特定 key 则自动丢包

经济方案

在绝大多数的服务器上都会部署一个 nginx 作为反向代理,因此本方案的通用性是足够好的

在 nginx 中,我们可以通过接入 lua 扩展来定制化请求校验逻辑(参见 openresty

而且相比上述高配方案,这里校验 https 请求也变得更为简单

  1. 首先需要配置禁止 ip 直接访问,目的是屏蔽那些简单粗暴遍历 ip 的爬虫

    1
    2
    3
    4
    5
    server {
    listen 443 default_server;
    server_name _;
    return 444;
    }
  2. 然后配置定制化校验逻辑,比如这里校验请求头部必须带上时间戳

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    location / {
    # deny anonymous request
    access_by_lua_block {
    local xtoken = ngx.req.get_headers()["Token"]
    if not xtoken then
    ngx.exit(ngx.HTTP_CLOSE)
    end
    local token = ""
    if type(xtoken) == "string" then
    token = xtoken
    else
    token = xtoken[1]
    end
    if not token then
    ngx.exit(ngx.HTTP_CLOSE)
    end
    local t = tonumber(token)
    if not t or math.abs(ngx.now()-t) > 600 then
    ngx.exit(ngx.HTTP_CLOSE)
    end
    return
    }

    root html;
    index index.html index.htm;
    }

第二步的校验逻辑也可以改成其他自定义的 token,配合一些加密算法效果更好

完成以上两步后,除非其他人专门渗透你的服务器,否则完全不用担心被爬虫扫到

即使日后你使用的依赖库爆出惊天大漏洞,也可以放心不会被脚本小子乱打

后记

从没有绝对的安全

每种安全方案都只能解决一些特定场景的问题,业务隐身能非常好的避免供应链漏洞被暴露在公共网络下

对于防止漏洞被滥用,防止无意义流量嗅探有着较好的效果,但绝不意味着可以放松警惕

它非常适合那些「希望自己的服务只被正确的人用来做合适的事」的场景

附录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
worker_processes  2;
pid logs/nginx.pid;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /opt/nginx/log/access.log main;
error_log /opt/nginx/log/error.log;
sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

# disable ip request
server {
listen 443 default_server;
server_name _;
ssl_certificate cert/example.org_bundle.pem;
ssl_certificate_key cert/example.org.key;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;

return 444;
}

# HTTPS server
#
server {
listen 443 ssl;
server_name example.org;

ssl_certificate cert/example.org_bundle.pem;
ssl_certificate_key cert/example.org.key;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;

location / {
# deny anonymous request
access_by_lua_block {
local xtoken = ngx.req.get_headers()["Token"]
if not xtoken then
ngx.exit(ngx.HTTP_CLOSE)
end
local token = ""
if type(xtoken) == "string" then
token = xtoken
else
token = xtoken[1]
end
if not token then
ngx.exit(ngx.HTTP_CLOSE)
end
local t = tonumber(token)
if not t or math.abs(ngx.now()-t) > 600 then
ngx.exit(ngx.HTTP_CLOSE)
end
return
}

root html;
index index.html index.htm;
}
}
}