小程序安全测试实战:前端透明与接口暴露下的漏洞挖掘与修复
在移动互联网的存量阶段小程序已经成为几乎所有线上业务的标配载体从点餐、出行、购物到政务、金融、教育你叫得出名字的行业基本都长出了自己的小程序。与此同时安全问题也肉眼可见地多了起来。标题里说的“好挖”本质上是在描述一个现状小程序的前端代码和接口暴露面比传统 Web 和 App 更容易被摸透漏洞密度也更高。这篇文章就来聊聊我在这类项目里做安全测试时踩过的坑、挖到过的几个高危漏洞以及它们背后通用的根因和修复思路。先说明一个前提全文讨论的所有测试都是拿到授权后的合规安全评估也就是甲方委托或平台 SRC安全应急响应中心任务。如果你只是在工具里输入别人家的域名乱测那不叫挖掘漏洞叫违规越权风险自担。安全测试的第一课永远是授权第二课才是技术。1. 为什么小程序漏洞那么“容易打出来”先看架构特点1.1 前端代码完全透明小程序包随便拆小程序和传统 App 最大的不同在于它的代码默认就在用户本地。微信、支付宝、百度、字节等平台都要求开发者将前后端代码打包成包体上传审核用户扫码后全量下发到本地。这个包在手机本地是明文存储的解包工具一把梭连 GUI 版的都有拖进去就能看到全部源码。很多人觉得前端代码泄露无所谓以前 Web 的 JS 从源码里能看到什么无非就是逻辑乱一点、混淆多一些。但小程序不一样它的大部分业务逻辑和接口路径都写在代码里而且很多团队开发小程序时压根就没做代码混淆。我见过一个餐饮类小程序解包后直接在 app.js 文件里躺着完整的地图密钥、微信支付商户号、AES 加密 Key甚至连数据库地址都有——不是生产库但也够吓人了。这意味着安全测试的第一步往往不是暴力扫描而是“读代码”。你能一口气看到所有请求接口、所有加解密函数、所有 API Key、所有隐藏的 WebView 页面地址。传统黑盒测试里需要靠 fuzz 半年才能碰到的资产在小程序这里解包就能拿到一份近乎完整的地图。不是漏洞好挖是资产太透明。1.2 业务逻辑前置开发者把服务端校验放到了前端如果说代码透明只是增加了信息暴露量那更核心的问题在于很多小程序的业务逻辑判断压根就不在服务端而是直接写在前端 JS 里。典型的表现包括登录态校验页面跳转前在 JS 里判断this.globalData.userInfo是否存在但后端接口没有对 token 有效性做校验。权限判断某个管理接口是否可调用前端根据userInfo.role来决定按钮是否展示但接口本身不校验 role。金额计算前端算好总价传给后端后端没有重新核算。验证码逻辑图形验证码、短信验证码的发送频率限制完全靠前端禁用按钮 60 秒抓包绕过就跟呼吸一样简单。这种“前端做校验、后端不设防”的架构几乎是中小型小程序团队的标配。原因很简单小程序审核需要时间业务方要求快速上线后端只负责存数据和转发所有交互逻辑都在前端实现。如果测试人员恰好懂一点接口测试概念那发现高危漏洞只是时间问题。1.3 更新节奏极快安全完善度普遍落后于业务迭代小程序一个很突出的特点是“发版像刷朋友圈”。微信审核快的话当天就能过很多团队把小程序当成活动页来迭代今天上线一个抽奖明天上线一个秒杀后天又接入了一个第三方 SDK。代码质量参差不齐很多历史接口忘了下线或者新加了促销逻辑之后漏掉了鉴权。我印象很深的一次测试某企业的小程序首页正常得像个不太熟的新朋友但翻源码时发现了一段被注释吞掉的老接口路径是/admin/v1/user/export后端压根没校验直接就能拖全量用户数据。问他们同事为什么没删得到的回答是“这个接口是三个月前活动用的后来没人调了就一直放那了。”旧接口、测试环境接口、开关配置项这些“影子资产”才是真正的高危背后推手。2. 实战前的准备授权边界和影子资产梳理2.1 先确认授权再谈推荐和工具这节放在所有技术之前。我见过太多人一上来就问“有没有抓包工具推荐”工具真的不值钱值钱的是授权书。正规的安全测试流程一定有一个数据包起点这个起点可能是甲方安全团队发来的授权委托书也可能是 SRC 平台已经对外公布的范围说明。在边界还没有确认清楚之前不要随便对一个第三方接口做深度测试哪怕它看起来没有防护。实操中我一般会先确认三个边界授权范围是否涵盖某一个小程序下的所有域名、二级域名、子路径还是只限定了某个功能模块。是否允许使用自动化工具进行扫描。很多甲方只允许人工手动验证对自动化扫描有明确限制。测试时间窗口。比如某些电商平台在促销期间不允许测试避免影响到线上正常业务。这些内容看起来无聊但每次测试开始前我都会白纸黑字再确认一遍。真出了问题授权书是你唯一能保护自己的东西。2.2 小程序包怎么解解完之后重点找什么不同平台的小程序包格式不一样但思路类似先拿到安装包文件在本地缓存目录里后缀一般是 .wxapkg 或类似格式然后是解析和还原目录结构。常见的处理手法有从手机本地缓存目录找到小程序包文件因为平台已经把它完整下载下来了。用通用小程序解包工具拆分出 JS 文件和资源文件。如果包内有分包需要把各个子包都拆开因为很多高危接口藏在分包里特别是“新人专区”“管理后台”“员工签到”这类不常用模块。拆完之后我会按优先级扫这些文件app.js、utils.js、config.js全局配置、密钥、请求封装。所有接口路径按域名去重按功能分类列出所有/api/、/v1/、/admin/路径。加密函数位置搜索 AES、RSA、DES、Base64、sign、nonce、timestamp 等关键词。第三方平台密钥地图、支付、推送、云存储等服务的 secret 字段。这一步完成之后整个小程序的后端接口地图基本就浮出水面了。就算不了解业务背景光凭这份地图也能找到一大批可能的测试点。2.3 从入口到后端API 发现可以把范围扩大好几倍小程序本身只是入口它调用的后端接口往往分布在多个域名上。很多人测试时只盯着 appid 跟主域名打转其实漏掉了大头。常见的情况是主业务域名 A负责商品浏览、登录、下单。静态资源域名 BCDN可能包含测试页和配置文件。第三方服务域名 C地图、客服、直播、支付回调。数据统计域名 D埋点、用户行为上报经常是未鉴权的裸接口。我习惯把所有从 JS 源码里提取出来的完整 URL 做成一张表按域名、方法、路径、参数、返回值去重分类。分类后在 burp 里把每个域名单独建一个 scope避免大量杂包干扰。在这一步我基本不跑重大漏洞扫描器。先手动看一遍源码结构再对每个接口试几个通用 Case比上来就丢一个全量扫描更高效也更符合甲方要求的“低风险、低噪音”。3. 三个高危漏洞实战案例拆解3.1 越权拿订单改一个参数就能看到别人的收货地址这是我在某电商类小程序的实际授权测试中遇到的经典垂直越权。来看思路。小程序下单后订单列表功能已经上线源码里找到了订单详情的接口GET /api/order/detail?orderId123456。表面上登录后访问自己的订单没问题但我顺手测试了把 orderId 改成相邻数值比如 123457、123458。结果响应直接返回了其他用户的订单记录包括手机号、收货地址、商品明细几乎所有敏感字段都有。这个问题的根因很典型服务端对订单详情接口只校验了用户是否登录没有校验该订单是否属于当前用户。也就是“我登录了”和“这是你的订单”被混为一谈。修复思路很简单只要在服务端获取订单明细时同时校验请求用户的 user_id 与订单归属的 buyer_id 是否一致然后配合风控系统做异常访问频率监控即可。越权漏洞在小程序里出现频率极高原因是前端每个页面都会把数据 ID 当成参数传给后端但很多后端框架只关注“能不能查到”不关注“该不该给你查”。我在测试中总结了一个通用规律但凡接口路径里包含主键 ID 的都值得测一遍越权不管这个参数叫 orderId 还是 goodsId 还是 taskId。3.2 硬编码密钥前端写死 AES 密钥加密直接变成摆设另一个印象深刻的高危漏洞出在某政务类小程序。该类小程序的数据包业务参数看起来做了加密每个请求体的字段都像乱码说好听点是“有安全意识”说难听点是“把加密做成了摆设”。原因在于我在 JS 源码里搜索到了两个东西一个是固定的 AES 密钥字符串另一个是初始化向量 IV两个都写在同一个 config.js 文件里。任谁拿到这个文件都能把请求体里的密文解出来复制一套同格式的数据自由重放。我测试了一个查询用户信息的加密接口解密后发现了明文身份证号和银行卡号等敏感数据。这里多说一句很多人以为前端加密就是安全这其实是认知误区。真正的安全边界在服务端前端加密顶多只能防住普通窥探防不住认真研究的人。这个案例的修复路径比较标准将加解密密钥迁移到服务端统一管理不再下发前端。前端如果需要加密应采用安全环境下可用的 Token 换取会话密钥而不是固定密钥。加密流程要配合服务端签名校验、时间戳防重放、随机数防伪造缺一不可。身份证、银行卡等敏感数据在接口返回时进行脱敏处理只返回尾号几位。3.3 支付金额前端可控一分钱支付了一个几百块的商品支付金额类漏洞可以说是小程序高危漏洞里最能引发老板焦急的一类。某教育和培训类小程序上线了一个课程报名功能前端源码里能够看到提交订单时把金额字段直接传给了后端接口服务端没有重新从商品表里读取价格。整个商户收款流程是用户选择课程 → 前端算出应付金额 → 后端接口生成支付参数 → 拉起微信支付。正常情况下这个流程没问题但如果测试时把提交订单接口里的amount599000改成amount1后端生成的支付单金额就会变成 0.01 元支付完成后服务端收到支付回调依然会判断为支付成功然后给用户下单成交。这个洞的修复痛点不在代码而在产品流程。服务端一定要以商品 ID 为准从数据库重新读价格前端传的金额最多只能做展示或金额比对不能作为扣款依据。同时建议引入风控规则异常金额订单、频繁改单请求、短期多单同设备登录等行为都需要拦截告警。我在实际复测中还发现一个有意思的延伸问题该类小程序在退款上也存在金额参数可改的情况因为支付金额被前端写死到了退款接口的输入参数里。如果开发方修补了下单逻辑但没同步审查退款逻辑照样可能被反向刷。修复完漏洞后一定要把同一业务链条的下单、支付、退款、公告、名单导出全都过一遍。4. 常见问题与排查技巧记录4.1 小程序包解不开或者代码被混淆了怎么破不是所有小程序都能一键解包的。有些团队为了安全做了加固具体表现为包体内关键 JS 被加密或者 .wxapkg 文件本身就是混淆过的、变量名和函数名都是 a、b、c。我的处理思路是先解包再观察资源文件里是否包含完整的加密块。如果主包混得厉害就去看分包和插件目录很多项目只加固了主包分包和第三方插件却完全没处理。如果连分包也加密了就考虑在运行时下手用调试工具 Hook 住网络请求库直接拦截明文请求和响应数据。也就是说即使你不去逆源码只要能看清发出的是什么、收到的是什么就已经拥有了一半的测试能力。代码混淆更多是给人类阅读增加障碍不是给请求抓包增加障碍。安全测试链路只要卡在“请求构造”这个节点上就没什么搞不定的。4.2 参数全加密接口就像加密盲盒根本测不动有些小程序的每个请求都会带 sign 签名参数也做了加密看起来确实比没加密的强一些。但前端密钥都躺在本地包里理论上不存在真正测不动的情况。实操中我一般按三个顺序来在源码里定位加密逻辑入口找到 Key 和 IV写一段本地脚本模拟生成签名。如果没有现成密钥就抓一个正常包修改部分参数后原样重放观察服务端是否校验签名。很多开发方为了性能只在特定节点做签名校验其他接口根本不校验。如果签名校验做得完整就去寻找不经过加密链路的接口比如图片上传、文件导出、客服聊天、WebView 页面。在测试某个金融类小程序时我卡在“所有业务接口带签名”上整整一个上午最后从一个 JS 里隐藏的 WebView 链接进入了他们的管理后台登录页后台接口既不签名也不鉴权复杂度直接从困难模式回到了练习模式。4.3 报告怎么写才不会被人拒收挖到漏洞只是完成了前半部分写得一手好报告才是闭环。很多初学尝试的人提交给甲方的报告里经常只有“我有漏洞”和两三张截图这种报告等于没写。我自己写报告时会遵循一个四段式结构概述这个漏洞出现在哪个页面、哪个接口用了什么方式复现风险等级定级依据是什么。复现步骤按顺序写清每一步操作附带关键请求包和响应对应关系最好给出修改前后的对比截图。影响范围这个漏洞能影响多少用户数据、是否能被远程利用、是否存在横向影响。修复建议至少给出服务端校验、鉴权、加密迁移等具体做法最好能辅助研发同事直接定位到代码逻辑层级。报告的本质是“帮助修复”不是为了炫技。说明清楚研发同事才能快速修复这也会让后续复测沟通顺畅很多。另外发现漏洞后切忌公开渲染细节。正规的做法是通过 SRC 平台或甲方指定的渠道提交等待确认和修复。技术讨论不该成为黑产的教程。写在最后我的两个真实体会小程序为什么“好挖”除了架构透明、迭代快这些客观原因还有一个隐藏在业务背后的因素很多团队在启动小程序项目时默认它就是“一个小页面”根本没有按正规业务系统的标准去做身份体系和服务端权限设计。等到用户量上来、数据多了再想补安全框架就很难受了。我评估过大量小程序后发现高效率的测试方法从来不是靠某个神器工具而是靠“先读代码、再理接口、最后构造请求”的这个流程。前端代码越放松后端接口就越该紧张。对开发者而言与其天天盯着市面上新出的漏洞类型不如先把四件事做好服务端二次校验、接口归属校验、密钥服务端托管、旧接口下线机制。这四件事做好了大部分高危漏洞就自动消失了。最后分享一个我在实际测试中养成的习惯每测完一个小程序我都会把发现的问题整理成一张“研发自查速查表”按高危、中危、低危列出问题描述和对应代码位置交给对应的研发同事去修。这样做的回报是双向的——研发同学修复效率高了我复测时就很少再遇到“修了 A 洞、打开 B 洞”的循环返工。安全最落地的形式就是把复杂攻击链条拆成一组简单明了的修复步骤。