提高 frp 内网穿透的安全性

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

从 v0.52.0 版本开始,frp 开始支持 TOML、YAML 和 JSON 作为配置文件格式。

请注意,INI 已被弃用,并将在未来的发布中移除。新功能只能在 TOML、YAML 或 JSON 中使用。

建议使用 TOML 作为配置文件格式。

更新 frpc 和 frps 的版本

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

为 token 配置强密码

在 frp 中,token 是一种简单的身份认证方式,只需要在 frpc 和 frps 配置文件中配置相同的 token 即可。我们可以使用 随机密码生成器 来生成包含大小写字母、数字和特殊字符的组合,并定期更改 token。

修改默认端口

frps 的配置文件中默认使用 7000 端口,建议改为高位端口号。

限制访问来源

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

加密与压缩

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

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

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

1
2
3
4
5
6
7
8
# frpc.toml
[[proxies]]
name = "ssh"
type = "tcp"
localPort = 22
remotePort = 6000
transport.useEncryption = true
transport.useCompression = true

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

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

使用 stcp

某些内网服务如果直接暴漏在公网可能存在安全风险,使用 stcp(secret tcp) 类型的代理可以实现将内网服务暴漏给经过授权的用户,但是访问者也需要运行另外一个 frpc 客户端。

配置 frps.toml

1
bindPort = 7000

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

1
2
3
4
5
6
7
8
9
10
serverAddr = "x.x.x.x"
serverPort = 7000

[[proxies]]
name = "secret_ssh"
type = "stcp"
# 只有与此处设置的 secretKey 一致的用户才能访问此服务
secretKey = "abcdefg"
localIP = "127.0.0.1"
localPort = 22

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

1
2
3
4
5
6
7
8
9
10
11
12
serverAddr = "x.x.x.x"
serverPort = 7000

[[visitors]]
name = "secret_ssh_visitor"
type = "stcp"
# 要访问的 stcp 代理的名字
serverName = "secret_ssh"
secretKey = "abcdefg"
# 绑定本地端口以访问 SSH 服务
bindAddr = "127.0.0.1"
bindPort = 6000

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

1
ssh -oPort=6000 test@127.0.0.1

自定义 TLS 加密

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

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

注意:启用此功能后除 xtcp 外,可以不用再设置 transport.useEncryption 重复加密

从 v0.50.0 开始,transport.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
# frpc.toml
transport.tls.certFile = "/to/cert/path/client.crt"
transport.tls.keyFile = "/to/key/path/client.key"
transport.tls.trustedCaFile = "/to/ca/path/ca.crt"

# frps.toml
transport.tls.certFile = "/to/cert/path/server.crt"
transport.tls.keyFile = "/to/key/path/server.key"
transport.tls.trustedCaFile = "/to/ca/path/ca.crt"

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

启用和检查日志

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

1
2
3
log.to = "./frps.log"
log.level = "info"
log.maxDays = 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日
许可协议