前言
在近期的安全测试中,笔者发现了几个与响应状态码验证逻辑相关的、非常有意思的安全问题。这些漏洞的共同特点是:关键业务请求执行完成后,后端并未在响应中返回强有效的校验字段(如openid、token等),这表明后端的状态验证机制可能存在缺陷。这引发了对响应状态码安全性进行深入思考的必要性。
案例一:从信息泄露到任意用户登录接管
本案例综合利用一处信息泄露漏洞和未经验证的响应包,实现了一套有效的任意用户登录接管流程。所有涉及的漏洞均已被修复,相关数据包格式已改变,下文将利用部分截图进行佐证。我们跳过了常规的信息收集步骤,直接锁定一个景区服务小程序,对其功能点进行逐一测试。对于景区类业务,测试重点应关注信息泄露及逻辑越权问题。

测试到“我的意见”功能点时,标准操作流程是:先清空所有历史请求记录,再点击该功能点,观察整个过程中发出了哪些数据包。这是在测试任何站点功能前都应完成的前置工作——确保一个功能点对应一整套清晰的历史记录,避免其他无关请求包的干扰。

由于原站点已修复,截图可能不全,此处陈述关键结论。在填写必填项并提交后,BP(Burp Suite)历史记录中收到了相关请求。对这些请求进行排序和逐一观察,可以定位到一个会回显个人信息的接口。通过浏览器直接发送请求测试发现,该接口无需任何鉴权字段即可返回数据,并且其中的ID参数可被遍历,导致用户姓名和电话等二要素信息泄露。
https://xxxx/minibgs2/public/index/suggest/sug_info/?id=570


接下来,可以编写脚本自动化完成以下操作:遍历url中的id值,并使用正则表达式匹配其中的姓名和电话。将获取到的信息保存到xlsx表格中。通过此方法,共获取了约5千条人员二要素信息。这为后续的任意用户账号接管创造了条件。


继续测试站点的其他业务,发现“我的套餐券”功能点是一个需要手机号登录的入口,里面记录了个人在景区的酒店订单等信息。

使用手机号接收验证码登录后,观察登录完成的响应包。后端没有返回任何常见的鉴权令牌,只有一个data参数,其值看起来非常像Base64编码。直接使用工具解码后发现,这个所谓的鉴权参数竟然只是“手机号 + 固定数字0572030”。
登录成功响应包示例:
{
"code": 100,
"msg": "验证通过",
"data": "MDU3MjAxxxxxxxxx0MzE1NjYwMA=="
}

于是,利用其他账号登录微信,将之前表格中获取到的其他用户手机号进行相同的编码(手机号+固定后缀,然后Base64),制作成响应包。在目标账号的登录处进行拦截,将其返回包替换为我们制作的响应包,即可顺利接管他人账号,查看并操作其订单信息。

案例二:支付状态签名复用绕过
这是我遇到过最“奇葩”的一类支付逻辑漏洞。本以为通过修改支付状态进行绕过的漏洞已经绝迹,但它仍然存在于某些支付场景中,尤其是在票务生成类业务里。绕过支付后,系统便会生成对应的有效凭证。其根本原因在于:后端没有验证前端传来的支付状态是否真实,只要接收到“成功”状态就执行出票逻辑。此漏洞分析仅用于技术研究,请严格遵守《网络安全法》。
本次测试目标定位在一个医院挂号业务。

随意选择一个科室进行挂号操作,走完正常的支付下单流程,并记录下所有请求。

定位到关键的支付状态确认接口。在用户付款后,该接口直接返回“支付成功”,而没有其他明显的校验操作。首先想到的是尝试替换返回包,以达到支付绕过的效果。
接口地址示例:
https://xxxxxxx/api/bz/order/Sta/appoint
支付成功响应示例:
{
"code": 0,
"message": "支付成功",
"data": null
}

重新下一个挂号订单进行测试。在支付页面,不进行支付,而是直接关闭(叉掉)支付二维码弹窗。此时,系统会触发一个验证支付状态的请求。然而,当我满心欢喜地拦截并替换这个请求的响应体为上述“支付成功”的JSON后,订单状态却并未改变,仍然显示未支付。

冷静思考一下:响应体主体确实没有鉴权字段,那么问题是否出在响应头中携带了其他凭证呢?回去仔细观察成功的响应包,果然在响应头中发现了一个形似签名或追踪ID的字段:tlogTraceId: jyvU1094795431411044352。
好消息是我找到了替换响应包失败的原因;坏消息是,如果这个参数是与订单号绑定的,那么我可能就无法突破了。

但这时,我产生了一个大胆的想法:这个签名(或追踪ID)是否可以复用呢?既然JWT令牌在某些情况下可以复用,那么这个签名也值得一试。渗透测试就需要脑洞大开。于是,我复制了整个支付成功的响应包(包括响应头和响应体)。

再次下单,在关闭支付二维码并捕获到支付状态判断请求后,拦截此请求。这次,不是只替换响应体,而是将整个响应(包括tlogTraceId等头部信息)替换为之前保存的成功响应包。最终,页面显示挂号成功,并生成了就医凭证二维码。虽然可能在订单列表中看不到这条成功记录,但已经绕过了支付步骤,直接生成了可用的就医凭证,可以凭此前往医院排队使用。


案例三:定位鉴权参数绕过票据生成
通过上一个案例,我不禁思考:难道只有这种返回包特征明显的接口才会存在这类问题吗?答案是否定的。我的理解是,任何“生成”类业务(如生成订单、票据、凭证)都可能存在某个请求的鉴权不完整。这个思路可以拓展到任意功能点。假设完成某个关键请求后,后端没有进行严格的权限校验就返回了结果,那么都可以尝试用此方法测试。但前提是,必须定位到那个控制生成状态的关键请求!
常见的状态码参数变换思路:
code: 500 ---> 200
statr: 0 ----> 1
data: 500 ----> null
本次目标是景区的一个购票业务,选择游玩项目后下单。

走正常业务流程进行支付下单,并捕获所有数据包,定位到支付成功后的关键返回包。

支付成功后,保留返回包。这个返回包很长,乍一看没有明显的鉴权参数,大部分是时间戳等信息,缺少订单核心信息。这是否意味着,只要能定位到控制出票的鉴权字段,就可以尝试绕过呢?

通过仔细比对支付成功和失败时的返回包,并逐一测试关键参数,最终确定是由三个状态码参数控制后端的出票结果。在未支付时拦截返回包,将其中2个参数从0改为1,1个参数从1改为0。
修改的关键参数(示例):
tyep: 1 ----> 0
status: 0 ----> 1
status: 0 ----> 1


修改完成后放行数据包,由于后端没有验证前端传来的状态是否真实,导致系统直接生成了有效票据,实现了真正意义上的“0元购”——无需支付一分钱即可完成购买。


拓展:响应状态码的更多攻击面
在漏洞挖掘中,若想从众多安全研究者中脱颖而出,要么拥有独特的测试思路,要么能够接触到更多、更深的测试资产(即更多的功能点和攻击面)。信息收集手法大同小异,但很多资产需要特定权限的账号才能进入测试。本节所述方法并非首创,有其他师傅分享过类似思路,笔者在此结合自身实践进行复现与深化。需要明确的是,这类方法不仅适用于登录框,任何存在权限控制步骤的场景,都可以尝试利用响应状态码进行绕过。
相信很多师傅都遇到过这类场景:在某个流程中,必须提交真实、有效的信息才能进入下一步,且无法跳过。如果我们没有真实信息,可以尝试提交虚假信息,然后仔细观察响应包的返回情况,判断是否有绕过的可能。

在提交虚假信息时,通常会收到如下错误返回包:
{
"success": false,
"code": "A000200",
"message": "无记录",
"msg": "无记录",
"data": null
}

此时,我们可以返回应用的其他功能模块,进行正常的点击操作,触发其他接口请求。然后在BP的历史记录中,寻找其他接口响应成功时的返回包格式。如果找到字段结构相似的成功响应,就可以尝试用它来替换当前步骤的错误响应,看看是否能绕过验证。
以下是一个从其他接口获取的成功响应示例:
{
"success": true,
"code": "A00000",
"message": "成功",
"msg": "成功",
"data": true
}
那么,我们重复提交虚假信息,在点击下一步时拦截请求,将错误的响应包替换为上述正确的响应包。

成功绕过验证后,便进入了后续的门店信息管理等功能点。这时,新的攻击面又出现了,可以继续测试是否存在未授权访问等问题。这种方法的危害性取决于站点的敏感性。如果是一个敏感系统的后台,通过这种方式短暂“欺骗”前端进入,可能会发现更多的高危漏洞。直接拼接后台接口通常会有鉴权,而修改响应包的方式则是在“欺骗”前后端的校验逻辑。


总结与思考
关于支付逻辑漏洞和状态绕过的手法还有很多。在漏洞挖掘过程中,不断复盘总结,拓宽对不同业务场景的测试思路,相信各位安全研究人员都能发现危害性更大的安全问题。值得注意的是,这类漏洞的发现,往往需要对HTTP协议交互过程有细致的观察力,并能灵活运用抓包改包工具。对Web应用前后端逻辑的深入理解,是挖掘此类深层逻辑漏洞的关键。如果你对安全测试和网络协议分析有更多兴趣,欢迎在云栈社区与大家一起交流探讨。