设想一个假设的企业差旅平台。员工在平台上预订机票和酒店;任何超出消费限额的行程都必须经主管审批;每家客户企业预先充值账户余额;合作伙伴提供的优惠码每家企业只能使用一次;行程取消后,费用退回到同一余额中。最近一次自动化扫描的结果很干净。
然而,扫描报告并没有回答财务团队最关心的问题。如果审批步骤从未发生,员工能否直接发送确认预订的请求?如果两位同事在同一秒使用同一张优惠码,两人是否都能拿到折扣?一笔已经退款的行程,能否再次提交退款申请并再次入账?这些请求个个格式正确,都能通过输入校验,却都会悄无声息地破坏企业赖以运转的规则。
这正是首席信息安全官、首席技术官或研发负责人在采购业务逻辑安全测试时面临的难题:风险存在于业务流程和规则之中,而不在畸形输入里,因此常规的渗透测试范围界定、报价和验收方式都难以适用。本文将说明这类工作究竟检查什么、如何围绕业务流程界定范围、合格的测试人员如何在不干扰线上业务的前提下开展测试,以及在宣布项目完成之前应当要求哪些证据。
业务逻辑测试究竟检查什么
业务逻辑测试检查的是应用程序是否执行了业务方设定的规则,而不仅仅是代码碰巧实现的那些规则。谁有权批准退款;结账步骤必须按什么顺序进行;一项折扣可以生效几次;哪个账户可以查看哪些数据;操作在中途取消时,余额会发生什么变化。
合法的请求,错误的上下文
注入和内存安全缺陷通常源于应用本不该接受的输入。逻辑缺陷则不同:请求在语法上完全正确,往往还十分普通;问题在于,一个本被允许的操作在错误的上下文中、以错误的顺序、以过多的次数、以错误的数量或以他人的身份被执行。
这改变了采购的对象。您付费购买的不是一个向每个参数发送攻击载荷的工具,而是一位足够理解业务流程的专家:他会追问“如果这一步被跳过、被重复,或者与另一个请求同时竞争,会发生什么?”,然后通过经授权的测试证实答案。
OWASP《业务逻辑安全备忘单》(Business Logic Security Cheat Sheet)从防御方的角度表达了同样的观点:一个操作即使语法有效,也可能违反业务流程或业务不变量。它的测试建议集中在以下几个方面:调整步骤顺序、重放请求、并发执行操作,并确认价格、权限和流程状态由服务器端强制执行,而不是轻信客户端提交的内容。
以不变量为工作单元
界定范围最有效的方式是使用不变量,即无论收到怎样的请求序列都必须始终成立的陈述。“超出限额的预订在没有主管审批的情况下绝不会被确认。”“一张优惠码对每家企业最多产生一次折扣。”“一次行程的退款总额绝不超过实际支付金额。”
不变量为双方提供了共同的语言。产品负责人可以确认它们,测试人员可以攻击它们,开发人员可以落实它们,回归测试则可以在项目结束后持续检查它们。如果一个项目说不出自己测试了哪些不变量,它实际测试的内容往往要模糊得多。
为什么流程状态存在于应用之中,而不在HTTP里
一种常见的简化说法是:既然HTTP是无状态的,服务器就不会在两次请求之间保存会话数据。这种说法是错误的,而且这个错误很关键,因为正确的理解恰恰解释了逻辑缺陷从何而来。
协议无状态,不等于服务器无状态
HTTP在协议层面是无状态的:每个请求都被独立理解,协议本身并不知道第三个请求和第二个请求属于同一次结账。但这并不妨碍服务器保存状态。几乎所有真实的应用都在这样做——把状态保存在以Cookie为键的服务器端会话存储中,保存在记录订单状态的数据库记录中,或者保存在缓存里。Cookie和会话标识符让应用能够把彼此独立的请求与它所保存的状态关联起来。
由此得出的结论不是会话无法实现,而是维护流程顺序完全是应用自己的责任。每收到一个请求,服务器都必须查询被操作对象的当前状态,并判断此时、对这个操作者而言,这次状态转换是否被允许。HTTP不会替它完成这件事。
漏洞出现在哪里
逻辑缺陷往往出现在这种检查缺失、不完整或放错层次的地方。常见情形包括:某个步骤仅靠界面隐藏按钮来“保护”;状态标志由客户端在隐藏字段或请求体中回传;检查只在多步骤流程开始时执行,结束时却没有再次执行;“先读后写”的逻辑在每次只处理一个请求时没有问题,两个请求同时到达时却会出错。
并发问题值得单独一提。HTTP提供了条件请求,例如ETag配合If-Match,用于在两个客户端修改同一资源时防止更新丢失。涉及余额和配额的操作通常还需要原子更新、唯一性约束、行级锁或幂等键。测试人员的任务是检查在请求真正相互竞争时防护是否依然有效,而不是看设计文档里是否提到了某种机制。
通用扫描器为何会跟丢线索
自动化扫描器擅长识别单个请求中的已知模式,但通常无法理解对某项具体业务而言,什么才算合法的订单、审批链或租户边界,因此无法判断一个格式正确的请求在特定上下文中是否合法。这是基于推理得出的结论,而非测量结果,但它解释了为什么逻辑测试需要按专家工作来配置人员和定价,也解释了为什么一份干净的扫描报告几乎无法说明流程的完整性。
攻击边界在哪里
不同的逻辑缺陷破坏的是不同的假设。把它们按边界梳理清楚,有助于决定哪些内容纳入范围、应当期待什么样的控制措施,以及由哪个团队负责修复。
左右滑动查看对照
| 边界 | 业务方的假设 | 测试人员如何挑战 | 服务器端应有的控制 |
|---|---|---|---|
| 流程顺序 | 各步骤按设计的顺序发生 | 直接调用后续步骤,跳过或重复步骤,恢复已放弃的流程 | 每次状态转换都检查状态机 |
| 重放与幂等性 | 每个操作只生效一次 | 重新发送截获的退款、兑换或确认请求 | 用幂等键或状态检查拒绝重复操作 |
| 并发 | 同一时刻只有一个请求改动余额或限额 | 针对同一优惠码、余额或配额发送并行请求 | 原子更新、锁或唯一性约束 |
| 服务器端拥有的数值 | 价格、总额和折扣由系统决定 | 在请求中篡改价格、数量或币种,或提交负数金额 | 依据权威数据在服务器端重新计算 |
| 权限与审批 | 只有正确的角色才能审批或覆盖 | 申请人审批自己的申请,或角色超出其限额操作 | 与当前操作者身份绑定的权限和限额检查 |
| 租户隔离 | 每个客户只能看到自己的数据和操作 | 替换标识符以操作其他租户的对象 | 每次查询和每项操作都施加租户范围限制 |
调整顺序与跳过步骤
在这个假设的差旅平台中,设计的流程是:草稿、提交、审批、确认、出票。测试人员会尝试对草稿直接调用确认,用修改后的金额重新提交已审批的预订,或者在预订被编辑后重放审批请求。每次要回答的问题都一样:服务器是在检查当前状态,还是相信客户端是老老实实走到这一步的?
重放与并发请求
重放测试检查本应只发生一次的操作能否发生两次:退款被入账两次,一次性奖励被再次领取,邀请在撤销之后仍被接受。并发是重放更棘手的“近亲”:两个相隔几毫秒发出的兑换请求,可能都在任何一方写入“已使用”之前读到了“未使用”。
服务器必须独立校验的数值
客户端可以提交希望购买的数量,也可以选择系统支持的币种。服务器必须核对这种选择是否符合规则,包括数量范围、币种可用性、商品限制和交易条件。价格、折扣、汇率和应付总额则应依据服务器端的可信数据计算,而不能直接相信客户端算好的结果。只有当修改输入能够绕过这些规则,例如产生负数总额或使用无权享受的折扣时,才构成相应发现。修复应恢复服务器端的业务规则,而不是一概禁止客户端提交数量。
逻辑测试与访问控制的交叉地带
租户和角色边界介于逻辑测试与传统授权测试之间。替换对象标识符以读取其他租户的记录,通常被归类为访问控制失效;允许申请人批准自己的报销,则更常被视为逻辑问题。叫什么名字并不重要,重要的是确保范围同时覆盖这两种情况,并且没有哪个团队想当然地认为另一个团队已经测过了。对于以API为主的产品,REST API安全评估方法一文介绍了对象级和功能级授权检查如何与流程检查相互配合。
围绕业务流程界定测试范围
大多数逻辑测试项目的成败,在范围界定阶段就已经决定了。如果范围只是一份主机名清单加上接口数量,测试人员就会把最初几天花在逆向还原业务方早已掌握的信息上。
测试开始前需要准备什么
让逻辑测试真正有效的输入,大多是业务层面的材料:关键流程及其状态的说明、角色与权限矩阵、重要的限额和配额(消费阈值、兑换规则、具有业务含义的频率限制),以及至少两个租户中每个角色的测试账户。能够让测试人员在不触及真实客户的情况下完成创建、审批、取消和退款的合成数据,比宽泛的网络访问权限更有价值。关于环境和访问权限方面的准备,如何为安全测试准备Web应用一文有更详细的介绍。
哪些在范围之内,哪些在范围之外
一份可操作的范围说明,应当列出需要深入测试的流程以及每个流程必须保持的不变量,同时也要写明哪些内容不在范围之内。常见的排除项包括:压力测试和拒绝服务测试;社会工程;支付处理商或身份提供商等第三方系统,除非其所有者授权测试;以及任何会转移真实资金或通知真实客户的操作。当某个流程依赖外部服务商时,常见的折中做法是在沙箱中测试应用如何处理该服务商的响应,而不是测试服务商本身。
值得追问的报价信号
如果报价仅按URL或接口数量来计算逻辑测试的费用,就要保持警惕。逻辑风险随着流程、角色、状态和资金操作的数量而增长:一个小型API背后可能隐藏着复杂的审批引擎,而一个庞大的营销网站可能几乎没有值得测试的逻辑。忽略业务流程的报价,很可能只是换了名称的普通扫描。
如果您希望与Pentest.red一起界定这样的范围,最好的起点就是上面那份清单:哪些流程涉及资金流转、授予权限或跨越租户边界,业务方最关心哪些不变量,有哪些环境和测试账户可用,以及项目结束时您期望获得哪些证据。您可以从渗透测试范围准备页面开始这场讨论;工作量和形式会在此基础上协商确定,而不是事先承诺。
测试如何开展
NIST SP 800-115发布于2008年,将评估过程分为规划、执行和执行后活动;最后一个阶段包括分析发现、提出缓解建议和形成最终报告。它并不是一套完整的测试计划,但只要每个阶段都立足于业务模型而非工具输出,其阶段结构就非常适合逻辑测试。
建立流程模型
规划从产品负责人开始,而不是从爬虫开始。测试人员为范围内的每个流程梳理参与者、状态和允许的状态转换,标出涉及资金、权益或不可逆操作的环节,并写下不变量。在这个假设的差旅平台中,这样的模型会展示预订的状态机、每个角色的审批限额、预订和退款时余额如何变化,以及优惠码的使用规则。
逐一挑战每个不变量
执行阶段会逐一拿出每个不变量,用经授权的非破坏性测试用例尝试打破它:调整顺序、跳过、重复、制造竞争、篡改服务器端拥有的数值,以及跨越角色和租户边界。下面的流程展示了一个不变量如何从模型走向长期有效的控制措施。
- 建立流程模型与产品负责人一起梳理参与者、状态、允许的状态转换,以及涉及资金、权限或不可逆操作的环节。
- 明确不变量用双方都认可的表述写下必须始终成立的规则,例如“一张优惠码对每家企业最多产生一次折扣”。
- 尝试打破它仅使用测试账户和合成数据,对相关请求进行调整顺序、跳过、重放或并发竞争,并篡改服务器端数值。
- 确认并评级稳定复现该问题,并用业务语言描述影响:滥用者能得到什么、造成多少损失、谁会察觉。
- 修复并固化在服务器端强制执行该规则,复测原始路径及其变体,并补充自动化回归测试。
用业务语言分析发现
分析阶段不仅按技术严重程度、也按业务影响对发现进行排序。一个能让同一张优惠码被使用两次的竞态条件,在漏洞评分体系中也许显得无关紧要,但对于发放大量合作伙伴优惠码的企业来说可能相当严重;反过来,一个需要审批人本人凭据才能实现的理论绕过,其重要性可能比乍看之下要低。报告应当解释评级的理由,让管理层把这些发现视为业务风险,并像其他风险一样记入同一份风险登记册。
在真实交易上安全地开展测试
逻辑测试会触及交易、审批和余额,因此运营安全比基于模式的扫描更为重要。
授权、时间窗口与停止条件
测试只能在书面授权下进行。授权文件应列明系统范围、测试时间窗口、双方联系人以及测试必须停止的条件,例如出现意外的真实资金流动、通知发送到了真实用户,或者系统性能下降。测试规则还应规定严重发现需要在多长时间内通报,而不是等到最终报告时才披露。
数据、支付与外部副作用
测试数据应当是合成数据。任何具有外部影响的操作——支付、电子邮件、短信、合作伙伴接口调用、流入会计系统的账务记录——都应在沙箱中进行,或逐项获得明确批准。如果无法避免在生产环境中测试,应事先约定测试交易如何标记、撤销和对账。
进行并发测试而不造成附带损害
竞态条件测试本身就要求发送成批的并行请求。在共享的预发布环境中,这可能干扰其他团队的测试;在生产环境中,则可能触发反欺诈控制或频率限制。应事先约定测试目标、并发规模和时间窗口,并使用专门的测试对象,例如只为测试创建的优惠码、测试租户下的余额,这样即使竞争成功,也不会损害任何真实的东西。
值得引用的标准,以及它们做不到什么
当标准能让工作可追溯、可比较时,它们是有帮助的;当标准取代了对具体流程的思考时,它们就有害了。
WSTG和ASVS用于追溯,而不是界定范围
OWASP《Web安全测试指南》4.2版设有专门的业务逻辑测试章节,服务商应能说明其测试用例分别对应其中哪些领域。OWASP ASVS 5.0.0采用带版本号的需求标识,格式如v5.0.0-1.2.5,使报告能够准确注明检查的是哪一条需求、依据的是哪个版本。固定版本号很重要:需求编号在不同版本之间会发生变化,一条引用了不带版本号标识的发现,一年后可能就会变得含义不清。
意识提升清单不能划定边界
OWASP Top 10是一份意识提升文档,是讨论的起点,而不是逻辑测试项目的范围边界。基于公开漏洞记录建立的行业弱点排名,自然更突出那些在许多产品中反复出现的缺陷。逻辑缺陷往往只存在于某个组织的特定流程中,因此它们在这类排名中不显眼,并不意味着风险低。本文不对逻辑缺陷的普遍程度作任何断言;诚实的立场是:它们的重要性取决于您的应用在多大程度上涉及资金流转、授予权限或隔离租户。
渗透测试不是什么
业务逻辑测试是一项有时间和范围限制的工作。它能够说明哪些具体的不变量以哪些具体方式受到了挑战,以及结果如何。它不是认证,不是鉴证,也不是对应用中不存在逻辑缺陷的保证;一份干净的报告并不能证明在已测流程之外、或在测试之后引入的问题不存在。
项目收尾前应接受哪些证据
逻辑测试项目的价值,体现在它留下了什么。签字验收之前,值得做以下这些检查。
左右滑动查看对照
| 交付物 | 应当体现的内容 | 如何核查 | 警示信号 |
|---|---|---|---|
| 流程模型与不变量清单 | 范围内每个流程的状态、转换、角色以及所测试的规则 | 由产品负责人确认模型与业务的实际运作方式一致 | 只有通用的漏洞类别清单,没有点出任何具体流程 |
| 可复现的发现 | 精确的请求序列、所用账户、竞态条件的时序 | 由您的工程师至少独立复现所有严重发现 | 只有截图,或复现步骤依赖测试人员自己的工具 |
| 业务影响说明 | 滥用者能得到什么、造成多少损失、哪项控制失效 | 财务部门或产品负责人认同所述影响 | 只有严重性评分,没有解释业务后果 |
| 复测结果 | 修复后重新尝试原始路径及合理的变体 | 复测记录引用具体的修复内容和尝试过的变体 | 标记为“已修复”,却没有记录复测了什么 |
| 覆盖范围声明 | 已测试和未测试的流程与不变量 | 与约定的范围和不变量清单进行对照 | 对局限性、未测流程或环境差异只字未提 |
发现与复现
一条好的逻辑缺陷发现读起来像一个有精确剧本的小故事:起始状态、请求序列、涉及的账户、结果,以及这个结果为何违反了不变量。对于竞态条件,它应写明并行请求的数量和产生效果的时序,因为一个只是缩小了时间窗口的修复,在草率的复测中也可能显得有效。关于报告结构的更多内容,可参阅一份有用的渗透测试报告应包含什么。
复测与回归
每一个已确认的逻辑缺陷,最终都应带来两样东西:一项服务器端控制措施,以及一个在每次构建时检查该不变量的自动化回归测试。复测不仅要尝试原始的请求序列,还要尝试相近的变体,例如换一个角色、换一种步骤顺序、稍微改变时序。如何让这种证明经得起推敲,可参阅渗透测试修复验证:如何证明修复真正有效。
如何选择服务商与测试频率
区分逻辑测试与“换了名字的扫描”的问题
请对方提供一份脱敏的样例报告,其中应展示流程模型、已测试的不变量和业务影响。询问服务商如何建立模型、需要您这边哪些人员参与;了解他们如何安全地测试竞态条件、如何处理外部系统中的副作用、发现如何对应到WSTG的领域和带版本号的ASVS需求,以及复测包含哪些内容。对方如果能用您的业务流程来回答,比列出一串工具名称更能说明问题。更全面的比较方法,可参阅如何比较渗透测试服务商。
根据暴露程度确定测试频率
首先评估暴露程度。如果应用涉及资金流转、授予权益、隔离租户或触发不可逆操作,就有理由开展专门的逻辑测试。如果应用主要提供静态内容,自动化测试和安全配置可能已经覆盖了大部分风险。
然后确定测试频率。业务流程会随着功能一起变化,因此与重大流程变更挂钩的逻辑测试,通常比单纯按年度固定日期执行更有用。一个大致的参考:高暴露且变更频繁时,需要与发布节奏挂钩的定期专家测试;高暴露但产品稳定时,需要定期的深度测试并配套回归测试集;低暴露时,自动化覆盖加上定期复查即可。
做出决定之后,您可以与Pentest.red讨论如何把业务逻辑检查纳入更全面的渗透测试项目:哪些流程需要深入测试,哪些只需较轻的覆盖,复测如何组织。这些都将根据您的应用和限制条件来确定,而不是套用固定套餐。
结语
业务逻辑缺陷隐藏在看似合法的请求之中,这正是它们能够躲过扫描器的原因,也是为什么像采购扫描那样采购逻辑测试很少奏效。被滥用的流程状态存在于您的应用之中——存在于会话、数据记录以及服务器在每次请求时都必须执行的检查之中——而不在HTTP里。
请以流程和不变量而非接口来界定范围。期待测试人员在书面测试规则下、使用合成数据,去调整顺序、重放请求、制造竞争并篡改服务器端数值。依据证据验收工作成果:经确认的模型、可复现的发现、业务影响说明、对变体的复测、诚实的覆盖范围声明,以及在项目结束很久之后仍在持续检查不变量的回归测试。同时,要如实看待结果:它是在某一时间点对特定规则进行的有范围限制的测试,而不是一纸证书。
PENTEST.RED
讨论渗透测试范围与报价
首先明确系统、访问权限、限制条件和预期证据。
本文以深入研究为基础,经过编辑整理,注重清晰表达和实际应用。