外观
安全基线
字数
2813 字
预计阅读
11 分钟
下面这些措施默认开启,每一项都有对应的测试或自动检查。
认证与会话
- 不使用 JWT。访问令牌和刷新令牌都是随机字符串,Redis 中只保存它们的哈希值;
- 访问令牌默认 30 分钟,活跃时自动续期,但不会超过绝对上限(12 小时;勾选"保持登录"或在手机端登录时为 7 天。这两个上限是固定的,不能配置);
- 刷新令牌存放在
HttpOnly+SameSite=Strict的 cookie 中,每次使用后都会更换。旧令牌被重复使用时,会吊销整个会话链; - 管理员重置密码、停用或删除用户,以及用短信找回密码时,该用户的所有会话都会失效;本人修改密码时,其他会话全部失效,只保留当前这一个;强制下线只结束被强退的会话(用"强退该用户"时结束这个用户的全部会话)。被结束的会话的 WebSocket 连接也会立即断开;
- 本人修改密码、短信找回密码、管理员重置密码时,还会解除这个账号的微信小程序绑定。这样即使有人曾用泄露的密码绑定了自己的微信,改密后也登录不了了。强制下线不会解绑。
登录防护
- 限流:按 IP 和按用户名分别计数;
- 同一个"用户名 + IP"连续失败 5 次会锁定 10 分钟。只锁这一对组合,所以别人无法借此把管理员账号锁死;
- 同一个用户名在多个 IP 上累计失败过多后,强制要求验证码;
- 不存在的用户名同样会计数,响应内容和耗时都与存在的用户名一致,无法借此探测账号是否存在;
- 支持 IP 黑名单(单个地址或 CIDR 网段,不支持
*通配,写错的条目会被忽略)、首次登录强制改密、密码过期、密码复杂度策略(前后端使用同一套规则)。
微信小程序登录
功能介绍和启用步骤见登录与账号 · 微信小程序登录。这里只列安全规则:
- 默认关闭。开关
auth.wx_mp.enabled没打开,或者缺少WX_MP_APPID/WX_MP_SECRET时,登录和绑定接口都返回 404; - 小程序传来的
code只能用一次;微信返回的session_key服务端收到后直接丢弃,既不保存,也不发给客户端; - 还没绑定的微信账号会拿到一张绑定票据:5 分钟内有效,只能用一次,只能从同一个 IP 使用;
- 绑定时要用账号密码或短信验证码证明身份,验证码、失败锁定和限流规则和普通登录一样。同一个微信账号不能绑定两次(返回 409),一个账号也只能绑定一个微信;
- 解绑不受开关限制,并且会让本人在其他手机上的登录全部失效(当前这台保留);
- 每次微信登录都记在登录日志里,类型是
wx-mp。身份验证通过但绑定被拒时,也会记一条失败的日志。
单点登录(OAuth2)
功能介绍见单点登录(OAuth2),接入细节见 OAuth2 接入指南。这里只列安全规则:
- 授权码方式必须用 PKCE,只接受
S256,plain或缺省都会被拒绝。授权码 300 秒内有效、只能用一次,换令牌失败的码也会作废; - 只支持机密客户端:申请、校验、撤销令牌都必须带客户端密钥,带了 PKCE 也一样;
- 客户端密钥由服务端生成(256 位随机值),只在创建和重置时显示一次。数据库里只存它的 SHA-256 摘要(不是加密,无法还原),之后任何页面、接口和日志里都看不到明文。比对时用固定耗时的算法,不存在的客户端也照样比对,无法借响应时间判断客户端是否存在;
- 回调地址登记时只接受
https://(本机调试的http://localhost、http://127.0.0.1除外),不能带#、通配符或用户名密码;授权时逐字比对。请求无效时同意页原地显示原因,从不跳转,不会把用户送到没登记的地址;同意页还会显示回跳的主机名,方便用户认出冒充的应用; - 第三方令牌和后台隔离:第三方令牌只能调用用户信息接口,调用后台其他接口一律 401,也连不上实时推送;后台的刷新接口不接受第三方的刷新令牌;授权接口只接受本系统自己的登录会话,所以第三方无法替用户点同意。
console、mobile是保留的客户端标识,不能登记; - 会话随账号一起结束:用户修改密码、被重置密码、被停用、被删除或被"强退该用户"时,他的所有 OAuth 会话和还没兑换的授权码一起失效;停用或删除客户端时,它的所有会话立即结束。刷新令牌每次使用后都会更换,旧令牌在 30 秒宽限期外被重复使用时,吊销整个会话;
- 令牌同样是随机字符串,不是 JWT,Redis 中只保存哈希值;令牌接口按 IP 限流;请求日志里的密钥、授权码和令牌自动脱敏,同意记录写进操作日志时,带授权码的回跳地址也会脱敏。
越权访问(IDOR)
- 所有按 id 查询、修改、删除的接口,都会先检查这条记录是否在当前用户的数据范围内,超出范围返回 404;
- 防越权授予:不能给别人超过自己的权限,详见权限与数据范围;
- 流程表单的附件是私有文件:除了上传人、超级管理员和有文件列表"查看"权限(
storage.object.view)的人,只有能查看这个流程实例、并且能看到这个附件字段的人才能下载,见工作流 · 附件。 - 审批数据按字段权限过滤:只有超级管理员和该流程的流程管理员能看到全部字段,其他人看不到在流程任一节点隐藏过的字段(页面、导出和筛选里都没有)。因为流程管理员能看全部字段,修改流程管理员名单需要单独的权限
wf.model.managers,只有wf.model.modify的人不能把自己加进去(返回 403),见工作流 · 审批数据里的字段权限。
注入与代码执行
- 只使用参数化 SQL,排序字段有白名单;有检查脚本禁止把字符串拼接进 SQL;
- 禁止
eval、new Function和vm(由 lint 和脚本检查); - 定时任务只能调用白名单中的处理函数,流程条件只能用结构化规则;
- 表单设计器保存的表单结构会在别人的浏览器里渲染,所以保存时前后端用同一份白名单检查:包含函数、会被当作代码执行的字符串(比如
$FN:)、事件和联动设置、未知组件时直接返回 400,白名单以外的设置被去掉,服务端只保存过滤后的结果,见表单设计器 · 安全; - BPMN 流程图只当作数据,不当作代码:服务端只接受一个白名单子集(开始、结束、审批、抄送、排他/并行/包容网关和连线),脚本任务、表达式(
${...})、监听器和其他 BPMN 工具的扩展配置一律拒绝;条件只能用条件构造器设置,从不当作表达式求值。XML 超过 80 KiB 或带 DOCTYPE、实体声明时,解析之前就拒绝,解析过程不访问网络和文件。发布时服务端从 XML 重新推出流程树,和树形设计器画的流程一样检查,由同一个树形引擎运行,不采信浏览器传来的结果,见工作流; pnpm ci:local在构建之后扫描前端产物,确认里面没有白名单以外的eval/Function调用,也没有 wangeditor v4 的代码。
XSS
- 富文本在保存时就经过白名单清洗(去掉
style、on*、script和iframe); - 站内信按纯文本处理;邮件预览在沙箱 iframe 中显示;
- 前端页面带严格的内容安全策略(CSP)。
文件上传
- 按文件内容识别真实类型,不相信扩展名;使用扩展名白名单,
html、svg、js永远不允许上传; - 保存时使用随机文件名,防止路径穿越;
- 下载时强制
attachment+nosniff+CSP sandbox; - 私有文件必须通过鉴权接口下载。
SSRF
管理员可以配置的外部地址(S3、SMTP),服务端连接前会先解析 DNS,拒绝内网、本机和云厂商元数据服务的地址,只允许白名单中的端口。
其他
- Excel:导出时,以
=、+、-、@开头的单元格会被转义,防止公式注入;导入时限制文件大小和行数,拒绝包含宏的文件; - 重复提交:新增类接口在服务端做了防重复提交,3 秒内的重复请求返回 429(写法见防重复提交、限流与锁);
- 敏感数据:密码使用 bcrypt 存储;第三方服务的密钥(比如 S3、SMTP)用 AES-256-GCM 加密后存入数据库;OAuth2 客户端密钥只存 SHA-256 摘要;日志中的密码、令牌等字段自动脱敏;
- 供应链:锁定依赖版本;新版本发布满 24 小时才允许安装;依赖的安装脚本需要逐个审核;依赖许可证自动检查。