☰
逻辑漏洞挖掘实战:从越权到竞态条件,比SQL注入更难防
2026/10/10 12:43:19 网站建设 项目流程

1. 逻辑漏洞到底是什么,为什么它比SQL注入还难防

做了这么多年安全测试,我有个特别深的感触:很多团队把WAF、RASP、漏洞扫描器堆了一整套,SQL注入、XSS这种常规问题倒是堵得死死的,结果业务上线没几天,就被一个看起来"不像漏洞"的漏洞打了脸——逻辑漏洞。

逻辑漏洞(Logic Vulnerability)说白了就是业务逻辑层面的缺陷。它不是数据库语法出了问题,也不是代码执行了不该执行的命令,而是程序处理业务请求时,对状态的判断、对流程的约束、对权限的校验不到位,导致攻击者可以用"合法的方式"做"非法的事"。

我经常拿一个比喻来解释:SQL注入像是有人从窗户翻进你家,而逻辑漏洞更像是你给了外卖小哥门禁密码,结果他顺手把密码贴在了楼道里——所有操作看着都合理,但系统不该允许它发生。

传统漏洞和逻辑漏洞最大的区别在于:前者是工程问题,代码写错了,修掉就行;后者是设计问题,代码可能一行都没写错,但业务规则本身就有漏洞。比如"订单金额由前端传参决定",代码逻辑完全正常,但业务设计就是错的。这也是为什么漏洞扫描器几乎扫不出逻辑漏洞——它不了解你的业务,而业务恰恰是逻辑漏洞的主战场。

这篇文章适合谁看?如果你在学渗透测试、在做安全评估、或者你是后端开发想了解自己系统哪里容易被薅羊毛,都值得往下看。我尽量把这么多年踩过的坑、总结出的方法论一次讲透。

2. 逻辑漏洞常见类型盘点:每种背后的业务原因

逻辑漏洞的类型说多不多,说少不少,但万变不离其宗。我按业务场景把它们分成六大类,每一类背后都有明确的业务缺陷原因。

2.1 越权漏洞:最经典也最容易批量发现的一类

越权分两种:水平越权(平行越权)和垂直越权(纵向越权)。

水平越权就是你操作了和你同级别的人的资产。最常见的就是改了ID就访问到别人的数据。比如一个查看订单详情的接口,URL是/api/order/detail?id=1001,服务端只校验了用户是否登录,但没有校验这个订单是不是属于当前用户。那我登录自己的账号,把id改成1002、1003,一路遍历下去,就能看到别人的订单、手机号、收货地址。

垂直越权就是你干了比你权限高的人才能干的事。比如普通用户调用了管理员的接口/api/admin/deleteUser,服务端没做角色校验。这类漏洞在管理后台和用户前台共用一套后端服务的系统里特别常见。

越权漏洞的根因就一句话:服务端只校验了"你是谁",没有校验"你有没有权限碰这个资源"。所谓"基于对象的授权校验"缺失,英文叫Broken Object Level Authorization(BOLA),在OWASP API Top 10里常年排第一,不是没道理的。

2.2 支付与订单逻辑:薅羊毛的重灾区

支付逻辑漏洞应该是逻辑漏洞里"含金量"最高的,因为直接影响钱。

最常见的有:

  • 金额参数前端可控:下单时把价格参数放在请求体里,比如{"goods_id":1, "price":100}。攻击者直接把price改成0分钱下单,甚至改成负数,比如-100,这样不但不花钱,账户还可能到账100块。有些系统最终和支付平台结算时用的又是另一套计算逻辑,两边一不一致就出大问题。
  • 优惠券/积分重复使用:一个优惠码理论上只能用一次,但服务端只在前端做了禁用判断,或者用session存储已用状态,换个会话就能再次使用。
  • 订单金额篡改:先加购物车,价格100,然后篡改购物车接口的单价,再去结算。如果结算接口信任购物车数据而不重新计价,就白嫖了。
  • 支付状态回调查验不严:支付回调接口只校验"签名正确",不校验订单号与金额是否匹配,或者只校验is_paid=true没校验业务订单状态,攻击者直接构造回调请求就能把未支付订单变成已支付。

这一类漏洞的根因,是前端传参和服务端计算边界混淆。凡是业务规则里可被用户影响的数据,都不能作为计费依据。

2.3 密码重置与身份校验绕过

密码重置流程里的逻辑漏洞也是高频点,而且往往能直接打到账号接管。

  • 步骤可跳过:正常的重置流程是"输入手机号 -> 验证码校验 -> 设置新密码",但有的系统每个步骤都是独立接口,攻击者调第一步、绕过第二步、直接调第三步,就能给别人账号重置密码。
  • 验证码不失效:验证码校验通过后,服务端不销毁验证码状态,同一个验证码可以反复用。更离谱的是有的系统验证码只有4位数字,服务端不限制尝试次数,直接爆破10分钟就能爆出来。
  • 响应包可控:有的系统在step2接口的响应里返回了"success": true或者直接把token放在响应体里,攻击者改一下响应包就能通过前端校验。
  • 修改参数指向他人:校验验证码接口传入的是phone=自己的手机号,设置新密码接口传入的却是phone=目标手机号,两步之间的手机号没有做绑定校验,就能用自己手机收到的验证码改别人的密码。

这类问题的根因是:流程状态共识缺失。一个多步骤流程,每一步之间没有建立统一的会话状态凭证,每一步都是独立的,自然就能跳步和错位绑定。

2.4 竞态条件(Race Condition):并发下的逻辑失控

竞态逻辑漏洞在支付、秒杀、优惠券、签到场景里很常见,而且是出了名地考验耐心。核心原理是:多个请求同时到达服务端,竞争访问同一个资源,服务端在"检查状态"和"更新状态"之间存在时间窗口,攻击者利用这个窗口重复发起请求,把只能执行一次的操作执行多次。

经典的例子:一个账户余额100元,提现接口逻辑是"查询余额 -> 判断余额>=100 -> 扣减余额 -> 打款"。攻击者同时发送10个提现100元的请求,如果并发足够高,10个请求可能同时通过"判断余额"这一步,然后各自执行扣减和打款,结果就是余额被扣成负数,但打款打了10笔。

我用一句话概括竞态漏洞的根因:检查与操作不是原子的,中间存在可被并发插入的间隙。服务端的锁没锁住,事务没隔离好,或者压根没上锁。

2.5 短信验证码、邮件轰炸与接口滥用

说白了就是业务接口没做频率限制。一个发送短信验证码的接口,服务端不限制同一手机号的发送频率,攻击者写个脚本循环调用,就能让某个手机号收到几百条垃圾短信,运营商那边还以为是用户自己在刷。邮件轰炸同理。

更隐蔽的是配合业务逻辑的滥用。比如有的系统注册接口会先判断手机号是否已注册,攻击者可以利用这个接口做手机号碰撞——批量探测哪些手机号是平台用户,为后续钓鱼和撞库做准备。

这类问题的本质是业务资源的无限制消耗,既不涉及越权也不涉及金额,但危害是实打实的——骚扰用户、消耗短信成本、拖慢服务。

2.6 接口数据篡改与越权叠加

这类要结合具体业务来看。比如一个积分商城,兑换商品接口参数是{"product_id": 88, "points_cost": 100},攻击者把积分消耗改成0,就免费换到了商品。再比如一个简历编辑接口,参数里带了user_type=1代表普通用户,改成user_type=2代表VIP,就能解锁VIP专属功能。

这类漏洞的根因更直接:服务端相信了不该相信的客户端数据。但凡关键业务参数能从前端传进来,就有被篡改的可能。

3. 逻辑漏洞挖掘的实操流程与方法论

很多人觉得逻辑漏洞难挖,是因为没有方法论,不知道从哪下手。我总结了一套自己的测试流程,从信息收集到深入验证,按这个顺序走,基本不会漏太多东西。

3.1 第一步:先摸清业务边界,别急着发包

拿到一个目标系统,先注册账号、登录进去,以正常用户身份把核心业务流程完整走一遍。走的时候重点记录三件事:

  • 每个步骤对应的URL和参数
  • 每一步之间是否有状态关联
  • 服务端通过Cookie、Token还是Session维护会话

这一步的意义在于建立"业务基线"。你必须知道正常流程长什么样,才能发现异常流程。很多新手一上来就乱改包,结果改的接口根本不涉及核心逻辑,浪费时间。

我一般会用Burp Suite把所有流量过一遍,全程开着记录。对每一个关键接口,我会单独在Repeater里存好,方便后面反复改参测试。

3.2 第二步:梳理接口清单,标记敏感参数

打开Burp的HTTP History,把登录后的流量全部筛选出来,每看到一个接口就问自己三个问题:

  • 这个接口的参数里有没有ID、type、role、status、price、amount这类高价值字段?
  • 这个接口返回的数据里有没有包含其他用户的数据特征?
  • 这个接口背后连接的资源是否属于当前登录用户?

凡是有ID的参数,都要试水平越权——登录A账号,拿B账号的资源ID去替换,看能不能访问。凡是有type/role参数的,都要试垂直越权——把普通用户的type改成管理员,看看后端是否做校验。

这里有个很实用的经验:优先测查询类接口,再测操作类接口。查询类越权只影响数据泄露,测试成本低、风险小;操作类接口可能删数据、改状态,需要谨慎。

3.3 第三步:重点测试多步骤流程的状态绑定

密码重置、绑定手机号、更换邮箱、实名认证这类流程,是最容易出现逻辑漏洞的地方。我的测试方法是:

  1. 正常走一遍完整流程,记录每一步的请求包
  2. 尝试跳过中间步骤,直接请求最后一步
  3. 尝试乱序请求,比如先调第三步再调第二步
  4. 尝试跨用户,用A的验证码去给B重置密码
  5. 尝试重放,重复发送同一个验证请求,看验证码状态是否被销毁

每一步我都会对照响应结果判断是否成功,而不是只看HTTP状态码。有些系统前端做了跳转限制,但接口层面实际已经执行了,要会用Burp直接观察返回数据里的业务字段。

3.4 第四步:并发测试要会用工具

做竞态测试,手工点击肯定不行。我用Burp的Intruder或者Turbo Intruder插件来打并发。基本思路:

  • 找到"检查+操作"两步逻辑的接口
  • 把操作请求发送到Turbo Intruder,设置高并发线程
  • 同时发送几十个甚至上百个请求,观察资源状态的变化

比如测试优惠券是否能重复使用,就抓取使用优惠券的请求,用Turbo Intruder一次性打50个并发,然后去订单列表看是否生成了多笔优惠订单。如果只有一笔,说明服务端处理得当;如果出现多笔,恭喜你中奖了。

3.5 第五步:不要忽略低频接口和管理端接口

很多时候,核心接口防护做得很好,但边缘接口全是洞。比如:

  • 后端管理接口没有做权限控制,任何人可访问
  • 测试接口(/test、/debug)遗留在生产环境
  • 第三方回调接口(支付回调、登录回调)没有良好的验签逻辑

我会在收集阶段专门用字典爆破管理后台路径,顺便看响应头的特征。一旦发现没有权限控制的接口,先用低权限账号试探,确认是否存在垂直越权。

4. 实战案例拆解:四个真实场景的完整走查

前面讲了方法论,这一节我用四个我在实际测试中遇到过的场景,把完整的过程走一遍。细节我会模糊掉,但思路完全保留。

4.1 案例一:水平越权遍历订单数据

某电商平台有个"我的订单"功能,点击订单详情时,前端发起GET /api/order/detail?order_id=12345请求。我登录自己账号后替换order_id为12346,响应直接返回了另一个用户的订单信息,包括收货人、手机号、详细地址。

我当时的验证思路是这样的:先用自己的两个账号互查确认可以互相访问,再用第三个不相关的账号去访问,确保不是账号间互相信任的问题。确认越权后,我进一步测试了批量导出——写脚本遍历order_id从10000到100000,看响应是否存在规律,最终确认可以批量拉取订单数据。

这个案例的根因很典型:服务端只做了登录校验,没做资源归属校验。后端的查询语句大概率是select * from orders where id = {order_id},压根没带user_id条件。

修复方案也很简单,查询条件改成select * from orders where id = {order_id} and user_id = {当前用户id}。

4.2 案例二:支付金额负数与优惠券复用

某内容平台有个打赏功能,打赏金额由前端传入/api/reward?amount=10&anchor_id=xxx。我直接把amount改成-1,发现账户余额居然增加了1块钱。

这里有个细节值得说明:负金额能成功,是因为后端只做了if amount < 0 then reject的校验,但用的是前端传入的amount直接更新钱包余额,没有用符号判断+绝对值校验的组合。我把amount改成-1后,服务端逻辑是余额 = 余额 + amount,变成了充值1元。

另一个案例是优惠券。某外卖平台的优惠券接口,使用后返回"code":0表示成功。我把同一个请求在Burp里手动重放了几次,发现第一次返回成功,第二次也返回成功,第三次才提示"优惠券已使用"。原因是服务端先判断优惠券状态,再核销,两步之间存在延迟,重放速度快就能卡住窗口期。用Turbo Intruder并发测试后确认,50个并发的请求里,有11个成功核销了同一个优惠券。

这类问题的修复思路是加状态更新的原子性判断,比如SQL写update coupon set status=1 where id=? and status=0,影响行数为0就说明已被使用。

4.3 案例三:密码重置流程跳步

某网站找回密码分三步:第一步输入手机号获取验证码,第二步校验验证码,第三步设置新密码。我抓包发现,前两步验证通过后,服务端在响应中返回了一个reset_token,第三步设置新密码时提交这个token。

绕过思路很明显了:我先用自己的手机号走完前两步拿到reset_token,然后把第三步请求里的手机号参数改成目标的手机号,token保持不变,直接提交。结果目标账号的密码被重置成功了。

问题出在:token只和验证码校验结果绑定,没有和手机号绑定。服务端应该在生成token时关系该手机号,第三步设置密码时同时校验token归属手机号,但我测试的系统完全没做这层绑定。

这个案例告诉我们,多步骤流程里的每一步,都必须携带统一的流程凭证,且凭证必须与发起用户强绑定。

4.4 案例四:管理接口无鉴权

某SaaS系统,主站是app.example.com,我发现有个子域名internal.example.com可以直接访问,打开是一个管理控制台登录页。顺手试了一下常见弱口令,没进去。然后在Burp里抓主站的登录请求,发现登录后的Token是JWT格式,解码后发现payload里带了"role":"user"字段。

我把JWT的role改成"admin"重新编码,替换到管理台的请求里,发现服务端直接用JWT的role字段做权限判断,没有和数据库里的真实角色做比对,管理台直接放行了。

JWT的坑在于,如果服务端不校验签名,或者秘钥是弱秘钥,改payload是很容易的。这个案例里,我甚至不需要知道秘钥——服务端压根没验签名,改了payload直接就能通过。

5. 逻辑漏洞挖掘避坑指南与修复建议

最后这部分,我把平时测试中容易踩的坑和测试完之后的修复思路一并说清楚,方便做安全建设和漏洞修复的同学直接参考。

5.1 测试中的三大坑:误报、连锁、合规

第一个坑是误报。逻辑漏洞和常规漏洞不一样,很多"看起来像漏洞"的现象其实是业务设计。比如积分排行榜里能看到别人的昵称,这不一定是越权,可能是产品功能。我判断一个逻辑漏洞是否成立,标准只有一个:这个操作或数据访问,是否超出了当前用户应有的权限范围。在报告里一定要结合业务场景描述影响,而不是单纯列现象。

第二个坑是连锁影响。测试越权时,如果一个接口返回了别人的手机号,就涉及个人信息泄露;如果还能进一步修改,比如改别人的收货地址,那就从信息泄露升级到了业务中断。写报告时要把危害等级拉高,但测试时要格外小心——别真的把别人的数据改了,我在测试时会尽量避免对他人数据的写操作,只做最小化的读取验证。

第三个坑是合规边界。逻辑漏洞测试本质上是在模拟攻击者,前提必须是你有授权。在授权范围外做测试,哪怕初衷是好的也可能出问题。我的原则是:白盒测试先在测试环境做充分验证,黑盒测试尽量用自己创建的账号和数据,不动真实用户资源。

5.2 逻辑漏洞修复的五个关键动作

修复逻辑漏洞,不能头痛医头。我建议按以下五条线同时推进:

  1. 所有对象级操作必须校验归属:服务端在查询、修改、删除任何资源之前,先判断当前用户是否有权操作该资源。这是最基础也最重要的一条。统一实现一个权限校验中间件,不要在每个接口里手写。

  2. 关键业务参数服务端计算:金额、积分、价格、折扣等涉及钱的参数,一律由服务端根据商品ID和用户身份实时计算,前端传什么就拒绝什么。前端只能传业务对象的ID,不能传价格。

  3. 多步骤流程加入全局会话状态:用Redis或数据库维护流程状态,生成一次性token,每一步都校验token与当前会话和当前用户是否匹配,步骤完成后立即销毁token。拒绝通过独立接口跳步。

  4. 并发场景使用原子操作:凡是"检查-更新"两步逻辑,必须保证原子性。数据库层面用条件更新(update ... where status = 0),代码层面用分布式锁,避免并发穿透。

  5. 敏感接口全量加频率限制:短信、邮件、验证码、登录、注册、找回密码等接口,必须限频。同一手机号、同一IP、同一账号分别做限制,前端限制只是锦上添花,服务端限频才是关键。

5.3 一个压箱底的测试小技巧

最后分享一个我自己的压箱底经验。挖逻辑漏洞的时候,别总盯着主流程,多注意"功能之间的跳转"。比如支付完成后的回调、第三方登录后的跳转、下单成功后的页面跳转,这些跳转点经常携带关键参数,比如订单号、用户ID、token。我见过很多系统,主流程的接口防护做得滴水不漏,但在"跳转带参"这个环节里,把用户的ID直接拼在URL上交给前端。

测试方法很简单:用抓包工具观察每次跳转的URL参数,凡是在URL里出现的用户标识类参数,全部尝试篡改一遍。这个技巧帮我发现了不少在甲方眼里"想不到"的漏洞,比闷头测主流程效率高多了。

逻辑漏洞这门功课,说到底拼的是对业务的理解深度。技术手段翻来覆去就那几板斧,但对业务流程的敏感度,是需要在一次次实战里磨出来的。希望这篇内容对你有实际帮助,下次再遇到业务逻辑问题的时候,能有个清晰的排查方向。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询