隐藏在站点下的NGINX前置WSS隧道建设

在之前的文章《带探测防御的高级HTTP/HTTPS隧道建设》中,阐述了一种向外表示为正常网页服务,而真正的代理服务无法被探测的隧道建设方式。不难发现,在那个方法中,NGINX只作为实现HSTS的301跳转工具,整个功能其实都是由Gost实现的。

该文章的实现方式不同之前,这次的实现中把NGINX前置,首先利用NGINX实现了一个完整的网页服务(HTTP、HTTPS、HSTS),然后利用NGINX的路径分流功能来在正常的HTTPS流量中建立代理连接,显得更加真实,而NGINX实现的TLS会比golang的实现更快。但是本方法并非没有缺点,gost仍然要处理websocket连接,且websocket连接增加了握手耗时(在TLS基础上)。

配置服务端

配置服务端NGINX

安装NGINX

可以查看之前的文章《编译安装NGINX与使用》。

书写配置文件

先建立第一个server(HTTP服务):

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
server_name my.site;

if ( $host != "my.site" ) {
return 403;
}

return 301 https://my.site$request_uri;
}

这个server的配置意义是,监听在80端口(HTTP服务默认端口),监听域名的 my.site ,如果访问的域名不是 my.site 则拒绝连接,对合法连接进行301跳转到本机的HTTPS服务。

然后建立第二个server(HTTPS服务):

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
server {
listen 443 ssl;
server_name my.site;

if ( $host != "my.site" ) {
return 403;
}

ssl_certificate /etc/certs/my.crt;
ssl_certificate_key /etc/certs/my.key;
ssl_protocols TLSv1.3;
ssl_early_data on;

location / {
add_header Cache-Control no-cache;
proxy_set_header Host remote.site;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://remote.site;
proxy_connect_timeout 30s;
}

location /websocketPathDiversion {
proxy_redirect off;
proxy_pass http://127.0.0.1:34567;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

其中,对于websocket分流的路径,为了保证安全性,可以用工具随机生成一个UUID(Universally Unique Identifier,通用唯一识别码),例如:

1
2
3
location /dadefda5-2ff4-4481-a46c-06d26e598ca4 {
...
}

下面对配置中的一些字段做解释:

  • listen 443 ssl:监听443端口,并启用SSL。

  • ssl_certificate:填写SSL证书的绝对路径。

  • ssl_certificate_key:填写SSL证书密钥的绝对路径。

  • ssl_protocols:指定使用的TLS版本,这里锁定了TLS1.3。(注意:如果OpenSSL版本太老,有可能不支持TLSv1.3,例如CentOS7自带的就不支持,这需要你自行升级OpenSSL并重新编译安装NGINX来指定OpenSSL的安装路径。

  • ssl_early_data on:开启了TLS1.3的0-RTT特性,减少握手延迟,NGINX因为安全原因默认关闭此项。(注意:如果NGINX版本太老,有可能不支持该字段。

  • location /:该路径以下的请求将被该代码块处理。

  • add_header Cache-Control no-cache;:无缓存。

  • proxy_set_header Host remote.site;

    HTTP header 中的 Host 含义为所请求的目的主机名。当 NGINX 作为反向代理使用,而后端真实 web 服务器设置有类似 防盗链功能 ,或者根据 HTTP header 中的 Host 字段来进行 路由过滤 功能的话,若作为反向代理的 nginx 不重写请求头中的 Host 字段,将会导致请求失败。

  • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    HTTP header 中的 X_Forward_For 表示该条 http 请求是由谁发起的。如果反向代理服务器不重写该请求头的话,那么后端真实 web 服务器在处理时会认为所有的请求都来自反向代理服务器。如果后端 web 服务器有防攻击策略的话,那么反向代理服务器对应的 ip 地址就会被封掉。

    上述配置的意思是增加一个 $proxy_add_x_forwarded_forX-Forwarded-For里去,注意是增加,而不是覆盖。当然由于默认的 X-Forwarded-For 值是空的,所以我们总感觉 X-Forwarded-For 的值就等于 $proxy_add_x_forwarded_for 的值。

    X-Forwarded-For的格式为X-Forwarded-For:real client ip, proxy ip 1, proxy ip N,每经过一个反向代理就在请求头X-Forwarded-For后追加反向代理IP。

  • proxy_set_header X-Real-IP $remote_addr;:获取真实的客户端IP。

  • proxy_redirect off;:禁止URL重定向。

  • proxy_http_version 1.1;

    proxy_set_header Upgrade $http_upgrade;

    proxy_set_header Connection "upgrade";

    以上三个字段都是在保证NGINX会将请求当作websocket处理。

加载新的配置文件

1
nginx -s reload

配置服务端Gost

1
gost -L ws://:34567/:12345?path=/websocketPathDiversion

配置客户端

客户端只需配置gost。

1
gost -L tcp://:45678 -F="forward+wss://my.site:443?secure=true&ip=x.y.z.v&path=websocketPathDiversion"

然后连接客户端的45678端口就能连接上服务端的12345的tcp服务了。