提高 frp 内网穿透的安全性

在现代网络环境中,内网穿透服务成为越来越重要的需求。而 frp 作为一款功能强大的开源内网穿透工具,为我们提供了便捷的解决方案。然而,安全始终是我们应该关注的重点。在本篇文章中,记录了如何通过合理的配置和措施来提高 frp 的安全性,以确保内网服务在穿透的同时也得到了充分的保护,尽量降低安全风险。

更新 frpc 和 frps 的版本

应始终使用最新的 frpc 和 frps 的软件版本,新版本通常修复了已知的安全漏洞和问题。

配置强密码

在 frps 的配置文件中,为管理员密码和 token 设置强密码。使用长密码、包含大小写字母、数字和特殊字符的组合,并定期更改密码。

限制访问来源

通过配置 frps 的 bind_addr,仅允许特定的 IP 地址或 IP 地址范围访问 frps 服务。这样可以限制对服务的访问,并减少潜在的攻击面。

使用 stcp

使用 stcp(secret tcp) 类型的代理可以避免让任何人都能访问到要穿透的服务,但是访问者也需要运行另外一个 frpc 客户端。

frps.ini 内容如下:

1
2
[common]
bind_port = 7000

在需要暴露到外网的机器上部署 frpc,且配置如下:

1
2
3
4
5
6
7
8
9
10
[common]
server_addr = x.x.x.x
server_port = 7000

[secret_ssh]
type = stcp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22

在想要访问内网服务的机器上也部署 frpc,且配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[common]
server_addr = x.x.x.x
server_port = 7000

[secret_ssh_visitor]
type = stcp
# stcp 的访问者
role = visitor
# 要访问的 stcp 代理的名字
server_name = secret_ssh
sk = abcdefg
# 绑定本地端口用于访问 SSH 服务
bind_addr = 127.0.0.1
bind_port = 6000

通过 SSH 访问内网机器,假设用户名为 test:

1
ssh -oPort=6000 test@127.0.0.1

加密与压缩

每一个代理都可以选择是否启用加密和压缩的功能。

加密算法采用 aes-128-cfb,压缩算法采用 snappy。

在每一个代理的配置中使用如下参数指定:

1
2
3
4
5
6
7
# frpc.ini
[ssh]
type = tcp
local_port = 22
remote_port = 6000
use_encryption = true
use_compression = true

通过设置 use_encryption = true,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止传输内容被截取。

如果传输的报文长度较长,通过设置 use_compression = true 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 CPU 资源。

使用 TLS 加密

use_encryptionSTCP 等功能能有效防止流量内容在通信过程中被盗取,但是无法判断对方的身份是否合法,存在被中间人攻击的风险。为此 frp 支持 frpc 和 frps 之间的流量通过 TLS 协议加密,并且支持客户端或服务端单向验证,双向验证等功能。

frps.inicommontls_only = true 时,表示 server 端只接受 TLS 连接的客户端,这也是 frps 验证 frpc 身份的前提条件。如果 frps.inicommontls_trusted_ca_file 内容是有效的话,那么默认就会开启 tls_only = true

注意:启用此功能后除 xtcp 且 xtcp 的 protocol 配置为 kcp 外,可以不用再设置 use_encryption 重复加密

从 v0.50.0 开始,tls_enable 的默认值将会为 true,默认开启 TLS 协议加密。

如果 frps 端没有配置证书,则会使用随机生成的证书来加密流量。

默认情况下,frpc 开启 TLS 加密功能,但是不校验 frps 的证书。

启用双向验证

双向验证应该是目前最安全的方案。启用双向验证需要先生成证书,首先建立一个文件夹,将以下配置写入 my-openssl.cnf 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[ ca ]
default_ca = CA_default
[ CA_default ]
x509_extensions = usr_cert
[ req ]
default_bits = 2048
default_md = sha256
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca
string_mask = utf8only
[ req_distinguished_name ]
[ req_attributes ]
[ usr_cert ]
basicConstraints = CA:FALSE
nsComment = "OpenSSL Generated Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = CA:true

生成 ca:

1
2
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.ca.com" -days 5000 -out ca.crt

生成服务端证书:

1
2
3
4
5
6
7
8
9
10
11
12
openssl genrsa -out server.key 2048

openssl req -new -sha256 -key server.key \
-subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=server.com" \
-reqexts SAN \
-config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:你的服务端公网ip,DNS:example.server.com")) \
-out server.csr

openssl x509 -req -days 5000 -sha256 \
-in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-extfile <(printf "subjectAltName=DNS:localhost,IP:你的服务端公网ip,DNS:example.server.com") \
-out server.crt

subjectAltName 中的 IP 地址改为服务端的公网 IP,否则客户端连接时会报如下错误:

[W] [service.go:133] login to server failed: tls: failed to verify certificate: x509: certificate is valid for 127.0.0.1, not x.x.x.x

如果需要绑定域名,将 example.server.com 改为域名。

生成客户端证书:

1
2
3
4
5
6
7
8
9
10
11
12
openssl genrsa -out client.key 2048

openssl req -new -sha256 -key client.key \
-subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=client.com" \
-reqexts SAN \
-config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:client.com,DNS:example.client.com")) \
-out client.csr

openssl x509 -req -days 5000 -sha256 \
-in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-extfile <(printf "subjectAltName=DNS:client.com,DNS:example.client.com") \
-out client.crt

最终的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── ca.crt
├── ca.key
├── ca.srl
├── client.crt
├── client.csr
├── client.key
├── my-openssl.cnf
├── server.crt
├── server.csr
└── server.key

1 directory, 8 file

将证书上传到服务端并修改配置文件,增加 tls 验证相关配置:

1
2
3
4
5
6
7
8
9
10
11
# frpc.ini
[common]
tls_cert_file = /to/cert/path/client.crt
tls_key_file = /to/key/path/client.key
tls_trusted_ca_file = /to/ca/path/ca.crt

# frps.ini
[common]
tls_cert_file = /to/cert/path/server.crt
tls_key_file = /to/key/path/server.key
tls_trusted_ca_file = /to/ca/path/ca.crt

随后重启服务端和客户端。至此,客户端和服务端的双向验证配置完毕。

启用和检查日志

启用 frps 的日志记录功能,并定期检查日志以检测异常连接或潜在的安全问题。

1
2
3
log_file = ./frps.log
log_level = info
log_max_days = 5

强化服务器安全

除了保护 frps 服务本身外,还应采取适当的安全措施来保护整个服务器。这包括使用安全的 SSH 配置、禁用不必要的服务、限制用户访问权限等。

总结

  1. 始终使用最新版本的 frp
  2. 配置 token 验证,设置复杂密码
  3. 若无必要,不要开启 dashboard
  4. 启用双向 tls 验证
  5. 强化系统安全

参考资料

https://gofrp.org/docs/overview/


提高 frp 内网穿透的安全性
https://cui.cc/frp-security-config/
作者
南山崔崔
发布于
2023年8月24日
许可协议