逻辑漏洞通常没有明显的语法错误。页面可以正常打开,接口也能返回成功,但业务流程中的权限、顺序、金额或状态可以被绕过。

这类问题很难只靠一个通用漏洞扫描器发现,因为“正确行为”由业务规则定义。开发者需要从攻击者视角重新检查:

  • 谁能调用这个功能?
  • 能操作哪一条数据?
  • 操作顺序能否跳过?
  • 同一请求能否重复提交?
  • 客户端提交的金额、角色和状态是否可信?

一、身份认证流程

认证系统是逻辑漏洞最常见的高价值目标。一次认证绕过可能直接导致管理员权限失守。

检查认证流程时,应关注:

  • 用户名、密码和验证码是否都由服务端校验。
  • 验证码是否与当前会话、用途和有效期绑定。
  • 登录失败是否会有统一提示,避免用户枚举。
  • 会话是否在登录后重新生成。
  • 普通用户创建后是否仍可能访问管理功能。
  • 高权限账户是否要求多因素认证。

1.1 验证码只在前端校验

如果页面通过 JavaScript 判断验证码,服务端没有再次验证,那么攻击者可以直接调用接口绕过页面。

正确流程应满足:

用户提交验证码
    -> 服务端读取当前会话对应挑战
    -> 检查有效期与尝试次数
    -> 校验答案
    -> 一次性销毁挑战

验证码不能与账户密码权限发生混淆。无论验证码是否正确,服务端都必须独立完成账户认证和角色授权。

1.2 会话固定

用户在登录前获得一个会话标识,登录成功后如果服务端继续使用同一个标识,攻击者可能提前获取它并复用。

修复方式:

  1. 登录成功后生成新的 Session ID。
  2. 作废旧的匿名会话。
  3. 在 Cookie 中设置安全属性。
  4. 退出登录时服务端主动失效会话。

二、Cookie、Session 与 Token

Cookie 是浏览器保存数据的一种机制,不等于完整登录凭证。实际身份通常由服务端会话、签名 Token 或安全存储的数据确定。

安全属性包括:

  • HttpOnly:限制 JavaScript 读取。
  • Secure:只通过 HTTPS 发送。
  • SameSite:限制跨站请求携带范围。
  • Path 与 Domain:控制发送范围。

Cookie 的值不应直接包含可信角色或权限,除非同时具备可靠的签名和完整性校验。

2.2 Session

服务端 Session 更便于撤销权限。需要防止:

  • Session ID 可预测。
  • 会话过期时间过长。
  • 退出后会话仍有效。
  • 登录后不轮换 Session ID。
  • 服务端不校验角色和资源归属。

2.3 Token 与 JWT

JWT 是一种常见的令牌格式,但 JWT 本身不是安全保证。

服务端必须验证:

  • 签名是否有效。
  • 签发者是否符合预期。
  • 受众是否匹配当前服务。
  • 过期时间是否有效。
  • 令牌是否已被撤销。
  • 当前操作是否还需要额外权限检查。

不要把前端解析出的角色字段直接当作授权依据。

三、越权漏洞

越权是指用户能够访问本不应访问的功能或数据。权限判断必须在服务端完成,前端隐藏按钮只能改善界面体验,不能作为安全控制。

3.1 垂直越权

低权限用户直接调用高权限接口,例如普通用户请求管理员用户管理能力。

错误设计:

前端判断当前用户是管理员
    -> 显示管理按钮
服务端没有再次检查角色

正确设计:

每个管理接口
    -> 验证当前身份
    -> 验证所需角色
    -> 记录审计日志
    -> 执行操作

3.2 水平越权

同一角色用户访问其他用户的资源。例如修改订单 ID 后读取别人的订单。

不安全查询:

SELECT id, amount, status
FROM orders
WHERE id = ?;

如果只检查订单存在,没有检查订单归属,攻击者可能遍历 ID。

安全查询:

SELECT id, amount, status
FROM orders
WHERE id = ?
  AND user_id = ?;

参数绑定解决 SQL 注入,user_id 条件解决对象级授权。两者不能互相替代。

3.3 统一对象级权限

大型系统容易在部分接口遗漏资源归属检查。建议使用统一授权层:

身份认证
    -> 功能权限
    -> 资源归属
    -> 字段级权限
    -> 审计

查询、修改、删除和导出接口都应经过同一套规则,而不是开发者在每个 SQL 中手工判断。

四、支付与交易流程

支付逻辑不能信任客户端提交的价格、优惠、角色和订单状态。

4.1 价格篡改

客户端通常只负责展示商品价格,实际金额应在服务端重新计算。

安全流程:

  1. 服务端根据商品 ID 查询当前价格。
  2. 服务端重新计算优惠。
  3. 服务端验证库存和购买限制。
  4. 创建待支付订单。
  5. 支付回调验证签名和金额。
  6. 只有可信回调才能把订单改成已支付。

4.2 支付回调重复通知

支付平台可能多次发送相同通知。如果没有幂等处理,订单可能重复发货。

可以使用唯一支付流水号、订单状态机和幂等记录:

订单创建
    -> 待支付
    -> 支付确认中
    -> 已支付
    -> 已发货

状态只能按业务允许的路径变化,不能从“已退款”直接跳回“待发货”。

4.3 优惠券与积分

常见问题包括:

  • 同一优惠券重复使用。
  • 领取数量超过限制。
  • 优惠金额超过订单金额。
  • 退款后优惠券没有按规则恢复。
  • 多设备并发领取。

服务端应使用唯一约束、事务和幂等键,而不是只依赖前端按钮禁用。

五、并发竞争

条件竞争发生在多个进程或线程同时处理同一资源时。检查与更新之间如果存在时间差,最终结果可能违反业务规则。

常见场景:

  • 同一余额被并发消费。
  • 同一库存被多人同时锁定。
  • 优惠券被多个请求同时兑换。
  • 同一订单被重复取消或退款。
  • 竞争条件下创建多个唯一账户。

5.1 原子更新

不安全思路是先查询余额,再在应用层判断,最后写入。多个请求可能同时读到旧值。

更安全的方式是让数据库在一条语句中同时检查条件和扣减:

UPDATE accounts
SET balance = balance - ?
WHERE id = ?
  AND balance >= ?;

执行后必须检查影响行数。如果影响行数为零,说明余额不足或记录状态已变化。

5.2 事务与锁

涉及多条记录时,可以使用事务和行锁:

START TRANSACTION;

SELECT stock
FROM products
WHERE id = ?
FOR UPDATE;

UPDATE products
SET stock = stock - ?
WHERE id = ?
  AND stock >= ?;

COMMIT;

不同数据库的锁语义、隔离级别和超时行为不同。实现前需要确认业务允许的并发模型。

5.3 幂等键

支付、下单和兑换接口可以要求客户端提交幂等键:

idempotency_key
    -> 唯一约束
    -> 保存请求结果
    -> 重复请求返回同一结果

幂等键应绑定用户和业务类型,不能由攻击者随意覆盖其他用户的记录。

六、文件与路径逻辑

上传接口除了防止 Webshell,还要保证文件归属于正确用户。

需要检查:

  • 用户是否能下载他人的文件。
  • 文件名是否可能造成路径穿越。
  • 下载接口是否只用对象 ID 查询。
  • 私有文件是否放在公开目录。
  • 分享链接是否有有效期和权限限制。
  • 删除文件时是否检查所有者。

安全查询示例:

SELECT storage_key, mime_type, original_name
FROM user_files
WHERE id = ?
  AND owner_id = ?
  AND deleted_at IS NULL;

服务端拿到内部 storage_key 后才读取文件,不直接使用用户提交的路径。

七、状态机与流程绕过

多步骤流程需要防止跳步、重复和乱序。

例如找回密码:

提交账户
    -> 发送一次性令牌
    -> 校验令牌
    -> 设置新密码
    -> 作废令牌和旧会话

需要确保:

  • 令牌一次性使用。
  • 令牌有明确有效期。
  • 令牌只适用于当前用户和用途。
  • 设置密码后旧会话失效。
  • 不能通过修改步骤编号跳过验证。

服务端应保存流程状态,而不是依赖前端页面顺序。

八、测试方法

逻辑漏洞测试应以完整业务链路为单位,而不是只测试单个参数。

8.1 权限矩阵

列出角色和操作:

操作普通用户编辑管理员
查看自己的文章允许允许允许
编辑自己的文章允许允许允许
编辑他人的文章禁止按业务允许
管理用户禁止禁止允许

针对每一格测试正面和负面用例。

8.2 对象级测试

准备两个测试账号,验证:

  • A 能否读取 B 的订单。
  • A 能否修改 B 的地址。
  • A 能否删除 B 的文件。
  • A 能否通过修改 ID 绕过权限。

测试数据必须属于授权的隔离环境。

8.3 并发测试

对同一资源同时发送多个请求,检查:

  • 余额是否变成负数。
  • 库存是否超卖。
  • 优惠券是否重复使用。
  • 订单是否重复发货。
  • 幂等键是否真正生效。

并发测试需要控制速率,避免影响真实业务。

九、防御清单

  • 所有权限判断都在服务端完成。
  • 每个敏感接口检查角色和资源归属。
  • 价格、数量、角色和状态由服务端确定。
  • 登录成功后轮换 Session ID。
  • JWT 验证签名、签发者、受众和有效期。
  • 支付回调验证签名、金额和订单状态。
  • 下单、支付和兑换使用幂等键。
  • 余额和库存使用原子更新或事务锁。
  • 文件下载检查所有者。
  • 多步骤流程保存服务端状态。
  • 关键操作写入审计日志。
  • 权限矩阵和并发场景有自动化回归测试。

十、小结

逻辑漏洞的难点不在于代码语法,而在于业务规则是否真正由服务端执行。

可靠的设计需要同时回答三个问题:谁可以操作、能操作哪条资源、在什么状态下可以操作。只有把身份、对象所有权和状态机统一放进服务端授权与事务边界,才能减少越权、重复提交和并发竞争。