ocserv 1.1.3 安全加固指南

ocserv 1.1.3 安全加固:账号锁定与证书认证、TLS 收紧、路由与 NAT 隔离、内建 ban + fail2ban 双层防爆破、审计日志与调试泄漏治理,全部措施带实测验证(2026-10-26)。

ocservVPN网络2026/10/26

ocserv 1.1.3 安全加固指南

目标:给出 ocserv 1.1.3 生产安全加固的完整路径——攻击面分析、认证与账号控制(账号锁定、证书认证)、TLS 版本收紧、网络与防火墙隔离、双层防爆破(内建 ban 计分 + fail2ban 联动)、审计与日志治理,每项措施均带「修改-验证」闭环实测。全部命令与回显取自 Ubuntu 22.04 + ocserv 1.1.3-1 服务器实测(2026-10-26);安装与参数基线见《Ubuntu 22.04 安装 ocserv 1.1.3 指南》与《ocserv 1.1.3 配置参数详解》。

一、环境信息

  • 操作系统:Ubuntu 22.04(jammy),x86_64;服务器主机名 vpn-server,服务地址 10.89.65.10。
  • ocserv 版本:1.1.3-1(Ubuntu 22.04 仓库版),systemd 托管,单元 /lib/systemd/system/ocserv.service。
  • fail2ban:1.0.2-2ubuntu0.1(Ubuntu 22.04 仓库版,防爆破联动用)。
  • 网络拓扑:VPN 服务 8132/TCP+UDP;VPN 地址池 172.31.100.0/24;受保护内网段 10.89.66.0/24(内网 Web 服务器 10.89.66.20,仅经 VPN 可达);客户端网段 10.89.65.0/24(客户端机 10.89.65.20)。
  • 演示账号:admin / q1w2e3r4(本地密码文件认证;加固篇另签发同用户客户端证书)。
  • 关键路径:配置 /etc/ocserv/ocserv.conf、证书 /etc/ocserv/certs/、密码文件 /etc/ocserv/ocpasswd、fail2ban 过滤器 /etc/fail2ban/filter.d/ocserv.conf。

二、攻击面分析与加固路线图

ocserv 1.1.3 的暴露面集中在四个层面,逐层对应的加固措施与本文实测章节如下:

攻击面威胁加固措施实测章节
认证通道(443/8132 HTTPS)口令爆破、账号失窃账号锁定/解锁治理、证书认证替代口令、双因素四章
TLS 握手层降级到旧版本 TLS、弱密码套件tls-priorities 显式禁用 TLS1.0/1.1五章
数据转发面客户端横向扫内网、越权路由split 路由最小化 + NAT 隔离 + 转发面限制六章
管理与日志面暴力攻击无人告警、调试日志泄漏口令内建 ban 计分 + fail2ban 联动、日志审计七、八章

加固总原则(与官方配置注释口径一致):认证尽量去口令化、TLS 只留现代版本、客户端只给必要路由、封禁自动化、日志常态化审计。

三、加固前基线快照

安装指南交付后的基线(/etc/ocserv/ocserv.conf 关键项实查):

auth = "plain[passwd=/etc/ocserv/ocpasswd]"
tcp-port = 8132
udp-port = 8132
tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-RSA:-VERS-SSL3.0:-ARCFOUR-128"
max-same-clients = 1
max-ban-score = 80
ban-reset-time = 300
isolate-workers = false

基线状态的三点评估:认证为纯口令(可被爆破,四章解决);TLS 策略未显式禁用 TLS1.0/1.1(旧客户端可协商降版本,五章解决);isolate-workers 未开启(worker 无 seccomp 隔离,官方注释口径约 2% 吞吐开销,建议生产开启——本章不改它,保持基线,理由见十章 FAQ)。

四、身份认证与账号控制加固

4.1 账号锁定与解锁(ocpasswd -l / -u)

离职、疑似失窃等场景需要立即停用账号。ocpasswd -l 锁定后密码文件哈希前出现 ! 标记(实测):

ocpasswd -c /etc/ocserv/ocpasswd -l admin
cat /etc/ocserv/ocpasswd
admin:*:!$5$n6zo9hgYmCEbwPC4$peVBbkh7acoRC53pxAgLNr6A6nQNAiexqUbAfGVw9tB

锁定立即生效、无需重启服务——客户端仍拿原凭据登录直接失败(实测回显):

POST https://10.89.65.10:8132/auth
POST https://10.89.65.10:8132/auth
Login failed.

解锁(-u)同样即时生效,登录恢复:

ocpasswd -c /etc/ocserv/ocpasswd -u admin
cat /etc/ocserv/ocpasswd
admin:*:$5$n6zo9hgYmCEbwPC4$peVBbkh7acoRC53pxAgLNr6A6nQNAiexqUbAfGVw9tB
Connected to HTTPS on 10.89.65.10 with ciphersuite (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
Configured as 172.31.100.165, with SSL + LZ4 connected and DTLS + LZ4 connected

⚠️ 注意:-l 只锁密码文件账号;证书认证的用户凭据在客户端证书本身,吊销需走 CA 侧 CRL(crl 配置项),两套账号的「停用动作」不同,混用时都要演练。

4.2 切换客户端证书认证(auth = certificate)

口令认证的根治是证书认证:客户端持有 CA 签发的证书才能连,且全程无口令可泄。安装指南 4.2 节已生成 client-cert.pem/client-key.pem(CN/uid=admin),此处把主认证从 plain 切到 certificate(实测):

sed -i "s|^auth = \"plain\[passwd=/etc/ocserv/ocpasswd\]\"|auth = \"certificate\"|" /etc/ocserv/ocserv.conf
grep -n "^auth" /etc/ocserv/ocserv.conf
42:auth = "certificate"
300:auth-timeout = 240
systemctl restart ocserv
systemctl is-active ocserv
active

启动日志确认主认证方法切换(实测):

note: vhost:default: setting 'certificate' as primary authentication method

负样本一:certificate 模式下不带客户端证书、只递账号密码——认证直接走不通(实测):

HTTP body length:  (250)
Please enter your username.
Failed to complete authentication

负样本二:客户端证书与当前 CA 不匹配——服务端日志给出明确吊验原因(实测):

worker: 10.89.65.20 client certificate verification failed: The certificate is NOT trusted. The certificate issuer is unknown.
ocserv[4433]: GnuTLS error (at worker-vpn.c:861): Error in the certificate.

正样本:使用当前 CA 签发的证书连接——无任何口令输入,直接建隧道(实测回显节选,--certificate 指定客户端证书与私钥):

sudo openconnect --servercert "pin-sha256:MWbNlxA33/F2BwGPcRqmoVugLFpq0ANDVzd2QCjDTSY=" \
  --certificate /etc/ocserv/certs/client-cert.pem --sslkey /etc/ocserv/certs/client-key.pem 10.89.65.10:8132
Using client certificate 'admin'
Server certificate verify failed: signer not found
Connected to HTTPS on 10.89.65.10 with ciphersuite (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)

服务端会话核对:用户名取自证书 uid(cert-user-oid = 0.9.2342.19200300.100.1.1),DTLS 数据通道正常(实测):

occtl show users
      id     user    vhost             ip         vpn-ip device   since    dtls-cipher    status
    4476    admin  default    10.89.65.20 172.31.100.165  vpns0     5s  (AES-256-GCM) connected

验证后切回口令认证(本专栏后续实验基于 plain 口令):

sed -i "s|^auth = \"certificate\"|auth = \"plain[passwd=/etc/ocserv/ocpasswd]\"|" /etc/ocserv/ocserv.conf
grep -n "^auth " /etc/ocserv/ocserv.conf
42:auth = "plain[passwd=/etc/ocserv/ocpasswd]"
systemctl restart ocserv
systemctl is-active ocserv
active

认证加固取舍:certificate 的强度最高(私钥不出机),但发证/吊销流程重;plain+OTP(auth = "plain[...]" 与 enable-auth = "plain[...]" 组合、或 plain[passwd=...,otp-template=...])可低成本加第二因素,1.1.3 支持 HOTP/TOTP。用户名与证书身份映射的细节见 ocserv(8) 手册页「certificate auth」一节。

4.3 组与每用户管控

ocpasswd -g <组> 把账号划入组,配合 config-per-group 目录可按组下发路由与限制(参数级实测见《ocserv 1.1.3 配置参数详解》5.8 节);证书认证时组名取自证书 cert-group-oid。最小权限原则:按「人 → 组 → 路由/端口」三级收敛,而不是人人全路由。

五、传输加密与 TLS 收紧

基线 TLS 策略未禁 TLS1.0/1.1(三章快照),先验证再收紧。用 gnutls-cli 强制 TLS1.1 握手(服务器本机自测):

gnutls-cli --x509cafile /etc/ocserv/certs/ca-cert.pem --priority "NORMAL:-VERS-ALL:+VERS-TLS1.1" -p 8132 10.89.65.10 </dev/null
*** Fatal error: Error in the certificate.

基线策略下 TLS1.1 握手可以通过(报错只发生在证书主机名校验这一步)——即旧版本通道真实存在。收紧配置(tls-priorities,改后 ocserv -t 校验并重启):

tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-RSA:-VERS-SSL3.0:-ARCFOUR-128:-VERS-TLS1.0:-VERS-TLS1.1"
ocserv -t -c /etc/ocserv/ocserv.conf >/dev/null 2>&1; echo rc=$?
rc=0
systemctl restart ocserv

复测 TLS1.1——直接被拒(实测):

gnutls-cli --x509cafile /etc/ocserv/certs/ca-cert.pem --priority "NORMAL:-VERS-ALL:+VERS-TLS1.1" -p 8132 10.89.65.10 </dev/null
*** Fatal error: The TLS connection was non-properly terminated.
gnutls-cli --priority "NORMAL:-VERS-ALL:+VERS-TLS1.1" -p 8132 10.89.65.10 </dev/null
*** Fatal error: The TLS connection was non-properly terminated.

现代版本回归:TLS1.2 正常协商(实测),TLS1.3 正常建隧道(实测):

openssl s_client -tls1_2 -connect 10.89.65.10:8132 </dev/null
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
Connected to HTTPS on 10.89.65.10 with ciphersuite (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
Configured as 172.31.100.165, with SSL + LZ4 connected and DTLS + LZ4 connected

结论:%COMPAT 与新客户端共存无碍,禁 TLS1.0/1.1 后老客户端会被硬拒——上线前先盘点存量客户端版本。需要更极端时可在优先级串继续追加 :-VERS-DTLS1.0 等;套件级黑名单(如 :-AES-128-GCM)同理按需追加。

六、网络与防火墙隔离

6.1 路由最小化(split-include)

客户端只应看到必须访问的内网段。基线只推送一条路由(实查配置):

route = 10.89.66.0/24

客户端路由表实查(只有 VPN 网段与推送路由,无默认路由劫持):

ip route | grep tun0
1.1.1.1 dev tun0 scope link
8.8.8.8 dev tun0 scope link
10.89.66.0/24 dev tun0 scope link
172.31.100.0/24 dev tun0 scope link

6.2 内网段隔离验证(不经 VPN 不可达)

隔离的判据是「拨不上 VPN 就摸不到内网」。负样本:客户端不经 VPN 直接访问内网 Web 服务器(实测,4 秒超时无响应):

curl -s --max-time 4 -o /dev/null -w "HTTP:%{http_code}\n" http://10.89.66.20/
HTTP:000
echo "rc=$?"
rc=28

正样本:建隧道后同一目标可达(实测):

ip -br addr show tun0
tun0             UNKNOWN        172.31.100.165/32 fe80::fc58:62ec:7671:4804/64
curl -s --max-time 5 http://10.89.66.20/
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Intranet Portal</title></head>

可达性由 NAT 与转发共同支撑(基线配置,实查):

sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
iptables -t nat -S POSTROUTING | grep ocserv
-A POSTROUTING -s 172.31.100.0/24 -o eth0 -j MASQUERADE

收敛建议:MASQUERADE 的源地址只写 VPN 池(172.31.100.0/24)而非全网卡子网;服务器本机对外只放行 8132/TCP+UDP 与 SSH;有条件时在 VPN 池与内网之间加 FORWARD 链白名单(仅放行到 10.89.66.0/24 的必要端口)。另有配置级限制键 restrict-user-to-routes(客户端流量被限定在推送路由内,由 ocserv-fw 下发 iptables)与 restrict-user-to-ports,语法见 ocserv(8) 手册页;本专栏未对这两个键单独实测,启用前请先在测试环境演练。

七、自动化防爆破:内建 ban 与 fail2ban 联动

7.1 内建 ban 计分(无外部依赖的第一道闸)

ocserv 对每个来源 IP 记分:错口令尝试 +10 分、纯连接 +1 分、KKDCP 请求 +1 分,累计达到 max-ban-score 即封禁 min-reauth-time 秒(配置见三章基线:80 分 / 300 秒)。实测:对 admin 连续 8 次递错口令,服务端日志逐次记账(实测回显):

Oct 26 01:38:41 vpn-server ocserv[3351]: worker[admin]: 10.89.65.20 failed cookie authentication attempt

计分与封禁状态通过 occtl 审计(实测):

occtl show ip ban points
            IP          score
   10.89.65.20             80
occtl show ip bans
            IP          score                        expires
   10.89.65.20             80               2026-10-26 01:44 ( 5m:00s)

封禁期间客户端连接直接失败(实测):

Failed to connect to host 10.89.65.10
Failed to open HTTPS connection to 10.89.65.10
Failed to complete authentication

管理员解封即时恢复(实测):

occtl unban ip 10.89.65.20
IP '10.89.65.20' was unbanned

内建 ban 的价值是零依赖;局限是按阈值自动封自动解,没有告警推送,也不封「慢速爆破」。第二道闸交给 fail2ban。

7.2 fail2ban 安装与 ocserv jail(systemd 日志后端)

Ubuntu 22.04 仓库安装 fail2ban(实测回显节选):

apt install -y fail2ban
Executing: /lib/systemd/systemd-sysv-install enable fail2ban
Created symlink /etc/systemd/system/multi-user.target.wants/fail2ban.service → /lib/systemd/system/fail2ban.service.
systemctl is-active fail2ban
active

写 ocserv 专用过滤器 /etc/fail2ban/filter.d/ocserv.conf——匹配两类日志:认证失败行与 ban 计分行(对应 1.1.3 的真实日志格式,见 7.1/八章回显):

[Definition]
failregex = ^worker\[[^\]]*\]: <HOST> failed cookie authentication attempt$
            ^main: added \d+ points \(total \d+\) for IP '<HOST>' to ban list$
journalmatch = _SYSTEMD_UNIT=ocserv.service

jail 定义 /etc/fail2ban/jail.d/ocserv.local——用 systemd 日志后端直读 ocserv 单元日志(不依赖 rsyslog 文件):

[ocserv]
enabled  = true
backend  = systemd
port     = 8132
protocol = tcp
filter   = ocserv
maxretry = 3
findtime = 300
bantime  = 600

💡 技术拓展:ocserv 认证失败日志默认走 journal(authpriv 语义),两种采集后端对比——

后端写法取数来源取舍
systemdbackend = systemd + journalmatchjournalctl 直读单元无需 rsyslog,即时性好,本文实测采用
文件logpath = /var/log/auth.logrsyslog 落盘文件需先配 rsyslog 转发,适合集中式日志栈

加载并核对 jail(实测回显,Journal matches 一行证明 systemd 后端生效):

systemctl restart fail2ban
fail2ban-client status
Status
|- Number of jail:	1
`- Jail list:	ocserv
fail2ban-client status ocserv
Status for the jail: ocserv
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	0
|  `- Journal matches:	_SYSTEMD_UNIT=ocserv.service
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:

上线前先用 fail2ban-regex 对历史 journal 做正则回放验证(实测,4 条 ban 计分行全部命中):

fail2ban-regex --journalmatch "_SYSTEMD_UNIT=ocserv.service" \
  '^worker\[[^\]]*\]: <HOST> failed cookie authentication attempt$|^main: added \d+ points \(total \d+\) for IP '"'"'<HOST>'"'"' to ban list$' systemd-journal
4
Oct 26 01:45:33 vpn-server ocserv[3527]: main: added 10 points (total 10) for IP '10.89.65.20' to ban list
Oct 26 01:45:33 vpn-server ocserv[3527]: main: added 10 points (total 20) for IP '10.89.65.20' to ban list
Oct 26 01:45:33 vpn-server ocserv[3527]: main: added 10 points (total 30) for IP '10.89.65.20' to ban list
Oct 26 01:45:33 vpn-server ocserv[3527]: main: added 10 points (total 40) for IP '10.89.65.20' to ban list

7.3 fail2ban 封禁实测(iptables 层生效)

连续错口令触发后,fail2ban 计数推进并落 ban(实测):

fail2ban-client status ocserv
Status for the jail: ocserv
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	3
|  `- Journal matches:	_SYSTEMD_UNIT=ocserv.service
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:

ban 落地后,iptables 里出现 f2b-ocserv 专用链并 REJECT 目标(实测快照):

iptables -S f2b-ocserv
-N f2b-ocserv
-A f2b-ocserv -s 10.89.65.20/32 -j REJECT --reject-with icmp-port-unreachable
-A f2b-ocserv -j RETURN
fail2ban-client status ocserv
Status for the jail: ocserv
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	4
|  `- Journal matches:	_SYSTEMD_UNIT=ocserv.service
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	10.89.65.20

与内建 ban 的关键差异:f2b 封禁在 iptables 层,已建立的会话也会被掐断——在线客户端实测收到服务器主动断开(实测):

Send BYE packet: Server request
Session terminated by server; exiting.

解封即时恢复(fail2ban-client set ocserv unbanip 10.89.65.20),f2b-ocserv 链恢复为只有 RETURN。

7.4 双层防爆破参数组合建议

两层闸门同时启用时的推荐组合(本环境实测值):

层参数推荐值依据
ocserv 内建max-ban-score80(仅内建)或 400(与 f2b 并用时放宽)80 分 ≈ 8 次错口令即封(实测);并用 f2b 时主防交 f2b,避免误伤共享出口 IP
ocserv 内建min-reauth-time300封禁时长,实测 5m:00s 到期自动解封
fail2banmaxretry/findtime3 次 / 300 秒实测 3 次失败即 ban
fail2banbantime600比 ocserv 内建的 300 秒长一档

八、审计与监控

8.1 日志形态与关键字段

ocserv systemd 日志是审计主源。四类关键字段(实测回显):

启动与认证方法(审计配置变更):

note: skipping 'pid-file' config option
note: vhost:default: setting 'plain' as primary authentication method
note: setting 'file' as supplemental config option

会话签发与认证(审计谁在连):

sec-mod: sec-mod instance 0 issue cookie
sec-mod: using 'plain' authentication to authenticate user (session: xTx542)
worker[admin]: 10.89.65.20 failed cookie authentication attempt

ban 计分与封禁(审计爆破行为):

main: added 10 points (total 10) for IP '10.89.65.20' to ban list

证书吊验(审计证书信任问题):

worker: 10.89.65.20 client certificate verification failed: The certificate is NOT trusted. The certificate issuer is unknown.
ocserv[4433]: GnuTLS error (at worker-vpn.c:861): Error in the certificate.

会话级明细用 occtl(实测 occtl show users,见 4.2 正样本;occtl show status 含 Total authentication failures、IPs in ban list 计数,可对接监控抓取)。occtl 支持 JSON 输出(occtl -j show users),适合接入自动化告警。

8.2 调试级别泄漏警告(严禁生产常开)

排障时临时开 -d 4 调试级,journal 会记录认证请求原文,含明文口令(实测回显,口令为演示约定值):

journalctl -u ocserv --no-pager -o cat | grep config-auth
<config-auth client="vpn" type="auth-reply" aggregate-auth-version="2"><version who="vpn">v8.20-1</version><device-id>linux-64</device-id><capabilities><auth-method>single-sign-on-v2</auth-method></capabilities><auth><password>q1w2e3r4</password></auth></config-auth>

⚠️ 注意:-d 4 仅限排障临时开启,用完立即恢复单元默认配置并 systemctl daemon-reload && systemctl restart ocserv;对已落盘的 journal 可用 journalctl --vacuum-time 收敛。证书认证模式无口令可泄,从根上规避此类泄漏。

九、加固后整体验证清单

逐项复验的实操清单(全部带本篇实测依据):

#验证项命令/方法预期
1账号可锁定ocpasswd -l 后客户端登录Login failed.(实测 4.1)
2证书认证生效auth = "certificate" 后无证书连接Failed to complete authentication(实测 4.2)
3证书信任链正确错误 CA 签发的证书连接服务端 certificate issuer is unknown(实测 4.2)
4TLS1.0/1.1 已禁gnutls-cli 强制 TLS1.1non-properly terminated(实测五章)
5TLS1.2/1.3 正常openssl s_client / openconnect 重连TLS1.2 协商 / TLS1.3 建隧道(实测五章)
6路由最小化客户端 ip route | grep tun0仅推送的 10.89.66.0/24 等(实测 6.1)
7内网隔离不拨 VPN curl 内网超时无响应(实测 6.2)
8内建 ban 生效8 次错口令score 80、封 5 分钟(实测 7.1)
9fail2ban 生效3 次错口令f2b-ocserv 链 REJECT、在线会话被断(实测 7.3)
10无调试泄漏journalctl 检索 config-auth非 -d 4 环境无口令原文(实测 8.2)

十、常见问题(FAQ)

Q1:内建 ban 与 fail2ban 都开着,会不会互相干扰? 会叠加但语义不同:内建 ban 只挡新 TLS 握手、到期自动解;f2b 的 iptables REJECT 连已建会话一起断(实测对比 7.1/7.3)。误伤共享出口 IP 时先用 occtl unban ip + fail2ban-client set ocserv unbanip 双解,再按 7.4 调宽 max-ban-score。

Q2:isolate-workers = true 要不要开? 生产建议开(worker 逐进程 seccomp + 独立网络命名空间)。本专栏基线保持 false 的原因:官方注释提示个别 libc 组合下 worker 可能随机失败,且约 2% 吞吐开销,开启前应在目标内核回归测试;验证方法为开启后完整跑一遍客户端连播与 occtl 查询。

Q3:证书认证下用户名从哪来? cert-user-oid(默认 UID OID 0.9.2342.19200300.100.1.1)从证书 DN 取,实测用户名 admin 即来自客户端证书 uid 字段;组名同理走 cert-group-oid。证书签发时确保模板里有对应字段(安装指南 4.2 节 client.tmpl 已含 uid = "admin")。

Q4:fail2ban 起了但 Total failed 一直是 0? 先确认后端:fail2ban-client status ocserv 的 Journal matches 必须是 _SYSTEMD_UNIT=ocserv.service(实测值);再用 7.2 的 fail2ban-regex 回放历史日志验证正则;最后核对 failregex 里的日志格式与 journalctl -u ocserv -o cat 实际输出逐字符对齐。

Q5:TLS 收紧后老 AnyConnect 客户端连不上? 预期行为(硬拒旧版本握手,实测 gnutls-cli TLS1.1 被拒)。升级客户端到支持 TLS1.2+ 的版本(OpenConnect v8.x 实测 TLS1.3 正常);确需保留老客户端时,在 tls-priorities 里临时去掉 :-VERS-TLS1.0:-VERS-TLS1.1 并接受降级风险,同时加快客户端换代。

十一、参考链接