Administrator
发布于 2026-09-02 / 5 阅读
0
0

FreeSWITCH 安装完别急着接公网:防盗打的最小安全配置

English
中文

FreeSWITCH 能注册、能呼入、能呼出,那么基本的就算配置完成了。此时直接把 5060、5080 和 8021 开到公网,离“可以上线”还差一些安全配置。

盗打通常不是某个配置配错了。攻击者扫到 SIP 端口,猜中分机口令,或者让未经认证的 INVITE 进入了带 bridge 的出站规则,电话就可能从你的运营商账户出局。Europol (欧洲刑警组织)对国际收益分成欺诈的描述很典型:呼叫量突然增大,通话时间偏长,目的地集中在少数高资费号码。账单只是结果,前面的每一层都应该有机会把它挡住。

这篇教程基于 Ubuntu 和 FreeSWITCH vanilla 配置,也接受软电话、移动网络和家庭宽带地址会变化这个现实。做完后,你应该能看到五个结果:合法分机能从动态公网地址注册;重复错误认证会触发临时封禁;只有运营商公布的地址能访问中继入口;普通分机只能拨明确放行的号码;未允许的国际或高资费号码在本机被拒绝,不会送到运营商。

先确认自己正在改哪套配置

软件包安装常见的配置根目录是 /etc/freeswitch,源码默认安装常见的是 /usr/local/freeswitch/confFreeSWITCH 官方入门文档也区分了这两种路径。先查运行中的实际值,别在错误目录里改半天:

# 读取运行中实例实际使用的配置目录和日志目录
fs_cli -x 'global_getvar conf_dir'
fs_cli -x 'global_getvar log_dir'

# 查看 SIP profile 和系统当前监听的常见端口
fs_cli -x 'sofia status'
sudo ss -lntup | rg ':(5060|5080|8021|5066|7443)\b'

如果系统没有 rg,可以改用 grep -E。把查到的配置目录记为 FS_CONF,下文示例按 /etc/freeswitch 写。改配置前复制一份备份,并确认磁盘空间足够。Profile 重启会中断该 Profile 上的现有通话,要放进维护窗口。

示例里的 203.0.113.0/24 是运营商信令网段占位符,属于文档保留地址,不能原样用于生产。分机侧按动态公网地址直注册设计,不设置来源 IP 白名单。

固定来源做白名单,动态分机只开放必要端口

把 SIP 端口从 5060 改成别的数字,只能少一点日志噪声,不能形成可靠防护。端口扫描照样能找到它,只是时间问题。运营商中继地址通常固定,可以按来源做白名单;公网分机地址经常变化,只能开放真正使用的传输协议,再让 FreeSWITCH 认证和自动封禁接手。

下面用 Ubuntu 自带的 UFW 演示。远程操作时,先允许你实际使用的 SSH 入口,再启用防火墙。否则可能把自己锁在服务器外面。Ubuntu 官方文档支持按来源网段和目标端口编写规则。

# 先保留 SSH 管理入口;如果 SSH 不是默认配置,请按真实端口和来源 IP 固定
sudo ufw allow OpenSSH

# 默认拒绝新建入站连接,保留正常出站连接
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 只允许运营商信令网段访问 external profile;按真实传输协议删减 UDP 或 TCP
sudo ufw allow proto udp from 203.0.113.0/24 to any port 5080
sudo ufw allow proto tcp from 203.0.113.0/24 to any port 5080

# 公网分机使用动态地址时,开放实际使用的 internal SIP 传输
# 本例只演示 UDP;话机不用 TCP 时不要顺手开放 5060/tcp
sudo ufw allow 5060/udp

# 公网分机还需要媒体可达;端口范围必须与 vars.xml 保持一致
# 若已按 Profile 拆分媒体地址或边界防火墙,请按实际拓扑进一步收紧
sudo ufw allow 16384:32768/udp

# 检查规则后再启用,并保留防火墙日志
sudo ufw status verbose
sudo ufw logging on
sudo ufw enable

运营商的 SIP 信令地址和 RTP 媒体地址可能不是一批 IP。白名单写错时,常见现象是来话无声、单通或注册反复掉线。运营商入口不要为了省事改成 0.0.0.0/0,应该索取完整的信令网段、媒体网段和传输协议。若所有话机都支持 SIP over TLS,可改为只开放 TLS 监听,但这还需要正确的证书、终端校验和配套 Profile 配置,不能只把端口换成 5061。

云服务器还要同步检查安全组。主机 UFW 放行而云安全组拒绝,业务不通;云安全组对全网开放而主机规则被误清空。

FreeSWITCH ACL 再拦一次

主机防火墙挡在 FreeSWITCH 外面,ACL 在应用内部复核来源。两层使用同一份经过确认的地址清单,能减少某一层误改后的影响。

autoload_configs/acl.conf.xml<network-lists> 内增加运营商列表:

<!-- 运营商中继白名单:默认拒绝,只允许官方公布的信令网段 -->
<list name="trusted-carriers" default="deny">
  <node type="allow" cidr="203.0.113.0/24"/>
</list>

sip_profiles/external.xml<settings> 中应用运营商白名单,同时保留 public context:

<!-- 外部中继来话只能进入 public context,不能直达带出站能力的 default context -->
<param name="context" value="public"/>

<!-- external profile 常按运营商 IP 建立信任,未通过 ACL 的请求直接拒绝 -->
<param name="apply-inbound-acl" value="trusted-carriers"/>
<param name="auth-calls" value="false"/>

auth-calls=false 不是让全世界免密调用你的网关。它适合运营商中继,前提是来源 IP 已经被防火墙和 ACL 限住,后面的 public dialplan 也只接收你拥有的号码。

动态公网分机不能套用 apply-register-acl,否则地址一变就会注册失败。应在 sip_profiles/internal.xml<settings> 中显式收紧认证:

<!-- internal profile 上的 INVITE 必须完成 SIP 摘要认证 -->
<param name="auth-calls" value="true"/>

<!-- 盲注册和盲认证只适合测试环境,生产配置显式关闭 -->
<param name="accept-blind-reg" value="false"/>
<param name="accept-blind-auth" value="false"/>

<!-- 注册认证用户名必须与 From 用户一致,减少身份混用 -->
<param name="inbound-reg-force-matching-username" value="true"/>

<!-- 业务不需要一号多终端时关闭多注册;共享分机不要照抄这一项 -->
<param name="multiple-registrations" value="false"/>

官方 Sofia Profile 文档说明,accept-blind-regaccept-blind-auth 只适合测试,inbound-reg-force-matching-username 用于拒绝认证用户名与 From 用户不一致的注册。multiple-registrations=false 会影响同一分机的多终端场景,确认业务不需要后再显式设置。

官方 ACL 文档说明,reloadacl 会重新加载 XML 并重建网络列表。apply-inbound-acl 和 internal Profile 参数变更需要安排维护窗口重启对应 Profile:

# 先验证 XML,再重载 ACL;命令返回错误时不要继续重启 Profile
fs_cli -x 'reloadxml'
fs_cli -x 'reloadacl'

# Profile 重启会影响现有通话,只在维护窗口执行
fs_cli -x 'sofia profile external restart'
fs_cli -x 'sofia profile internal restart'

把重复注册失败交给 Fail2ban

公网直注册意味着 5060 会持续收到扫描和口令猜测。FreeSWITCH 的 mod_fail2ban 会监听 Sofia 事件,把明确的注册失败写进独立日志,再由系统 Fail2ban 封禁来源。它不是所有软件包都默认加载,先确认模块和日志目录:

# 确认模块是否存在;返回 false 时应安装或构建与当前版本匹配的模块
fs_cli -x 'module_exists mod_fail2ban'

# 模块存在但未加载时加载它,并确认专用日志所在的根目录
fs_cli -x 'load mod_fail2ban'
fs_cli -x 'global_getvar log_dir'

为了让重启后仍自动加载,在 autoload_configs/modules.conf.xml 中加入下面一行。模块配置 autoload_configs/fail2ban.conf.xml 的默认日志通常是 $${log_dir}/fail2ban.log,但仍要在本机核对。

<!-- 记录 Sofia 注册失败事件,供系统 Fail2ban 读取 -->
<load module="mod_fail2ban"/>

用 Ubuntu 软件包安装系统 Fail2ban:

# 从系统仓库安装,避免与发行版的 systemd 和 UFW 配置脱节
sudo apt update
sudo apt install fail2ban

然后新建 /etc/fail2ban/filter.d/freeswitch-register.local

[Definition]
# 中文注释:锚定 mod_fail2ban 的完整事件行,避免普通日志文本误封地址
failregex = ^A registration failed user\[[^]]*\] ip\[<HOST>\] at\[.*\]$
# 中文注释:当前没有额外排除式;管理地址应通过 jail 的 ignoreip 单独配置
ignoreregex =

再新建 /etc/fail2ban/jail.d/freeswitch-register.local。下列阈值只是起点,呼叫中心、NAT 出口共享地址和批量终端上线时必须按正常失败基线调整:

[freeswitch-register]
# 中文注释:启用 FreeSWITCH 注册失败监控,并引用上面的过滤器
enabled = true
filter = freeswitch-register
# 中文注释:替换成 global_getvar log_dir 返回目录下的真实文件
logpath = /var/log/freeswitch/fail2ban.log
# 中文注释:官方 ufw 动作要求 UFW 已启用;封禁后应检查实际生成的规则
banaction = ufw
# 中文注释:十分钟八次失败后封一小时,必须结合正常业务调整
maxretry = 8
findtime = 10m
bantime = 1h

应用前先测试正则,避免一个宽泛表达式把正常请求误封。确认有匹配、无误匹配后再重启服务:

# 用真实日志验证过滤器;Matched 与 Missed lines 都要抽样检查
sudo fail2ban-regex /var/log/freeswitch/fail2ban.log /etc/fail2ban/filter.d/freeswitch-register.local

# 重启后确认 jail 已启动,并观察当前失败与封禁计数
sudo systemctl restart fail2ban
sudo fail2ban-client status freeswitch-register

FreeSWITCH 官方模块文档给出了注册失败日志格式;Fail2ban 官方 UFW 动作要求 UFW 已启用。不要从承载 SSH 管理的同一个公网 IP 做错误口令压测。测试地址被封后,可用 sudo fail2ban-client set freeswitch-register unbanip 测试IP 解封。

Fail2ban 只能压低同一地址的重复请求。攻击者轮换 IP 时效果会下降;凭据已经泄露并成功认证时,它也不会触发。后面的出站权限、运营商限额和监控仍是必须项。

删掉演示账号,也别让所有分机共用一个密码

Vanilla 配置默认带有 1000~1019 共 20 个测试用户,并通过 default_password 共用 1234。这不是猜测,官方入门文档直接列出了这些默认值。未使用的测试用户应移出 directory/default/ 的加载范围。还在使用的用户要改成独立账号,并给每个分机单独生成随机口令。

# 生成一个 48 位十六进制随机口令;每个分机重新生成一次
openssl rand -hex 24

用户目录可以这样配置:

<include>
  <!-- 2001 是示例分机号,password 必须替换成刚生成的独立随机值 -->
  <user id="2001">
    <params>
      <param name="password" value="REPLACE_WITH_RANDOM_SECRET"/>
    </params>
    <variables>
      <!-- 认证成功后进入内部拨号上下文 -->
      <variable name="user_context" value="default"/>

      <!-- 只授予国内移动号码权限;标签名称由运维自行定义 -->
      <variable name="toll_allow" value="domestic-mobile"/>
      <variable name="accountcode" value="2001"/>
    </variables>
  </user>
</include>

分机号不是秘密,改成生僻号码也不能代替强口令。口令不要复用运营商后台、SSH 或其他分机的密码,也不要写进工单截图和日志。离职、设备丢失或软电话配置泄露时,只撤销对应分机,不必让所有话机一起换密码。

外部来话只能经过 public context

FreeSWITCH 官方 public context 文档把这条隔离线写得很清楚:external profile 收到的未认证来话进入 public,认证后的内部用户才使用带完整功能的 default。把 external 的 context 改成 default,等于让外部请求接触内部出站规则。

检查 dialplan/public/ 时,只保留真实 DID 的明确匹配。下面把一个示例号码转给内部 2001 分机:

<include>
  <extension name="inbound-main-number">
    <!-- 只匹配运营商分配给你的完整 DID,不使用任意数字通配 -->
    <condition field="destination_number" expression="^861234501234$">
      <!-- 命中后只转到明确的内部目标 -->
      <action application="transfer" data="2001 XML default"/>
    </condition>
  </extension>
</include>

不要在 public 里写 ^\d+$ 后直接 bridge 到运营商,也不要把任意外部号码 transferdefault 再继续匹配。来话显示号码可以伪造,不能拿 Caller ID 当认证凭据。

出站规则从默认拒绝开始

分机通过认证,不代表它应该拥有所有呼叫权限。toll_allow 能给用户打标签,但只有 dialplan 实际检查这个变量时才生效。官方拨号条件文档也明确把目录变量和 dialplan 条件分成了两步。

下面的例子只允许带 domestic-mobile 权限的用户拨打手机号。它接受 11 位号码或带 86 的号码,送给运营商前统一补成 86 格式:

<include>
  <extension name="allow-mainland-mobile">
    <!-- 先核对认证用户是否拥有这一类出站权限 -->
    <condition field="${toll_allow}" expression="(^|,)domestic-mobile(,|$)">
      <!-- 只接收明确号码格式,拒绝空号、短号和任意国际前缀 -->
      <condition field="destination_number" expression="^(?:86)?(1[3-9]\d{9})$">
        <!-- carrier 必须替换成实际 gateway 名称 -->
        <action application="bridge" data="sofia/gateway/carrier/86$1"/>
        <anti-action application="hangup" data="CALL_REJECTED"/>
      </condition>
      <anti-action application="hangup" data="CALL_REJECTED"/>
    </condition>
  </extension>
</include>

这段规则要替换原有的宽泛出站规则,不是和 ^\d+$^(.+)$ 一起留着。旧规则如果先匹配并完成 bridge,新限制根本没有机会执行。固定电话、紧急号码、客服短号等业务确实需要时,再为它们增加单独的明确规则和权限标签。

国际电话和高资费号码默认不写路由。确实有人需要时,为少数账号增加独立权限,并按国家或号码段放行。不要给所有用户一个 international 标签,也不要用 00+ 后面任意数字的规则一次全开。

把 Event Socket 留在本机

Event Socket 默认常用 8021 端口。客户端通过认证后可以执行 FreeSWITCH API 命令,它的风险远高于一个普通查询端口。官方 Event Socket 文档建议绑定明确地址、设置窄范围 ACL 和强密码。

只供本机 fs_cli 或本机程序使用时,在 autoload_configs/event_socket.conf.xml 中写清楚:

<configuration name="event_socket.conf" description="Socket Client">
  <settings>
    <!-- 只监听本机,公网和局域网主机都不能直接连接 -->
    <param name="listen-ip" value="127.0.0.1"/>
    <param name="listen-port" value="8021"/>

    <!-- 使用独立强口令,不保留示例值 ClueCon -->
    <param name="password" value="REPLACE_WITH_ANOTHER_RANDOM_SECRET"/>
    <param name="apply-inbound-acl" value="loopback.auto"/>
  </settings>
</configuration>

远程管理优先通过 SSH 隧道或专用管理网进入,不要给 UFW 增加一条对全网开放 8021 的规则。业务系统必须远程连接时,绑定专用内网地址,再用独立管理网段 ACL 限制来源。

用 CDR 和运营商限制缩短止损时间

防护配置可能被人改错,分机终端也可能泄露。运营商侧再加一层限制很有价值:关闭不使用的国际和高资费目的地,限制最大并发,设置消费上限与异常通知。每家运营商提供的功能和名称不同,阈值也应该按你的正常话量来定,不适合照抄一个固定数字。

FreeSWITCH vanilla 默认加载 mod_cdr_csv。根据官方 CDR 文档,主记录通常位于 FreeSWITCH 日志目录下的 cdr-csv/Master.csv。先确认模块和实际目录:

# 确认 CDR 模块已加载,并找到当前日志根目录
fs_cli -x 'module_exists mod_cdr_csv'
fs_cli -x 'global_getvar log_dir'

# 观察当前通话与注册状态,基线应来自你的正常业务时段
fs_cli -x 'show calls count'
fs_cli -x 'sofia status profile internal reg'

监控至少要能发现这些变化:短时间内呼叫量突然上升;同一分机集中拨向陌生国家或号码段;非工作使用时段出现持续长话;运营商余额或账单增长偏离正常水平。CDR 用来发现和追溯,不能替代前面的阻断规则。

按五个结果做验收

不要只看 reloadxml 返回 +OK。安全配置要从不同网络位置真的试一遍,而且不要拨真实高资费号码。

  • 从一个动态公网地址使用正确凭据注册测试分机,预期注册成功并能在 sofia status profile internal reg 中看到。
  • 从不承载 SSH 管理的测试地址反复提交错误口令,达到阈值后预期出现在 fail2ban-client status freeswitch-register 的封禁列表中;测试结束后及时解封。
  • 从非运营商地址向 5080 发送 SIP 请求,预期被防火墙或 ACL 拒绝;合法运营商来话仍能命中真实 DID。
  • 使用带 domestic-mobile 权限的测试分机拨一个你控制的大陆手机号,预期正常接通并产生 CDR。
  • 拨一个不在允许格式中的测试号码,例如 00870123456789,预期 FreeSWITCH 本机返回拒绝,运营商后台没有对应呼叫尝试。

验收失败时,沿着“主机防火墙 → FreeSWITCH ACL/Fail2ban → SIP 认证 → context → toll_allow → 号码规则 → gateway”这个顺序查。它和一次呼叫实际经过的路径一致,定位会快很多。

容易踩坑的点

Fail2ban 要等失败发生后才封 IP,还会遇到攻击者换地址的问题。它适合保护无法按来源白名单收口的公网分机入口;地址固定的运营商入口仍应在防火墙和 ACL 直接拒绝无关来源。

TLS 能保护 SIP 信令在传输中的内容,却不会自动修复宽泛的出站规则,也不会替你关闭国际目的地。它值得部署,但不要把“启用了 TLS”理解成“不会盗打”。

WebSocket 5066、WSS 7443、IPv6 SIP Profile 和管理面板经常被漏查。没有使用就关闭或不放行;正在使用就给它们同样的来源限制、认证和日志策略。IPv4 防火墙规则不会自动保护 IPv6。

上线前按这个清单检查一下

  • 未使用的 vanilla 测试账号已经移除,所有分机使用独立随机口令。
  • 5060 只开放话机实际使用的传输,强认证和注册失败封禁已经生效;5080 只对运营商信令地址开放。
  • RTP 网段按运营商资料配置,通话双向语音已经验证。
  • external profile 保持 public context,public 中没有任意号码出站或无条件转入 default 的规则。
  • 每个分机按需求授予 toll_allow,出站 dialplan 同时检查权限和明确号码格式。
  • 8021 绑定本机或专用管理网,示例口令已经替换。
  • CDR 正常落盘,运营商侧的目的地、并发、消费上限和通知已经设置。
  • 已从动态公网完成正确注册、错误认证封禁、运营商呼入、允许呼出和拒绝呼出的验收。

如果你需要排查现有 FreeSWITCH 是否还留着盗打路径,可以准备 FreeSWITCH 版本、安装方式、已脱敏的 SIP Profile、ACL、directory、public/default dialplan、监听端口,以及运营商提供的信令和媒体网段。请不要发送分机密码、运营商密钥、真实账单或内部 IP。相关代码和联系方式可以从我的 GitHub个人博客找到。


评论