做小程序安全评估有些年头了,最近两年明显感觉到一个变化:以前接到的需求,十次里有八次是给 Web 站点做检查,现在反过来了,十次里有六七次是小程序。业务往轻量端迁移的速度,比大多数团队安全建设的速度快得多。很多项目上线节奏紧,前端同学把逻辑大量往客户端塞,服务端只管转发,结果就是表面看着是个小程序,底下的接口却处于半裸奔状态。这篇就聊聊微信小程序渗透测试的整体思路、实操环节里那些文档不会写的细节,以及几个我实际遇到过的案例改编。内容面向有一定 Web 安全基础、想把这套方法平移到小程序端的同学,也照顾刚入门、想知道“从哪下手”的新手。全程只谈授权范围内的安全评估方法、风险识别逻辑和修复建议,不涉及任何未授权操作。
1. 先划边界:授权与范围是小程序测试的第一道工序
1.1 为什么授权文件的分量比技术手段更重
小程序安全测试和 Web 测试最大的区别不在技术栈,而在“边界模糊”。Web 站点通常有明确的域名清单,扫一遍子域就大致知道范围;小程序不一样,它背后可能挂着一堆云函数、第三方 SaaS、消息推送回调、支付回调,这些东西从外部完全看不出来。如果授权文件只写了一句“对某某小程序开展安全测试”,那这个范围其实是虚的。
我习惯在开工前把范围拆成三层写进确认单:第一层是小程序本体,包括前端包体和它直接调用的业务接口;第二层是关联服务,比如配套的管理后台、开放平台接口、文件上传服务;第三层是明确排除项,比如第三方支付通道、云厂商基础设施、合作方系统。三层写清楚,后面测到边界才不会被叫停,也不会因为误触第三方系统惹麻烦。
还有一点经常被忽略:测试时间窗。小程序和 Web 不同,它没有“测试环境”这个概念,很多团队就是一套生产环境跑所有流量。你压测一下接口,真实用户可能就下单失败。所以时间窗要写死在授权里,比如“仅允许在凌晨 1 点到 5 点进行可能影响业务可用性的测试”,并且约定好紧急联系人和中止信号。这东西看着繁琐,真出事的时候是唯一的护身符。
1.2 测试范围的颗粒度该怎么定
范围写“某某小程序”,颗粒度太粗。我一般会要求客户提供一份接口清单或者至少是域名清单作为附件。没有清单也行,那就约定“以测试报告中列出的资产为实际测试范围,超出部分不作为结论依据”。这样既保护自己,也让报告更严谨。
颗粒度还要落到“账号”这个维度。小程序测试几乎离不开多账号对比,至少需要两个普通账号、一个管理员账号、一个未登录状态。有些团队只给一个测试账号,那水平越权这块基本没法测,只能靠接口猜测,结论的置信度会打折扣。我遇到过一次,客户只肯给一个账号,说另一个账号涉及真实用户数据不能提供。后来协商的方案是用他们内部造的测试数据开两个号,数据是假的,但逻辑链路是真的,这样既满足测试需求,也不碰真实信息。
提示:授权文件里一定要有“数据处理约定”这一条,明确测试过程中如果接触到真实用户数据,不得留存、不得外传、测试结束后如何销毁。这不是形式主义,是实操中真会踩到的坑。
1.3 测试环境的隔离与回滚预案
小程序没有天然的测试环境,但可以人为造一个隔离层。常用做法是让客户在测试窗口内把流量切到灰度集群,或者临时给测试账号打上白名单标记,让这些账号的请求走独立的数据分片。这样即使测试过程中触发了脏数据写入,也污染不到主库。
回滚预案要提前想清楚。比如测试“重复领取优惠券”这类业务逻辑时,很可能真的生成一堆脏订单。上线前跟业务方确认好:这些订单怎么标记、怎么批量作废、作废操作由谁执行。我在一个会员积分类项目里就吃过亏,测试时触发了积分重复发放,虽然金额不大,但清理起来花了半天,因为积分流水和订单流水是两套表,得两边对账。从那以后,凡是涉及资金或虚拟资产的测试,我都会先问一句“脏数据怎么清”,问清楚了再动手。
2. 小程序和Web的差异点:不能照搬思路的地方
2.1 运行环境封闭带来的测试路径变化
Web 测试里很多顺手的手段,在小程序里直接失效。比如你没法随便改浏览器地址栏参数,没法用浏览器插件注入脚本,没法靠改 Hosts 把域名指到本地。小程序的运行环境是封闭的,代码包要先下载、校验、再执行,中间还隔着一层运行时框架。
这个封闭性带来两个后果。一是测试路径变长:你得先把包弄下来,看懂结构,找到接口,再想办法观察通信。二是“前端即真相”这个假设不成立了。Web 时代你可以直接看源码逻辑,小程序虽然也能解包看代码,但代码往往经过压缩混淆,变量名全是单字母,读起来费劲。所以更高效的路子是“以接口为中心”,把包体当成线索来源,而不是当成主要分析对象。
我一般的顺序是:先解包提取配置和接口清单,再用抓包工具观察真实流量,两者对照,能快速定位哪些接口是前端直接暴露的、哪些是经过网关转发的。这个顺序比一上来就啃混淆代码效率高得多。
2.2 包体结构:为什么要先“看懂包”再动手
小程序的包体本质是个压缩包,解开之后大致能看到几类东西:全局配置文件、页面逻辑代码、样式文件、静态资源,以及一些编译产物。全局配置里通常藏着页面路由表、权限声明、分包配置,这些信息对梳理攻击面非常有用。比如路由表能告诉你这个应用一共有多少个页面,哪些页面是需要登录才能进的,哪些是公开的。
分包配置尤其值得看。主包受体积限制,通常只放核心逻辑,业务功能往往拆到分包里按需加载。这意味着你解主包只能看到一部分代码,剩下的得等运行时下载。实际测试中,我会把主包和能拿到的分包都过一遍,重点看分包里的接口调用和权限判断逻辑,因为很多开发者会把校验逻辑写在主包里,分包里直接信任主包传来的状态,这种设计一旦被绕过就是大问题。
还有一类容易漏的东西是静态资源里的注释和残留文件。有些团队打包时没清理干净,源码映射文件、调试配置、甚至测试环境的接口地址都留在包里。这些信息单看不算漏洞,但能极大降低后续测试的成本。
2.3 前端逻辑可见性带来的风险放大
小程序有个特点:业务逻辑大量前置。为了减少请求次数、提升体验,开发者会把校验规则、状态判断、甚至一部分权限控制写在客户端。这在 Web 上也常见,但小程序因为“看起来像个 App”,很多人潜意识里觉得它更安全,防护反而松懈。
举个典型场景:某预约类小程序,前端在提交预约前会判断“当前用户是否已有未完成订单”,如果有就弹窗提示不能重复预约。这个判断完全在前端做,服务端接口收到请求后直接落库,没有二次校验。测试时把前端校验绕过去,直接构造请求,就能产生重复预约。这类问题在小程序里出现频率相当高,因为前端框架提供了很方便的状态管理,开发者顺手就把业务规则写进去了。
我的经验是,凡是前端出现“灰掉按钮”“弹窗拦截”“条件跳转”的地方,都要去服务端验证一遍。判断标准很简单:如果我把这个前端判断去掉,服务端会不会拒绝?会拒绝,说明校验到位;不会拒绝,就是缺陷。
3. 资产梳理:从包体到接口的全景图
3.1 解包与目录结构的判读方法
拿到小程序包的过程不复杂,但细节多。常规做法是通过开发者工具或者抓包工具把包体缓存下来,找到对应的缓存目录,里面通常是一堆以哈希命名的文件,其中一个就是主包。识别方法是看文件头,压缩包有固定的魔数。找到之后解开,目录结构一般长这样:一个 app 级别的配置和逻辑文件,若干 page 目录,每个 page 下有对应的逻辑、结构和样式文件,再加一个存放静态资源的目录。
判读的重点不是逐行读代码,而是找“索引性”信息。全局配置里找路由和权限;页面逻辑文件里搜接口路径的特征,比如常见的请求方法名、基础地址变量、鉴权头的设置;静态资源目录里看有没有配置文件、证书、日志残留。我习惯在解包后先跑一遍关键词检索,把接口地址、密钥字样、调试开关这些一次性捞出来,形成一张初步清单。这个过程十分钟能完成,比漫无目的地翻代码高效得多。
3.2 接口清单的提取与分类
接口清单是整个测试的核心资产。提取方式有三种:静态提取、动态抓取、两者交叉。静态提取靠搜代码里的路径字符串,优点是全,缺点是可能包含大量废弃接口和拼接出来的动态路径;动态抓取靠实际操作小程序,优点是真实,缺点是覆盖不全,你没点到的功能就抓不到。
我一般先动态跑一遍主流程,把能触发的接口都记下来,再静态提取一遍做补充,最后交叉比对。比对的价值在于发现“隐藏接口”——代码里有但界面上没有入口的,这类接口往往是管理功能、测试功能或者老版本遗留,防护通常更弱。
分类维度我按三档走:公开接口(不需要登录态)、用户接口(需要普通用户态)、管理接口(需要更高权限)。这个分类直接决定了后面的测试策略。公开接口重点看有没有敏感信息泄露和未授权访问;用户接口重点看水平越权;管理接口重点看垂直越权和鉴权绕过。
3.3 域名与第三方服务的清点
小程序背后往往不止一个域名。业务接口一个、文件存储一个、消息推送一个、数据统计一个,有些还接了第三方的客服系统、内容审核服务。这些域名从包体里能提取出一部分,从抓包流量里能看到一部分,剩下的靠接口报错信息反推。
清点的目的是划清“能测”和“不能测”的界线。自有域名在授权范围内可以测;第三方服务的域名,即使流量经过,也不应该主动去打,最多记录“观察到与某第三方服务通信,未做进一步测试”。这一点在报告里要写清楚,既是专业性的体现,也是免责的需要。
我遇到过一个情况:某小程序把用户上传的图片直接传到第三方对象存储,返回的地址是公开可读的。这个问题算不算?算,但问题出在配置上,不在第三方服务本身。报告里我会写成“文件存储桶权限配置不当导致资源可被未授权读取”,修复建议是调整存储策略,而不是去测第三方的接口。这个区分很重要,写错了容易被质疑专业性。
4. 通信层测试:加密、签名与重放
4.1 证书校验带来的抓包阻力与应对思路
小程序默认会校验服务端证书,这意味着你直接用中间人方式抓包,往往会看到连接失败或者请求发不出去。这是第一道坎,很多人卡在这里就放弃了。
应对思路分两条。第一条是走“合法通道”:如果客户配合,可以让服务端临时下发一个测试用的证书链,或者在测试账号维度放开校验。这种方式最干净,不碰客户端,缺点是依赖客户配合度。第二条是走“客户端分析”:把包解开,看它有没有把校验逻辑写死在代码里、有没有可以通过配置关闭的调试开关。有些开发版本的小程序会保留调试模式,关掉校验就能抓包;正式版本通常没这么好心,那就得看运行时框架层面有没有可利用的缝隙。
需要强调的是,绕过证书校验这件事本身,在不同地区的合规要求下边界不一样。我个人的做法是:只在客户明确书面授权、并且测试环境是隔离集群的前提下才做客户端层面的改造,且改造只针对测试设备,不涉及任何分发。生产环境的正式包,我倾向于用服务端日志和流量镜像来观察通信,而不是硬啃客户端。
4.2 签名机制的常见设计缺陷
小程序接口普遍带签名,防篡改、防重放。签名机制的设计质量,直接决定了通信层测试的难度。我见过三种典型设计,安全性依次递增。
第一种是“固定密钥签名”,客户端代码里硬编码一个密钥,所有请求用它算签名。这种最弱,因为密钥就在包里,解包就能拿到,等于没有防护。第二种是“动态密钥签名”,密钥在登录时下发,存在本地,后续请求用这个密钥算签名。比第一种强,但如果密钥长期不轮换、不跟设备绑定,一旦泄露就是全量失守。第三种是“密钥加时间戳加随机数”,每次请求的签名都不同,服务端校验时间窗和随机数唯一性。这种设计到位的话,重放基本不可行。
测试时我会重点验证三件事:签名是否覆盖了所有关键参数(有没有漏掉某个字段可以随意改)、时间窗有多长(太长就有重放空间)、随机数是否真的做了去重(重复提交同一个随机数看服务端反应)。这三点里任何一点有缺口,通信层的防护就是纸糊的。
4.3 重放与参数篡改的判断标准
重放测试的关键不是“能不能重放成功”,而是“重放成功之后造成了什么后果”。一个查询接口能重放,危害有限;一个下单接口能重放,就是资损风险。所以判断标准要落到业务影响上。
参数篡改也是同理。改价格、改数量、改用户标识,每一种改法对应不同的风险等级。我在一个电商类项目里做过这样的梳理:把所有接受数值型参数的接口列出来,逐个标记“这个参数被篡改后影响什么”。影响金额的标红,影响状态的标黄,只影响展示的标绿。这样测下来,优先级一目了然,报告也好写。
注意:重放和篡改测试很容易产生脏数据,尤其是涉及订单、库存、优惠券的接口。动手前先确认清理方案,测试时控制次数,别为了验证一个低危问题造出一堆垃圾数据。
5. 鉴权与会话:最容易出问题的一层
5.1 登录态是怎么维持的
小程序的登录态维持方式跟 Web 有相似也有不同。相似的是都依赖令牌,不同的是获取令牌的流程多了一步:先调登录接口换一个临时凭证,再用这个凭证去换业务令牌。这个链条里任何一环校验不严,都会出问题。
我见过最常见的设计缺陷是“临时凭证可重复使用”。按设计,临时凭证应该是一次性的,用完即废。但有些实现里没做失效处理,同一个凭证可以反复换令牌,这就给了批量注册、批量登录的可乘之机。测试方法很简单:拿一个凭证连换两次令牌,看第二次是否成功。成功就是缺陷。
另一个常见问题是令牌的有效期和刷新机制。有些小程序令牌有效期设得极长,几个月不失效,且没有绑定设备或登录环境。令牌一旦泄露,攻击窗口极大。还有些刷新机制设计得很随意,旧令牌刷新后不失效,等于令牌数量只增不减。这些都属于设计层面的问题,测试时要结合业务场景判断危害。
5.2 水平越权与垂直越权的测试方法
越权是小程序测试的重灾区,因为很多团队把权限校验放在了前端路由层面,服务端只认令牌不认资源归属。
水平越权的测试方法很直接:用 A 账号的令牌去访问 B 账号的资源。资源标识通常是订单号、用户 ID、地址 ID 这类东西。测试时把请求里的标识换成另一个账号的,看返回的是不是对方的数据。这里有个技巧:不要只看返回状态码,要看返回内容。有些接口返回 200 但内容是空的,这不一定是安全的,可能是它做了过滤但没做校验,换个参数可能就出来了。
垂直越权是普通账号去访问管理接口。管理接口的路径往往有特征,比如带 admin、manage、console 这类字样,或者参数里带角色标识。测试时把普通令牌带到管理接口上,看是否被拒绝。还有一种隐蔽的垂直越权:接口本身不区分角色,但前端根据角色决定是否展示入口。这种最容易漏,因为从界面上看普通用户根本点不到那个功能,但接口是通的。
5.3 接口“裸奔”的识别与验证
所谓裸奔,就是接口完全不校验身份,谁调都返回数据。这类问题在公开接口里相对常见,比如“获取配置”“获取公告”“获取首页推荐”这类。单看危害不大,但如果返回内容里带了内部信息,性质就变了。
识别方法是在未登录状态下遍历接口清单。凡是返回 200 且带业务数据的,都要看一眼数据内容。我遇到过一个案例:某小程序的“获取帮助文档”接口不需要登录,返回的 JSON 里除了文档内容,还带了一个内部管理系统的地址和一组测试账号。这个信息泄露本身是中危,但它把攻击面直接扩大了,后续如果那个管理系统防护薄弱,就是连锁反应。
验证裸奔接口时要注意,有些接口看似不需要登录,实际上服务端通过其他方式做了限制,比如校验来源、校验频次、校验参数签名。这些限制可能很弱,但不代表没有。测试结论要写准确:“该接口未校验用户身份,仅依赖某某弱限制”,而不是笼统写“未授权访问”。
6. 数据落地:本地存储与缓存里的敏感信息
6.1 本地缓存的排查重点
小程序会在本地存不少东西:登录态、用户信息、业务缓存、配置项。存储位置一般在运行时的本地存储区,通过开发者工具或者设备文件系统能看到。
排查重点是“不该存的存了”。典型的有:明文存储的手机号、身份证号、银行卡号;存储了完整的令牌且没有加密;缓存了服务端返回的全量用户资料,包括一些界面上不展示的字段。这些信息一旦设备被他人接触,或者本地存储被其他手段读取,就是泄露。
我一般的检查顺序是:先看登录相关的键,再看用户信息相关的键,最后扫一遍所有键名,找看着像敏感信息的。键名通常有规律,比如带 token、user、auth、session 这些字样的优先看。检查时不仅看当前值,还要看历史值有没有残留——有些实现是覆盖写,有些是追加写,追加写的话旧令牌可能还躺在里面。
6.2 日志、剪贴板与其他侧信道
除了本地存储,还有几个容易被忽略的落地位置。日志是一个,开发阶段打的调试日志如果没在发布版本里关掉,可能会把请求参数、返回内容、甚至令牌打到日志里。小程序环境下日志不像 App 那么容易读,但通过开发者工具或者特定调试模式还是能看到。
剪贴板是另一个。有些小程序为了“便捷分享”,会把邀请码、订单号自动复制到剪贴板,用户一粘贴就暴露了。这个设计本身不算漏洞,但如果复制的是敏感信息,就有问题。还有截图和录屏,涉及敏感信息展示的页面(比如账单、证件信息)如果没有做防截屏处理,在特定场景下也是风险点。这类问题危害等级不高,但报告里提一句显得专业,也帮客户完善细节。
7. 业务逻辑漏洞:自动化工具测不出来的部分
7.1 业务流程的逆向梳理方法
业务逻辑漏洞没法靠扫描器发现,只能靠人把流程走一遍,然后想“哪一步可以被跳过、被重复、被乱序”。我的做法是画流程图,把正常流程的每一步、每个分支、每个状态转换都标出来,然后逐条问三个问题:这一步能不能跳过?这一步能不能重复执行?这一步能不能在错误的状态下执行?
以会员积分类业务为例,正常流程是:下单、支付、确认收货、发放积分、积分入账。要问的是:不支付能不能直接确认收货?确认收货能不能重复触发?积分发放能不能在订单取消后仍然执行?这三个问题对应的就是三种典型缺陷:状态校验缺失、幂等性缺失、状态机设计错误。
梳理流程时,最好把前端界面和后端接口对照着看。前端界面上没有的入口,不代表接口不存在;前端界面上的顺序限制,不代表后端也限制了。这个对照过程往往能发现最有价值的问题。
7.2 几类高频逻辑缺陷的识别特征
第一类是“条件竞争”。凡是涉及“先查询再写入”的逻辑,都有竞争窗口。比如“每人限领一张券”,实现是先查用户有没有领过,没有就发。如果两个请求同时到达,都查到没有,就都发了。识别特征是:代码里有“查询-判断-写入”三段式,且没有加锁或者唯一约束。测试方法是并发提交,看是否超额。
第二类是“数值篡改”。价格、数量、折扣、积分抵扣,这些数值如果由客户端传,且服务端不重新计算,就能被改。识别特征是:请求参数里出现了本该由服务端计算的值。
第三类是“状态机跳跃”。订单状态从“待支付”直接跳到“已完成”,跳过中间环节。识别特征是:状态变更接口只校验目标状态合法,不校验当前状态是否允许这个转换。
第四类是“流程绕过”。多步骤流程里,直接调用最后一步的接口,跳过前面的校验步骤。识别特征是:每个步骤的接口独立鉴权,但步骤之间没有依赖校验。
这四类缺陷在小程序里出现频率很高,因为小程序的前端流程做得比较重,开发者容易产生“用户只能按顺序操作”的错觉。实际上接口是暴露的,顺序完全可以自己定。
8. 常见问题排查与报告输出
8.1 抓不到包、解不开包的排查路径
抓不到包是最常见的问题,排查顺序我一般是这样的:先确认网络链路是否通,用最简单的请求测试一下;再看是不是证书校验拦住了,表现为连接建立后立刻断开或者报证书错误;再看是不是走了非标准端口或者私有协议,这种情况普通的抓包工具默认不解析,得手动配。
解不开包也常见。原因可能是包体加密、可能是分包没下全、可能是文件头被改过。排查方法是先看文件头魔数对不对,不对就往前找偏移;对的话看压缩包是否完整,不完整就想办法触发分包下载。有些小程序做了代码保护,解出来的代码是加密的,这时候就别硬啃了,转向动态分析,从流量和运行时行为入手。
还有一种情况是“包能解开但代码读不懂”,变量名全混淆了。这时候的有效手段是找字符串常量,接口路径、参数名、错误提示这些通常不会被混淆,顺着字符串往回找调用链,能还原出大部分逻辑。
8.2 测试中断与异常处理
测试过程中被打断的情况不少:客户突然说生产环境有异常要暂停、接口限流导致测试跑不下去、测试账号被封了。这些都要有预案。
限流的情况,我的处理是降低请求频率,加随机间隔,并且优先保证关键用例跑完。账号被封的情况,提前准备多个测试账号,并且记录每个账号的使用范围,避免全军覆没。生产异常的情况,立刻停止测试,保留现场日志,配合客户排查是不是测试引起的。
提示:整个测试过程要有时间戳日志,记录每个操作的时间、接口、参数概要。出问题的时候这份日志就是自证清白的依据,比事后回忆靠谱得多。
8.3 报告怎么写才有人看
安全报告最容易犯的毛病是“技术正确但业务看不懂”。我写报告的习惯是每个问题分四段:现象是什么(一句话说清楚)、怎么复现(步骤可执行)、影响是什么(落到业务损失上)、怎么修(给具体方案,不说套话)。
影响描述要具体。写“存在越权风险”没用,要写“普通用户可以查看其他用户的订单详情,包含收货地址和手机号,影响范围是全部注册用户”。修复建议也要具体。写“加强权限校验”没用,要写“在订单详情接口中增加资源归属校验,校验当前令牌对应的用户 ID 与订单所属用户 ID 是否一致,不一致返回 403”。
还有一个细节:风险等级要跟业务场景挂钩。同样是信息泄露,泄露的是公开的企业信息,和泄露的是用户身份证号,等级差好几个档。定级时我会跟客户的安全负责人对一遍,确认他们对业务敏感度的判断,避免自说自话。
9. 从测试到防护:几条落地建议
9.1 服务端才是最终的信任边界
这句话说了很多年,但在小程序项目里依然大量失效。前端做的任何校验,都只能算“体验优化”,不能算“安全控制”。按钮置灰、弹窗拦截、路由守卫,这些东西的作用是让正常用户不犯错,不是防止恶意用户乱来。
落地做法是把所有业务规则在服务端重写一遍。前端校验保留,用于提升体验;服务端校验必做,用于兜底。两边规则要一致,不一致的时候以服务端为准。这个工作量不小,但比出事之后补救便宜得多。
9.2 接口设计阶段就埋好防护点
很多问题在接口设计阶段就能规避。比如所有涉及资源访问的接口,统一加一个“资源归属校验”的中间件,而不是每个接口自己写一遍,写多了总会漏。比如所有涉及金额的接口,金额一律服务端计算,客户端传的值只做参考、不做依据。比如所有状态变更接口,统一走状态机校验,非法转换直接拒绝。
这些都属于“设计约束”,一旦定下来,后续开发自动遵守,比事后一个个修效率高。我在几个项目里推动过这套约束,初期开发有抵触,觉得麻烦,跑一段时间之后就习惯了,因为线上问题确实少了。
9.3 把安全测试嵌进发布流程
小程序发布频率高,一周一版很常见。如果每次发版都靠人工全量测一遍,成本扛不住。可行的做法是分级:核心接口(资金、权限、用户信息)每次发版必测,用自动化脚本跑一遍回归用例;非核心接口按季度做一次全量;新功能上线前单独做一次针对性测试。
自动化这块,重点不是覆盖多少接口,而是覆盖多少“关键判断”。比如越权检测,不需要把每个接口都测一遍,只需要把带资源标识的接口挑出来,用两个账号交叉访问,这个脚本很好写,维护成本也低。关键是坚持跑,跑一次两次没意义,跑一年才能形成防护网。
10. 几个案例改编后的复盘
10.1 会员积分项目的重复发放问题
某会员类小程序,积分发放逻辑放在支付回调里。回调接口做了签名校验,但签名只覆盖了订单号,没覆盖金额和状态。测试时构造了一个已支付订单的回调请求,改小金额重放,服务端照单全收,积分按原订单金额发放。因为回调没有幂等设计,同一个订单回调多次,积分就发多次。
这个问题暴露的是两个点:签名覆盖不全、回调无幂等。修复方案是把签名覆盖到全部关键字段,同时给回调加唯一约束,同一个订单号只处理一次。这个案例我后来在好几个项目里都见过类似变体,说明它不是个案,是设计模式层面的通病。
10.2 预约类项目的越权查看
某预约类小程序,订单详情接口用订单 ID 作为唯一标识,没有校验归属。测试时用 A 账号登录,把请求里的订单 ID 换成 B 账号的,返回了对方的姓名、电话、预约时间。因为订单 ID 是自增的,遍历成本极低。
修复方案是在接口里加归属校验,同时把订单 ID 换成不可预测的随机串。前者是根本,后者是纵深防御。很多团队只做后者,觉得 ID 猜不到就安全了,这是错的。ID 不可预测只是提高了门槛,真正的防护是归属校验。
10.3 一个被忽略的配置泄露
某工具类小程序,包体里留了一个测试环境的配置文件,里面有测试环境的接口地址和一组测试账号。测试环境防护比生产环境弱得多,接口基本没有鉴权。这个问题本身是低危,但它把攻击面从生产环境扩展到了测试环境,性质就变了。
修复方案是打包流程里加一道检查,确保测试配置不进正式包。这个事说起来简单,做起来难,因为打包是自动化的,配置是人工放的,中间很容易漏。可行的做法是在 CI 流程里加一个扫描步骤,匹配已知的测试配置特征,命中就阻断发布。
11. 我个人的几点实操体会
做了这么多项目,最大的感受是:小程序的安全问题,技术复杂度其实不高,难的是“想全”。Web 时代积累的那套方法论,平移过来八成是适用的,剩下两成需要根据小程序的特性做调整。调整的核心就两条:一是认识到前端逻辑的可见性更高,所以前端做的任何校验都不能当数;二是认识到资产边界更模糊,所以测试前必须把范围划清楚。
第二个体会是,报告的价值远大于测试本身。测出问题不难,难的是让开发团队愿意改、知道怎么改。我现在的报告里,修复建议占的篇幅比问题描述还多,每个建议都写到“改哪个文件、加什么判断、返回什么状态码”这个粒度。开发看了直接能动手,改的效率高很多。
第三个体会是关于节奏。小程序迭代快,安全测试如果跟不上迭代节奏,就会变成“上线后补测”,那时候改的成本是上线前的好几倍。比较理想的状态是把安全检查嵌进需求评审和提测环节,需求评审时看一眼有没有新的敏感数据流,提测时跑一遍自动化回归。这两步做了,大部分低级问题在上线前就能拦住。
最后一个体会是关于心态。做安全评估这行,容易陷入“找漏洞”的执念,觉得找到的高危越多越厉害。实际上客户要的不是漏洞数量,是风险可控。有些问题技术上算漏洞,但业务上影响微乎其微,硬要报高危反而显得不专业。判断危害要落到真实业务场景里,这个能力比技术本身更难练,也更值钱。