【SRC】金融场景挖掘技巧

admin 2026-01-08 01:49:52 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详解金融SRC漏洞挖掘技巧,涵盖注册绕过、KYC复用、支付逻辑缺陷如并发竞态、负值反冲及整数溢出等核心风险。结合优惠券滥用、越权查询、云存储配置错误等场景,提供代码分析与实战利用思路,帮助安全人员深入理解业务逻辑漏洞,提升金融系统安全挖掘效率。 综合评分: 88 文章分类: SRC活动,WEB安全,渗透测试,漏洞分析,红队


负值反冲

在支付或退款接口中,如果传入的金额参数为负数,且后端没有严格校验金额必须为正,可能导致用户账户余额不减反增

金融类转账严格要求不能为负数,当修改金额为负数时就会导致负值反冲

image-20251120110520581

int64%20导致金额溢出

金融系统常见操作:

  • 金额%20×%20精度(比如%20×10000%20或%20×100000)
  • 金额求和(累计%20risk%20amount)
  • transfer/settlement%20金额偏大(企业/跨境/贷款/还款)

如果攻击者传入一个极大的数值,可能导致系统计算时溢出,变成一个负数或一个很小的数

repayAmount%20:=%20precision.ConvertAmountByBase(
 %20 %20bo.RepayAmount.String(),
 %20 %20eaBo.MoneyMultiBase,
)

userName,%20email%20:=%20f.getUserInfo(ctx,%20userInfo)

now%20:=%20time.Now()
provisionExpireTime%20:=%20f.getProvisionExpireTime(ctx,%20bo.ChannelId,%20bo.UserId,%20bo.PlatformUserId,%20now)
paymentExpireTime%20:=%20f.getPaymentExpireTime(ctx,%20bo.ChannelId,%20now)

常见于后端使用%20int64%20处理金额,int64%20的范围有限(只有%209.22e18),金额乘倍率(比如%20×100000)后很容易超过这个范围,一旦超过CPU%20会直接把高位截掉,并且go不会自动检查溢出,传参过大会导致%20price%20/%20amount%20可被改为负数、0.01%20等

image-20251118153559079

image-20251118153520380

篡改参数免手续费进行转账

在涉及外币或虚拟币的交易中,攻击者尝试修改接口中传输参数,使最终结算金额显著降低

image-20251120110702048

计算最终金额的时候,会有很多参数参与运算,如渠道、汇率等,这时候可以尝试纂改相关参数免除手续费

image-20251128150611909

转账时如果传入xx_id值为0的情况下,使后端%20ampaign_result.code%20==%200%20,%20导致可以实现无手续费进行转账

if remittanceTicketReq.GetFeeWaivedAmount()%20!= 0 {
 %20  if remittanceTicketReq.GetCampaignId()%20== "" {
 %20 %20 %20 %20logger.Error(ctx,%20format: "invalid_fee_xxx_id")
 %20 %20 %20  return common.ErrWalletInvalidRequestParam
 %20 %20}
}

image-20251201115257442

优惠券场景 优惠券码爆破

优惠券码未采用加密,并且没配置网关限流策略,导致可以高并发遍历

image-20251201111453629

通过对voucher_code进行遍历,根据回显字段长度不同判断优惠券是否存在,如下图存在为820、819,不存在为798

image-20251201111530424

优惠券无锁限制重复领取

优惠券核销时,系统未严格校验该券的唯一使用记录或总使用次数

加锁安全的情况

func%20applyCouponWithLock(amount%20float64)%20float64%20{
 %20 %20coupon.mu.Lock()%20 %20 %20 %20  //%20加锁
 %20 %20defer%20coupon.mu.Unlock() //%20解锁

 %20 %20discounted%20:=%20amount%20-%20coupon.Discount
&nbsp;%20&nbsp;&nbsp;if&nbsp;discounted%20<&nbsp;0&nbsp;{
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20discounted%20=&nbsp;0
&nbsp;%20&nbsp;%20}
&nbsp;%20&nbsp;%20coupon.UsageCount++
&nbsp;%20&nbsp;&nbsp;return&nbsp;discounted
}

后端未加锁,导致可以重复领取

func%20applyCoupon(amount%20float64)%20float64%20{
&nbsp;%20&nbsp;%20discounted%20:=%20amount%20-%20coupon.Discount
&nbsp;%20&nbsp;&nbsp;if&nbsp;discounted%20<&nbsp;0&nbsp;{
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20discounted%20=&nbsp;0
&nbsp;%20&nbsp;%20}

&nbsp;%20&nbsp;%20atomic.AddInt64(&coupon.UsageCount,&nbsp;1)

&nbsp;%20&nbsp;&nbsp;return&nbsp;discounted
}
优惠券叠加使用

系统允许用户在单笔订单中,同时使用多张原本设计为互斥或限用一张的优惠券

image-20251204105300720

正常单张使用

image-20251201113637619

多张优惠券叠加使用

image-20251201113756324

信息查询场景 越权查询他人敏感信息

金融类对越权漏洞是十分严格的,像一般的越权查看个人信息、订单,是渗透测试的必测项目

image-20251119165630215

例如用户%20A%20在查询自己的信息时,通过修改请求参数为用户%20B%20的订单%20ID,成功查询到用户%20B%20的账单信息

image-20251204104318708

image-20251204104229848

稍微复杂一些的隐藏参数情况

image-20251203163346295

函数实现是优先从post传参userid去查询的,然后再去解token提取uid进行查询

但是正常调用的时候,post传入的是{},因此走到第二优先级的解token提取uid进行查询,那我们该如何去挖掘这类的漏洞呢,我们可以通过搜集history返回包中的参数组成字典去fuzz,如下图返回包中有 userphone、uid、role等字段

image-20251203162838679

通过fuzz发现uid可作为查询参数,越权查询他人的信息

image-20251203163817197

image-20251203163851509

Toc&ToB网关配置错误接口混用

在%20gRPC%20+%20Gateway%20架构里,越权问题通常发生在普通用户能调用原本只允许管理员的%20RPC%20方法。

  • 生成的%20HTTP%20路由没有绑定权限检查,任何人都能访问
  • Gateway%20没加角色校验,或者拦截器配置错误,token%20可以被忽略或者被默认当作%20admin

假设admin管理端域名为a.com,toc用户端域名为b.com,通过burp%20history导出一份a.com

image-20241202145003839

右键选中%20save%20item,导出对应的xml文件

image-20241202145302261

导出文件格式还需要做进一步的处理:

  1. Base64%20解码

  • 将%20XML%20中的 <request> 元素的内容进行%20Base64%20解码,解码得到完整的%20HTTP%20请求内容。
  1. 解析请求

  • 根据%20HTTP%20请求格式,第一行包含了请求方法和路径,例如:POST%20/api/login%20HTTP/1.1,提取 /api/login
  1. 提取%20POST%20数据

  • 通过检查是否遇到%20HTTP%20请求头结束标志 \r\n\r\n,来提取请求体
  1. 写入%20CSV%20文件

  • 将提取到的请求方法、API%20路径和%20POST%20数据写入到%20CSV%20文件中

导出文件命名为 burp_history.xml,导出文件为 burp_history.csv

脚本文件

import%20os
import%20xml.etree.ElementTree&nbsp;as&nbsp;ET
import%20csv
import%20base64

input_file%20=&nbsp;"burp_history.xml"
output_file%20=&nbsp;"burp_history.csv"

if&nbsp;os.path.exists(output_file):
&nbsp;%20&nbsp;&nbsp;try:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20os.rename(output_file,%20output_file)
&nbsp;%20&nbsp;%20except%20PermissionError:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;print(f"文件%20{output_file}%20正在被占用,请关闭相关程序后重试。")
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;exit(1)

tree%20=%20ET.parse(input_file)
root%20=%20tree.getroot()

try:
&nbsp;%20&nbsp;%20with%20open(output_file,%20mode="w",%20newline="",%20encoding="utf-8")&nbsp;as&nbsp;csvfile:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20csv_writer%20=%20csv.writer(csvfile)
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20csv_writer.writerow(["Method",&nbsp;"API%20Endpoint",&nbsp;"POST%20Content"])

&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;for&nbsp;item%20in%20root.findall('./item'):
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20request_element%20=%20item.find('request')

&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;if&nbsp;request_element%20is%20not%20None&nbsp;and&nbsp;request_element.text:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;try:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20request_data%20=%20base64.b64decode(request_element.text).decode("utf-8",%20errors="ignore")
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20except&nbsp;Exception&nbsp;as&nbsp;e:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;print(f"Base64%20解码失败:%20{e}")
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;continue

&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20header_body_split%20=%20request_data.split("\r\n\r\n",&nbsp;1)

&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;if&nbsp;len(header_body_split)%20==&nbsp;2:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20headers%20=%20header_body_split[0]
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20body%20=%20header_body_split[1].strip()

&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20header_lines%20=%20headers.split("\r\n")
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;if&nbsp;header_lines:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20first_line%20=%20header_lines[0].strip()
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20parts%20=%20first_line.split("%20")
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;if&nbsp;len(parts)%20>=&nbsp;2:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20method%20=%20parts[0].upper()
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20path%20=%20parts[1]
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;else:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20method%20=&nbsp;"UNKNOWN"
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20path%20=%20first_line

&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20post_content%20=%20body&nbsp;if&nbsp;method%20==&nbsp;"POST"&nbsp;else&nbsp;""
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20csv_writer.writerow([method,%20path,%20post_content])
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;else:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20csv_writer.writerow(["ERROR",&nbsp;"INVALID%20REQUEST",&nbsp;""])
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;else:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20csv_writer.writerow(["ERROR",&nbsp;"EMPTY%20REQUEST",&nbsp;""])

&nbsp;%20&nbsp;&nbsp;print(f"已成功将数据整理为%20{output_file}")

except%20PermissionError:
&nbsp;%20&nbsp;&nbsp;print(f"无法创建或写入文件%20{output_file},请检查权限。")
except&nbsp;Exception&nbsp;as&nbsp;e:
&nbsp;%20&nbsp;&nbsp;print(f"发生意外错误:{e}")

处理效果

image-20251202154135953

intruder测试选择 Pitchfork 模式进行发包

image-20241202152747911

payload%20encoding 取消勾选

image-20241202153846692

开始测试

image-20251202154105230

资源存储场景 合同资料遍历获取

合同、发票等敏感文件存储在一个可预测的路径下(如 /docs/contract/2025/ID_0001.pdf

img

攻击者通过爆破或递增%20ID%20的方式,可批量下载其他用户的合同文件

image.png

S3存储桶配置不当/泄露

许多金融机构使用云存储服务(如 AWS S3 或阿里云 OSS)来存储文件。如果存储桶 Bucket 的权限配置错误(如设置为 Public Read),可能导致所有存储的资料对外公开。

https://github.com/dark-kingA/cloudTools

image-20251118153909154

image-20251118154002622

SSRF及绕过

服务端存在可发起外部网络请求的接口(如图片加载、URL 抓取)。攻击者利用此接口,让金融服务器作为跳板,访问其内网敏感资源或未授权的云服务 API。

image-20251118182235376

但通常企业内部会有白名单域名,当我们请求的url匹配则可以绕过检测

image-20251118184618667

成功触发dnslog

image-20251201153958537

使用第三方厂商开发

JWT/密码硬编码未修改

某盘系统中,系统初始化的工程中,定义了默认值的appidjwttoken加密密钥

image-20251201160325500

导致攻击者可以利用该密钥伪造任意用户的 JWT Token,从而绕过身份验证机制

image-20251201154518199

运维后门

第三方运维有时候为了方便更新维护,会在内部脚本、后端代码中直接引用外部 URL 来获取密码,例如:

curl http://password.example.com/db-prod-pass
wget http://x.x.x.x/getKey?env=prod

这种设计虽然便于统一管理,但等同于主动为攻击者开放后门接口

image-20251204104557490

下面就是从SSO外部接口获取最新系统密码的实战例子

image-20251204104631703


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:泷羽Sec-track hyyrent《【SRC】金融场景挖掘技巧》

【SRC】金融场景挖掘技巧 网络安全文章

【SRC】金融场景挖掘技巧

文章总结: 本文详解金融SRC漏洞挖掘技巧,涵盖注册绕过、KYC复用、支付逻辑缺陷如并发竞态、负值反冲及整数溢出等核心风险。结合优惠券滥用、越权查询、云存储配置
WeChatHook 网络安全文章

WeChatHook

文章总结: 本文介绍基于Python的WeChatHook工具,通过pymem库动态修改微信内存特征码以绕过版本过低限制。该脚本替换WeChatWin.dll中
评论:0   参与:  0