把内网服务映射到公网,很多人第一步就做错了。最常见的做法是直接开放端口,或者随便用个穿透工具默认配置跑起来就完事。这种裸奔状态下的服务器,短则几分钟长则几小时,就会被扫描器盯上,接着就是暴力破解、漏洞利用一条龙。内网穿透的安全问题,核心不在于工具本身,而在于你如何配置访问链路、如何做身份验证、如何在暴露面和防护之间找到平衡点。
先搞清楚你的穿透架构属于哪一种Ubuntu服务器做内网穿透,目前主流方案分三类。第一类是自建中转,比如在公网有台云服务器,通过frp、rathole这类工具把内网端口映射出去。第二类是直接利用隧道服务,比如Cloudflare Tunnel或者国内的一些穿透平台,流量走它们的节点。第三类是点对点穿透,比如Tailscale、ZeroTier这类组网工具,不走集中式中转。三种架构的安全风险完全不同,配置策略也要区别对待。
自建中转的风险点在于你的公网服务器本身就是攻击面。frp默认端口7000,如果不改端口、不加认证,等于把内网大门钥匙挂在门外。隧道服务类的风险在于流量经过了第三方节点,虽然省去了维护公网服务器的麻烦,但证书管理、访问控制完全依赖服务商。点对点方案看似最安全,但一旦某个节点沦陷,整个虚拟网络都可能被横向移动。你得先明确自己用的是哪种,才能针对性地做防护。
frp配置的安全基线,大部分教程都没讲全frp是目前自建穿透用得最多的工具,但网上的配置教程十有八九只教你跑通,不教你加固。先说服务端frps,有几个配置项必须改。第一是bind_port,默认7000,建议改成五位数的随机高端口,避开扫描器常用端口范围。第二是token字段,这个一定要设,而且要用强随机字符串,至少32位以上。token是frp自己的一层简单认证,没有token的客户端连不上服务端,这是第一道门。
但光靠token不够。frp从0.37版本开始支持TLS加密传输,在服务端配置里加上tls_enable = true,同时指定证书文件路径。如果你没有域名证书,可以用自签证书,配合客户端的tls_enable和tls_trusted_ca_file来做双向验证。这样即使有人抓包,看到的也是加密流量,无法直接分析出你在穿透什么服务。
还有一个容易被忽略的点是frp的Dashboard面板。很多人为了调试方便开着dashboard,端口默认7500,用户名密码默认admin/admin,这简直是送人头。如果必须开dashboard,一定要改端口,设置强密码,并且用防火墙限制只有特定IP才能访问这个端口。更好的做法是调试完直接关掉,在配置里把dashboard_port设为0。
客户端frpc这边,除了配好token和TLS,还有一个关键配置是server_name或者自定义的认证方式。如果你用的是frp的stcp模式做点对点加密穿透,只有持有相同secret_key的客户端才能互相访问,这比直接暴露端口安全得多。stcp模式下的流量在frp层面就做了加密,即使中转服务器被控制,攻击者也看不到明文内容。
Ubuntu本机防火墙,不是开了就完事很多人觉得配了ufw,允许了22端口和穿透端口就万事大吉。实际上防火墙策略要配合穿透架构来设计。以frp为例,公网服务器上只需要开放frp的bind_port给所有客户端,但穿透后映射出来的服务端口,比如你映射了内网的3306数据库端口到公网的13306,这个13306端口绝对不要对全网开放。
正确的做法是,在公网服务器的ufw里,把13306端口的访问来源限制为只有你信任的IP。比如你只在公司和家里访问,那就只放行这两个IP段的入站流量。命令很简单:
sudo ufw allow from 你的IP to any port 13306
如果你需要从多个不固定IP的地方访问,那就不要直接把端口暴露在公网上,而是用后面会讲到的端口敲门或者WireGuard先建立安全隧道再访问。
内网Ubuntu服务器本身的防火墙同样重要。穿透工具只是把流量引进来,如果内网服务器上跑的服务本身有漏洞,穿透等于给攻击者开了条高速公路。内网服务器应该只开放必要的服务端口,并且这些端口只监听内网地址,不要监听0.0.0.0。比如你的Web服务只需要让frp客户端访问,那就让它监听127.0.0.1或者内网IP,而不是对所有接口开放。
SSH是重灾区,必须单独加固内网穿透最常见的用途就是远程SSH。但SSH也是被攻击最频繁的服务。Ubuntu服务器上,SSH配置至少要改这几项。首先禁止密码登录,只允许密钥认证,在/etc/ssh/sshd_config里设置PasswordAuthentication no。其次禁止root直接登录,PermitRootLogin no。第三,如果可能的话,把SSH端口从22改掉,虽然这不是安全措施,但能过滤掉90%的自动化扫描噪音。
更进一步,可以配置fail2ban来防护SSH暴力破解。Ubuntu安装fail2ban很简单,apt install fail2ban之后,创建一个本地配置文件/etc/fail2ban/jail.local,针对SSH做限制:
[sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 3600 findtime = 600
这样在10分钟内失败3次就封IP一小时。如果你的SSH是通过frp穿透出去的,fail2ban同样能起作用,因为frp转发过来的连接在auth.log里会记录真实的来源IP。但要注意,如果你用了CDN或者多层代理,来源IP可能会被改写,这时候需要配置frp的proxy_protocol_version来传递真实IP。
端口敲门,让服务端口平时完全不可见如果你需要从公网访问内网的高敏感服务,比如数据库、管理后台,端口敲门是一种非常有效的隐藏手段。原理很简单:服务端口平时是关闭的,只有按特定顺序访问几个指定端口后,防火墙才临时放行你的IP。在Ubuntu上可以用knockd来实现。
安装knockd后,编辑/etc/knockd.conf,配置一个敲门序列:
[options] UseSyslog [openSSH] sequence = 7000,8000,9000 seq_timeout = 5 command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT tcpflags = syn [closeSSH] sequence = 9000,8000,7000 seq_timeout = 5 command = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT tcpflags = syn
这段配置的意思是,按顺序访问7000、8000、9000端口后,临时放行你的IP访问22端口。关闭时反向操作。对于扫描器来说,你的22端口根本不存在,只有知道敲门序列的人才能让它现身。把敲门端口和frp的穿透结合起来,攻击面可以压缩到极致。
端口敲门有个进阶用法:把敲门序列和单次使用令牌结合。比如用knockd配合一段脚本,每次成功敲门后自动更换序列,或者通过TOTP动态生成敲门端口。这种方案实现起来稍微复杂,但对于高价值目标来说是值得的。
WireGuard作为前置隧道,先认证再暴露比端口敲门更优雅的方案是,所有穿透服务都不直接暴露在公网,而是先通过WireGuard建立加密隧道,再在隧道内部访问。这样做的好处是,公网上只暴露一个WireGuard端口,而这个端口本身不响应任何未认证的探测,扫描器扫到了也看不出是什么服务。
Ubuntu上配置WireGuard很简单。在公网服务器上安装wireguard,生成私钥和公钥,配置/etc/wireguard/wg0.conf:
[Interface] Address = 10.0.0.1/24 ListenPort = 51820 PrivateKey = 服务端私钥 [Peer] PublicKey = 客户端公钥 AllowedIPs = 10.0.0.2/32
客户端同样配置,AllowedIPs里可以只写10.0.0.0/24,这样只有访问这个网段的流量走隧道,不影响正常上网。隧道建立后,你的frp或者其他穿透服务就可以只监听WireGuard的虚拟IP,比如10.0.0.1,完全不暴露在公网接口上。攻击者要突破这层防护,必须先攻破WireGuard的加密层,而WireGuard的代码量极小,审计难度低,安全性在业界有共识。
这种架构下,你的公网服务器上ufw只需要放行一个UDP 51820端口,其他所有端口都可以关闭。即使有人扫描到51820端口,WireGuard对未认证的数据包直接静默丢弃,不会有任何响应,扫描器无法判断这是什么服务。
证书和TLS,别用自签糊弄如果你的穿透服务最终要对外提供HTTPS访问,证书管理必须规范。很多人图省事用自签证书,然后在客户端忽略证书错误,这种习惯非常危险。自签证书无法防止中间人攻击,如果有人在你和服务器之间做流量劫持,你根本察觉不到。
现在Let's Encrypt的免费证书申请已经极其方便,Ubuntu上certbot一条命令就能搞定。即使你的服务跑在内网,只要有一个公网域名解析到你的穿透入口,就可以申请证书。对于frp这种场景,可以在公网服务器上申请证书,然后通过frp的https插件或者nginx反向代理来使用。证书每90天自动续期,配合crontab或者systemd timer完全不用人工干预。
如果是纯内网环境,没有公网域名,那至少要用内部CA来签发证书,并且在所有客户端上信任这个内部CA。虽然搭建内部CA有一定工作量,但这是保证内网通信不被窃听篡改的基础。用step-ca或者easy-rsa都能在Ubuntu上快速搭起来。
日志监控和异常告警,别等出事再看安全配置做得再好,也需要监控来兜底。Ubuntu服务器上,至少要监控这几类日志。一是/var/log/auth.log,记录所有认证相关事件,包括SSH登录、sudo提权。二是frp的运行日志,默认在控制台输出,建议重定向到文件并配置logrotate做轮转。三是ufw的日志,需要先在/etc/ufw/ufw.conf里把loglevel设为low或medium,然后日志会写入/var/log/ufw.log。
光有日志不够,要配上实时告警。简单的方案是用swatch或者logwatch做关键词匹配,发现异常就发邮件。更完善的方案是把日志接入ELK或者Loki,设置规则自动检测暴力破解、异常登录IP、非工作时间的访问等行为。比如检测到同一个IP在一分钟内SSH失败超过5次,或者有来自从未出现过的国家的IP成功登录,立刻触发告警。
还有一个容易被忽略的监控点是穿透工具本身的进程存活和配置变更。如果有人入侵后修改了frp配置,加了新的映射规则,你需要在第一时间知道。可以用auditd监控配置文件目录的变更,或者用inotifywait写个脚本,发现/etc/frp/frpc.ini有变动就发通知。
最小权限原则,贯穿每一个环节最后要强调的是,以上所有措施都要建立在最小权限原则上。frp客户端不要用root运行,创建一个专门的低权限用户来跑。这个用户只给它访问需要穿透的端口的权限,其他一律拒绝。内网服务本身也要做权限隔离,比如数据库账号只给必要的SELECT、INSERT权限,不给DROP、ALTER。穿透映射的时候,不要为了方便把一个端口映射到另一个相同端口就完事,要仔细评估这个服务暴露出去后,攻击者能做什么。
如果你用的是Cloudflare Tunnel这类隧道服务,它们通常提供了访问控制功能,比如在Cloudflare Zero Trust里配置Application,要求访问者先通过身份验证,支持邮箱验证码、GitHub登录、甚至硬件密钥。这些功能是免费的,不用白不用。多一层认证就多一层保护,即使服务本身有漏洞,攻击者连认证关都过不了。
内网穿透的安全防护没有一劳永逸的方案,它是一个持续调整的过程。今天配好的规则,明天可能因为业务变化需要修改。关键是养成习惯,每次新增穿透规则时,都问自己几个问题:这个端口真的需要暴露吗?暴露给谁?他们怎么证明身份?出问题了我能多快发现?把这几个问题想清楚了,你的Ubuntu服务器内网穿透才算真正配好了安全防护。
