Teampass 3.2.3.2 安全加固指南

Teampass 3.2.3.2 安全加固指南:12 连击错误密码防爆破锁定全记录(含 auth_failures 解锁码)、2FA 生效条件实测、nginx TLS 反代与 HSTS 落地实验(HTTP/2 200、301 跳转、TLS1.1 拒绝)、审计日志核查、secrets 主密钥与密文存储的最小权限布局、固定版本 tag 与自动升级策略。

Teampass密码管理PHP 应用自托管2026/10/24

Teampass 3.2.3.2 安全加固指南

目标:把一个默认安装的 Teampass 3.2.3.2 收敛到可对内的安全基线:防爆破锁定与解锁、双因素生效条件、HTTPS 反代与 HSTS、审计日志核查、文件系统与数据库口令的最小权限布局、升级与补丁策略。全部结论在 Ubuntu 宿主机(Docker 29.8.1)部署的 3.2.3.2 实例上实测验证,包括一次完整的 12 连击错误密码锁定实验与一次 TLS 反代落地实验。

一、环境信息

  • 实例:Teampass 3.2.3.2(TP_VERSION 3.2.3 + MINOR 2,PHP 8.3.35,MySQL 8.0.46),部署与参数基础见《Teampass 3.2.3.2 Docker Compose 部署指南》与《Teampass 3.2.3.2 配置参数详解》
  • 加固前基线(默认值实测):维护模式 1、防爆破阈值 10 次/锁定 10 分钟、管理员强制 2FA 开关为 1 但无任何 2FA 提供者启用、HTTP 明文服务(8144 端口)、log_connections 类审计已默认记录
  • 加固实验环境:nginx:alpine(1.31.8)作 TLS 反代、自签证书 30 天、宿主端口 8153(443)/8154(80)

二、身份认证与访问控制

2.1 防爆破锁定(实测全记录)

默认策略:nb_bad_authentication=10(连续错误阈值)、bruteforce_lock_duration=10(锁定分钟)。实测对 dev1 账号连续提交 12 次错误密码:

[try1]  msg='Login credentials do not correspond!'
[try10] msg='Login credentials do not correspond!'
[try11] msg='Too many failed attempts, your account is blocked until: 2026-10-24 10:03:40'
[try12] msg='Too many failed attempts, your account is blocked until: 2026-10-24 10:03:40'

锁定同时在 teampass_auth_failures 表落两条记录(实查):

sourcevalueunlock_atunlock_code
logindev12026-10-24 10:03:409X2FfbBCM0ui0eMNokvS8ac4CZaxwm
remote_ip127.0.0.1NULLNULL

按登录名维度会带 unlock_at 与 unlock_code(锁定 10 分钟与阈值联动);按来源 IP 维度记录无解锁时间。管理员可在后台安全面板移除锁定记录(对应后端 authentication_lockout_remove 接口,实查仅管理员可调用)。

加固建议:保持 10/10 基线即可;更严格场景把阈值降到 5(该字段服务端强制非负整数)。注意 bruteforce_lock_duration 传 0 会被服务端纠正为 10,无法通过配置完全关闭锁定。

2.2 口令策略

安装向导与管理端实测口令规则:长度 ≥10、必须同时包含大写字母、小写字母与数字/特殊字符(q1w2e3r4 被拒、q1w2E3R4T5 通过)。security_dashboard_min_password_length=12 是安全仪表盘的评分基线,弱口令治理建议以此为准开展周期评审。

2.3 双因素认证(2FA)的生效条件

admin_2fa_required=1 是新装默认值,但实测语义:仅当至少一种 2FA 提供者启用时才生效(google_authentication / yubico_authentication / duo 任一为 1;源码判定顺序为先判提供者、再对管理员查 admin_2fa_required)。因此加固动作必须成对执行:

  1. 启用 google_authentication=1(TOTP);
  2. 保持 admin_2fa_required=1;
  3. 要求普通用户 2FA 时,开启用户级 mfa_enabled 或按角色要求(mfa_auth_requested_roles,AD 组映射的角色同样计入);
  4. ga_website_name 改为组织名(默认值 TeamPass for ChangeMe)。

WebAuthn/Passkey 通道(webauthn_provider_enabled)可作第二因素,webauthn_passwordless_satisfies_mfa=1 表示免密登录可同时满足 MFA 要求。

2.4 权限模型的最小化配置

实测确认的权限边界(配置时直接引用):

  • 角色-文件夹授权 W 只覆盖条目增删改查;建子文件夹被独立拒绝(实测回执 "You are not allowed to do that!");
  • 根目录建文件夹需要用户级 can_create_root_folder 能力(实测无能力者回执 "Folder is not allowed for this user");
  • 总开关 enable_user_can_create_folders=0 保持默认;
  • 批量移动/删除 enable_massive_move_delete=0、条目重复 duplicate_item=0 保持默认;
  • 用户删除为软删除(改名 <user>_deleted_<ts> + disabled),离职处置时先禁用/删除再依赖审计核查,同名重建走「恢复用户」而非新建。

2.5 会话控制

  • default_session_expiration_time=60(分钟):会话空闲上限,安全要求更高的内部部署可降到 30;
  • 会话 Cookie 实测属性:PHPSESSID 带 HttpOnly; SameSite=Lax(另有同站点的随机名会话 Cookie,HttpOnly,Max-Age 1800);明文 HTTP 部署下没有 Secure 标志——接入 HTTPS 反代后应只暴露 HTTPS 端口(见第三节);
  • encryptClientServer=1 保持开启:登录与 API 载荷在应用层再做一次 AES 加密(登录请求实测为 AES-256-CBC + PBKDF2 派生密钥,密钥随登录页下发并轮换)。

三、传输加密(TLS)

3.1 反代落地实验(实测)

应用镜像本身只提供 HTTP,TLS 由反代终结。实验拓扑:nginx:alpine 加入应用同网络,443 端口反代应用 80 端口,80 端口 301 跳转 HTTPS。核心配置(自签证书 30 天仅作实验,生产用正规签发证书):

server {
    listen 80;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl;
    http2 on;
    ssl_certificate     /etc/nginx/certs/tp.crt;
    ssl_certificate_key /etc/nginx/certs/tp.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    client_max_body_size 100m;
    location / {
        proxy_pass http://teampass-3.2.3.2-web:80;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
    }
}

实测验证结果:

== HTTPS ==          HTTP/2 200  server: nginx/1.31.8
== redirect ==       HTTP/1.1 301 Moved Permanently
== HSTS ==           strict-transport-security: max-age=31536000
== TLS 1.3 ==        握手成功,http=200
== TLS 1.0/1.1 ==    连接被拒(协议协商失败)

3.2 应用侧配套

  • 站点参数 cpassman_url 与 url_to_files_folder 改为 HTTPS 地址,否则邮件与附件链接仍走明文;
  • enable_sts=1:应用自身的 HSTS 开关,与反代头二选一或叠加均可;
  • api_require_https=1 默认已强制 API 走 HTTPS;
  • 生产建议:应用端口不对宿主机发布(去掉 compose 中的 8144:80 映射,仅反代网络内可达),对外只留 443。

四、网络与防火墙

实测基线(compose 部署默认已达成大半):

  1. 数据库端口不对宿主机发布:实测 docker compose ps 中 db 服务仅显示 3306/tcp(容器网络内可达,无宿主映射),这是最关键的一条网络边界,编排时不要给 db 加 ports;
  2. 自定义 bridge 子网:应用与数据库独立于默认网段(实测 10.89.70.0/24),与其他业务网络隔离;
  3. 反代与应用同网络:TLS 反代加入同一自定义网络,宿主机仅暴露 443(与可选的 80 跳转);
  4. 宿主防火墙:仅放行 SSH 与 443(ufw 场景 ufw allow 443/tcp),8144 之类应用端口不对外放行;
  5. 应急网络开关:维护模式(maintenance_mode)是比防火墙更细的软开关——开启后非管理员立即不可登录(实测语义,且其会话会被主动置失效),适合升级窗口与应急止血。

五、审计日志

5.1 日志落点(实测表清单)

表内容实测状态
teampass_log_system登录(user_connection)、失败认证(failed_auth)、用户管理动作(user_mngt)实测 11 条登录、12 条失败认证、3 条用户管理
teampass_auth_failures防爆破锁定记录(login/remote_ip 维度、unlock_at、unlock_code)2.1 节实验产物
teampass_log_items条目动作(创建/修改/查看)实测建条目后 1 条
teampass_background_tasks_logs后台任务执行日志配合 enable_tasks_log

5.2 核查与外发

# 最近登录与失败认证
docker exec teampass-3.2.3.2-db mysql -uteampass -pq1w2e3r4 teampass \
  -e "SELECT type, COUNT(*) FROM teampass_log_system GROUP BY type;"

# 失败认证明细(定位爆破来源)
docker exec teampass-3.2.3.2-db mysql -uteampass -pq1w2e3r4 teampass \
  -e "SELECT label, date, who FROM teampass_log_system WHERE type='failed_auth' ORDER BY id DESC LIMIT 20;"

加固建议:log_connections=1(登录审计)保持开启;syslog_enable=1 + syslog_host 把日志外发 SIEM;tasks_log_retention_delay=30 按合规要求调整保留期;send_mail_on_user_login 可对高危账号开登录邮件告警(需先配好 SMTP)。

六、服务降权与最小权限

6.1 文件系统权限(实测布局)

镜像 entrypoint 与安装第 6 步 chmod 落地的权限模型,实测核验:

路径权限属主说明
/var/www/html/storage/750nginx:nginx运行时根
storage/sk700nginx:nginx用户密钥存储
storage/config(settings.php 所在)750,文件 0640nginx:nginx配置持久层
storage/files、upload、backups750nginx:nginx附件/上传/备份
/var/www/html/secrets/700,密钥文件 0600nginx:nginxDefuse 主密钥(136 字节随机名文件)

PHP-FPM 以 nginx 用户运行(非 root),应用进程即使用户态被攻破也无法越权读写系统层;secrets/ 是唯一的高价值单点,备份介质须与数据库备份同等对待。

6.2 数据库口令与密文存储

  • 应用连库使用专用账号 teampass(非 root,compose 预建),按最小权限原则不应授予跨库权限;
  • settings.php 中 DB_PASSWD 为 Defuse 密文(def50200... 前缀实测),主密钥在 secrets/,二者分离落盘;
  • 条目口令密文核验(实测):teampass_items.pw 为密文(teampass_aes 类型,44 字符密文样例),每个有权用户在 teampass_sharekeys_items 持有独立的密钥包裹副本(encryption_version=3),数据库拖库无法直接读出任何条目口令;
  • 数据库自身传输加密:DB_SSL 常量置 true 并配证书(默认 false,同宿主内网部署可评估后取舍)。

6.3 编排层降权

生产可追加:应用与反代容器均不以 privileged 运行(默认即非特权);如宿主有多团队共管,为 Teampass 栈单独建 compose 项目与网络,卷命名前缀独立(实测卷名 teampass-3232-stack_*),便于按项目回收权限。

七、补丁与版本策略

  1. 固定版本 tag:镜像拉取一律 teampass/teampass:3.2.3.2,禁用 latest(实测 latest 是 master 滚动构建、digest 与稳定版不同,无法追溯);
  2. 自动升级机制:镜像 entrypoint 内置 schema 底线比对——读库中 teampass_version 与 upgrade_timestamp(teampass_misc 实测 3.2.3 / 时间戳值),低于镜像携带的 UPGRADE_MIN_DATE 即在启动时自动执行迁移。升级操作因此收敛为 docker compose pull && docker compose up -d;
  3. 升级窗口动作:先开维护模式 → 备份数据库卷与 secrets/ → 拉取新版 → 观察启动日志迁移段 → 关维护模式;
  4. 版本情报:订阅 GitHub Releases(github.com/nilsteampassnet/TeamPass/releases),安全类版本从发布到采用建议控制在周级;3.2.3.2 是官方明示的新装推荐版本(Release Notes 原文);
  5. 升级兼容性:跨多个 3.2.x 小版本升级由 upgrade_timestamp 水位驱动逐级迁移,禁止跳版直接换大版本前先阅读官方升级说明。

八、常见问题

Q1:接入 HTTPS 后登录 Cookie 仍是明文传输吗? 反代终结 TLS 后浏览器到反代段是加密的;反代到应用段在同宿主/同网络内明文。加固闭环 = 只暴露 443、去掉应用宿主端口映射、cpassman_url 改 HTTPS、开启 enable_sts。Cookie 的 Secure 标志由应用按请求协议判定,全链路 HTTPS 后自然带 Secure。

Q2:账号被锁后怎么立刻解锁? 等待 unlock_at(实测 10 分钟)自动解锁,或管理员在后台安全面板移除锁定记录(teampass_auth_failures 中 login 维度记录含 unlock_code,接口 authentication_lockout_remove 仅管理员可调)。

Q3:admin_2fa_required=1 为何不生效? 见 2.3 节:必须先启用至少一种 2FA 提供者,纯开关不产生任何强制效果。

Q4:误把 bruteforce_lock_duration 设为 0 想关闭锁定? 服务端会纠正为 10(分钟),无法关闭。锁定机制是内置安全底线,不建议绕过。

Q5:secrets/ 目录里的文件能删吗? 不能。随机名文件是 Defuse 主密钥,删掉或错配后数据库中全部密文(条目口令、DB_PASSWD)无法解密,等同数据全毁。迁移/重建时必须与数据库卷成对搬移。

Q6:数据库被拖库,条目口令会泄露吗? 不会直接泄露:条目口令为密文且每用户持有独立包裹密钥(实测 sharekeys 结构);但 settings.php+secrets/ 同盘失窃时防线即失效,因此备份介质要加密存放、secrets/ 权限 700/0600 保持不动。

Q7:如何临时禁止所有人登录做维护? 维护模式(maintenance_mode)开启后非管理员立即被拒且在途会话失效(实测语义),管理员仍可登录操作,完成后关闭即可——比停服务、改防火墙更精准。

九、参考链接