1. 项目概述一次典型的小程序安全渗透实战最近在参与一个内部的安全众测项目目标是一个基于微信生态的线上商城小程序。这类小程序通常承载着用户登录、商品浏览、下单支付等核心业务一旦出现安全漏洞后果不堪设想。我的任务就是扮演“攻击者”的角色尝试找出它的安全弱点。整个过程就像一次侦探游戏从最不起眼的接口开始抽丝剥茧最终竟发现了一条从越权访问到任意用户登录的完整攻击链。这不仅仅是找到一个漏洞更是理解小程序安全架构薄弱环节的绝佳案例。无论你是刚入门的安全爱好者还是想加固自己产品的开发者这次实战经历中的思路、工具和技巧都值得细细品味。接下来我就把这趟“挖洞之旅”的完整过程包括思路、操作、踩过的坑毫无保留地分享出来。2. 核心漏洞原理与攻击链路拆解在深入操作之前我们必须先搞清楚要寻找的目标是什么以及它们为何危险。本次实战涉及两个关键漏洞越权漏洞和由此引申出的任意用户登录漏洞。它们通常不是孤立存在的而是一条逻辑链上的不同环节。2.1 越权漏洞权限边界的模糊地带越权漏洞顾名思义就是超越了当前用户应有的权限访问或操作了本不该接触的数据或功能。在小程序的上下文中这极其常见。小程序前端与后端通过API接口通信每个接口理论上都应该有严格的权限校验A用户只能看A的数据不能看B的。但如果后端开发同学偷了个懒只在界面层做了限制而服务器端接口缺乏对请求发起者身份的二次校验漏洞就产生了。举个例子查看个人订单的接口路径可能是https://api.xxx.com/order/list?user_id123。一个安全意识薄弱的实现是前端小程序在请求时固定传入了当前登录用户的ID比如123后端接收到这个user_id123后就直接去数据库查询并返回用户123的所有订单。问题来了如果我通过抓包工具把请求中的user_id参数改成124后端还会校验这个“124”是不是我本人吗如果不会那么我就越权看到了用户124的订单信息。这就是最典型的“水平越权”。如果这个user_id参数还能遍历比如从1递增到10000就能批量获取大量用户数据危害性急剧上升。2.2 任意用户登录漏洞身份体系的崩塌如果说越权是“看到了不该看的东西”那么任意用户登录就是“变成了不该变的人”。这个漏洞的危害是毁灭性的攻击者可以直接以任意用户的身份登录系统拥有该用户的全部权限包括查看隐私信息、进行消费、修改资料等。在小程序中这个漏洞往往源于对用户身份凭证的生成、校验环节存在逻辑缺陷。常见的攻击入口有修改返回包在登录过程中服务器返回一个标识用户身份的字段如uid,token。如果客户端完全信任这个返回结果并且后续所有请求都基于这个结果那么拦截修改返回包将uid改为目标用户的ID就可能实现“登录”。参数污染某些登录或获取用户信息的接口接受诸如mobile、openid等参数。如果后端仅根据这些参数来识别用户而没有与当前会话进行强绑定校验攻击者传入他人的手机号或OpenID就能获取他人的登录态或敏感信息。Token生成缺陷用户登录后获得的Token令牌如果设计不当比如是简单的用户ID加密或者有规律可循攻击者就可能伪造或预测其他用户的Token从而直接冒用身份。在本案例中正是通过一个越权访问的接口我们获取到了关键的身份标识信息进而利用服务端逻辑缺陷最终实现了任意用户登录。这条攻击链路清晰地展示了一个点的疏漏可能导致整个防线崩溃。3. 前期信息收集与环境准备“工欲善其事必先利其器”。在开始动手测试之前充分的信息收集和合适的工具准备能事半功倍。对于小程序测试信息收集的维度与传统Web应用有所不同。3.1 小程序源码与结构分析微信小程序运行在微信客户端内其业务逻辑代码JavaScript、WXML、WXSS是下载到本地执行的。这意味着我们可以获取到这些前端代码进行反编译和分析这是小程序安全测试的一大优势。第一步获取小程序包在微信中打开目标小程序让其完全加载。然后找到手机存储中小程序的缓存包。安卓手机的路径通常类似于/data/data/com.tencent.mm/MicroMsg/.../appbrand/pkg/。这里会存放后缀为.wxapkg的小程序包文件。你需要一台已Root的安卓手机或者使用一些无需Root的备份提取工具来获取这个包。第二步反编译源码拿到.wxapkg文件后使用开源的反编译工具如wxappUnpacker对其进行解包。成功后会得到小程序的完整目录结构主要包括app.js、app.json、app.wxss全局配置和样式。pages/目录各个页面的逻辑.js、结构.wxml和样式.wxss文件。其他自定义组件和工具文件。第三步代码审计关键点反编译后的代码可能有些混淆但关键信息依然可寻。我们需要重点关注网络请求搜索wx.request、wx.uploadFile等API调用点找出所有与后端通信的接口URL、参数和请求方式GET/POST。全局变量查看app.js中定义的全局数据特别是存储token、userInfo的地方。页面参数传递分析页面跳转wx.navigateTo时传递的参数这些参数可能被用来构造请求。配置文件仔细查看app.json和各个页面的.json文件了解页面路由和配置。注意反编译行为可能违反小程序平台的使用条款请仅在获得明确授权的安全测试环境中进行。对反编译代码的分析应聚焦于理解应用逻辑和寻找潜在的攻击面而非用于非法目的。3.2 代理抓包环境搭建要拦截和修改小程序与服务器的通信我们必须配置抓包环境。由于微信小程序强制要求使用HTTPS并且有严格的证书校验我们需要让手机信任我们自己的CA证书。工具准备抓包软件Burp Suite Professional功能最全、Charles对HTTP/HTTPS抓包友好、Fiddler Everywhere。测试手机一台专门用于测试的安卓手机建议Root或iOS手机需安装描述文件。网络环境确保手机和电脑在同一局域网。配置步骤安装CA证书在电脑的抓包工具中生成CA证书并导出。在安卓手机上将证书文件放入SD卡进入“设置”-“安全”-“加密与凭据”-“从存储设备安装”选择证书文件安装。务必为证书起一个易识别的名字如“BurpCA”。在iOS上通过Safari访问抓包工具提供的特定地址如http://burp下载并安装描述文件。配置代理在抓包工具中设置监听所有接口如0.0.0.0:8080。在手机Wi-Fi设置中配置手动代理服务器地址为电脑的局域网IP端口为抓包工具设置的端口如8080。绕过小程序证书绑定这是关键一步。新版微信和小程序默认开启了证书绑定SSL Pinning会校验服务器证书是否与预期一致导致即使安装了Burp的CA证书也无法解密HTTPS流量。对于安卓Root手机可以使用JustTrustMe或SSLUnpinning等Xposed模块或者使用Frida脚本来禁用证书绑定。对于非Root环境过程会更复杂可能需要使用虚拟环境或特定版本的微信客户端。验证抓包打开手机浏览器访问一个HTTP网站如http://neverssl.com查看抓包工具中是否出现流量。然后访问一个HTTPS网站如https://www.baidu.com确认HTTPS流量也能被成功解密和查看。实操心得在测试初期我花了大量时间在证书绑定问题上。一个可靠的绕过方案是测试的基石。对于安卓MagiskLSPosedTrustMeAlready模块的组合在我多个测试中表现稳定。同时准备一个旧版本的微信客户端如果目标小程序兼容有时能省去很多麻烦。4. 漏洞挖掘实战从越权到任意登录环境准备好后真正的狩猎开始了。我们的策略是先广撒网寻找可能的越权点再深挖一点看能否将其升级为更严重的漏洞。4.1 接口遍历与越权测试启动抓包工具打开目标小程序进行正常的浏览、点击操作。同时手动触发所有你能想到的功能登录、注册、查看个人信息、浏览商品、加入购物车、查看订单、修改收货地址、查看优惠券等等。目标是尽可能多地让小程序发出网络请求并在Burp Suite的Proxy - HTTP history中记录下所有请求。关键操作流程筛选与归类在Burp的Target - Site map中可以看到捕获的所有主机和接口。将目标API域名下的接口整理出来。重点关注以下特征的接口包含/user/、/profile/、/order/、/address/、/coupon/等敏感路径的。请求参数中明显包含user_id、uid、mobile、order_id等标识ID的。使用GET方法的查询类接口因为它们参数暴露在URL中易于修改。测试越权以查看订单为例正常流程使用你的测试账号A登录进入“我的订单”页面。在Burp中捕获到这个请求例如GET /api/order/list?page1user_id10001。修改请求将这个请求发送到Burp的Repeater模块。将URL参数中的user_id10001修改为user_id10002其他部分如Cookie、Token保持不变。发送并观察点击Send发送修改后的请求。仔细观察返回的HTTP状态码和响应体。成功越权如果返回了状态码200并且响应体中包含了用户10002的订单数据那么恭喜一个水平越权漏洞到手了。务必截图保存请求和响应。权限校验有效如果返回403禁止访问、401未授权或者返回一个空列表、错误信息如“无权访问”则说明后端做了校验。扩大测试如果对10002越权成功不要停。尝试将user_id修改为一个不存在的ID如99999或者使用Burp的Intruder模块设置user_id为payload进行批量遍历注意控制速率避免触发风控。测试其他敏感接口对获取用户信息/api/user/info、收货地址列表/api/address/list、优惠券列表/api/coupon/my等接口重复上述步骤。注意有些接口的ID参数可能不在URL中而是在POST请求的JSON body里同样可以在Repeater中修改。注意事项在进行越权测试时务必使用你自己的两个测试账号A和B进行验证绝对不要使用真实的、未知的用户ID这是职业道德和法律底线。你的目标是证明漏洞存在而非窃取数据。4.2 关键发现泄露敏感标识的接口在本次测试中通过对用户个人中心相关接口的遍历测试我发现了一个信息泄露点。在请求GET /api/user/profile时这个接口本应只返回当前登录用户的基本公开信息如昵称、头像服务器的返回包中竟然包含了一个字段internal_uid。这个internal_uid是一个纯数字看起来像是数据库中的自增主键。更重要的是当我尝试用之前越权测试的方法在请求中附加?user_id目标ID参数去访问另一个用户的profile接口时这个接口竟然没有校验我可以直接获取到任意用户的internal_uid以及他们的昵称和头像。漏洞点分析后端逻辑错误/api/user/profile接口的设计初衷可能是查询“当前用户”的信息。它通过前端携带的Token或Session来识别当前用户并从数据库中取出该用户的internal_uid和其他信息返回。但是当请求中额外带了user_id参数时后端的逻辑可能是如果提供了user_id则查询该user_id的用户否则查询当前登录用户。这个逻辑在没有严格校验“提供的user_id是否等于当前登录用户的user_id”时就造成了越权信息泄露。敏感信息泄露internal_uid是系统内部用于唯一标识用户的关键字段。它的泄露为后续更深入的攻击提供了“弹药”。4.3 漏洞升级利用泄露信息实现任意登录拿到internal_uid后我开始思考这个ID在系统中还有什么用会不会用在登录或者会话维持的环节我重新审视了登录流程。正常登录流程抓包分析用户输入手机号和密码或验证码。小程序发送请求POST /api/auth/loginBody为{“mobile”: “138xxxx1234”, “password”: “...”}。服务器验证成功返回响应{“code”: 200, “msg”: “success”, “data”: {“token”: “eyJhbGciOiJ...”, “user_info”: {“nickname”: “xxx”, “internal_uid”: 10001}}}。小程序将token存入本地缓存后续所有请求在Header中带上Authorization: Bearer token。这里的关键是token。这个token通常是一个JWTJSON Web Token或类似的自定义令牌服务器通过解析它来确认用户身份。那么token的生成是否与internal_uid有关我尝试了一个大胆的假设如果token只是简单地对internal_uid进行了某种可逆或可预测的编码那么我是否可以直接伪造token测试步骤解密Token我将自己账号登录后获得的token复制出来放到在线JWT解码网站如 jwt.io尝试解码。幸运的是这个token没有签名验证这是一个严重的安全失误其Payload部分直接明文包含了{“uid”: 10001, “exp”: 173...}等信息。这里的uid值正是我的internal_uid。构造恶意Token既然token的Payload是明文的且服务器不验证签名那么我就可以自己构造一个。我使用获取到的其他用户的internal_uid比如10002按照同样的格式伪造一个token。虽然我无法生成有效的签名部分但由于服务器不校验所以只保留Header和Payload部分甚至去掉签名部分都可以。替换Token发起请求在Burp Repeater中我选择了一个需要登录态的接口例如GET /api/user/secure_info。我将请求头中的Authorization: Bearer 我的真实token替换为Authorization: Bearer 我伪造的包含uid10002的token。结果验证发送请求。服务器返回了状态码200并且响应内容赫然是用户10002的隐私信息如绑定手机号、账户余额等。这意味着服务器仅通过解析我伪造的token中的uid字段就认为我是用户10002从而实现了任意用户登录漏洞原理总结越权漏洞/api/user/profile接口存在逻辑缺陷允许通过user_id参数越权查询其他用户的敏感信息internal_uid。Token设计缺陷系统使用的身份令牌Token未采用安全的签名校验机制如JWT的HS256甚至可能是明文或简单编码导致令牌可被伪造。身份校验缺失服务器在验证Token时仅依赖于Token中携带的uid字段没有将该uid与当前会话的其他凭证如初始登录的IP、设备指纹等进行二次绑定校验。这三个环节的失守串联成了一条完整的攻击链使得一个普通的信息泄露漏洞升级为了高危的任意用户登录漏洞。5. 漏洞修复方案与安全开发建议发现问题是为了更好地解决问题。对于开发者和企业来说了解如何修复和预防此类漏洞至关重要。5.1 针对已发现漏洞的紧急修复修复越权接口 (/api/user/profile)后端强制校验在任何涉及用户资源的接口中后端必须进行严格的权限校验。不能依赖前端传递的用户ID。服务器端应该从当前有效的会话Session或已验签的Token中提取当前用户的真实ID并仅使用这个ID进行数据库查询。代码示例伪代码# 错误示例直接使用请求参数 user_id request.get(‘user_id’) user_data db.query(“SELECT * FROM users WHERE id %s”, user_id) # 正确示例从已验证的Token中获取 current_uid get_current_user_id_from_token(request.headers[‘Authorization’]) # 此函数应验证Token有效性并解析uid user_data db.query(“SELECT * FROM users WHERE id %s”, current_uid)移除敏感信息除非必要否则不要在公开接口中返回internal_uid这类系统内部主键。可以使用对外暴露的、无规律的UUID来代替。重构身份认证与Token机制使用强签名的JWT立即启用JWT的签名算法如HS256或RS256。服务器签发Token时使用安全的密钥进行签名验证时也必须校验签名有效性。任何篡改Payload或伪造Token的尝试都会因签名无效而被拒绝。增加Token黑名单/刷新机制实现Token的注销和刷新功能。当用户修改密码、登出或管理员禁用账户时将旧Token加入黑名单。短期访问令牌搭配长期刷新令牌使用提升安全性。绑定额外因子在Token的Payload中可以加入登录时间戳、设备指纹哈希值等。服务器验证时不仅校验签名和有效期还可以核对设备指纹是否发生变化对异常登录进行二次验证或告警。5.2 小程序安全开发最佳实践除了紧急修复更应从开发流程上建立安全防线。权限校验原则最小权限原则用户只能访问其授权范围内的资源。服务端校验是唯一真理所有权限校验必须在后端进行。前端界面隐藏或禁用按钮仅仅是用户体验不能作为安全手段。对所有输入进行校验包括URL参数、POST Body、Header等。确保用户ID等参数是预期的格式和范围。安全的身份认证设计使用成熟的认证方案如OAuth 2.0、OpenID Connect或基于框架提供的成熟认证库。Token安全存储与传输小程序端使用wx.setStorageSync存储Token但需知悉其仍有被反编译提取的风险。关键操作应要求二次密码验证或生物识别。敏感操作风控对登录、修改密码、支付等操作实施IP频率限制、设备识别、行为分析等风控策略。网络安全与配置全站HTTPS确保所有接口都使用HTTPS并正确配置SSL/TLS禁用弱加密套件。安全的通信对特别敏感的数据考虑在HTTPS之上再进行一次应用层的加密。接口限流与防爬对公开和非公开接口都实施合理的限流策略防止恶意扫描和暴力破解。安全测试与监控代码审计在开发过程中和上线前进行专门的安全代码审计重点关注业务逻辑漏洞。定期渗透测试邀请内部安全团队或外部白帽子进行模拟攻击测试。日志与监控记录详细的访问日志和错误日志监控异常访问模式如频繁遍历ID、大量登录失败并设置告警。6. 常见问题与排查技巧实录在漏洞挖掘和后续的修复验证过程中会遇到各种各样的问题。这里记录了一些典型场景和解决思路。6.1 抓包问题排查问题1小程序HTTPS流量抓不到全是Tunnel to或CONNECT请求。原因这是HTTPS隧道连接。小程序或微信的证书绑定SSL Pinning生效了阻止了中间人代理对TLS流量的解密。解决确认证书安装首先检查手机是否已正确安装并信任了Burp/Charles的CA证书。在安卓设置中查看“受信任的凭据”-“用户”选项卡下是否存在你的抓包工具证书。使用更强力的绕过工具对于安卓Root设备JustTrustMe模块可能在新版微信上失效可以尝试SSLUnpinning或使用Frida脚本动态Hook。可以搜索针对当前微信版本和小程序框架的特定Frida脚本。尝试旧版本安装一个较旧版本的微信客户端如两年前的版本有时能避开严格的证书绑定。使用虚拟环境在VirtualXposed、太极等应用内安装微信和小程序这些环境可能更容易配置抓包。问题2能抓到包但请求和响应都是乱码或加密的。原因小程序可能对API通信的数据体进行了自定义的加密或编码以增加反爬和安全性。解决搜索加密函数反编译的小程序源码中搜索encrypt、encode、CryptoJS、AES、RSA等关键词找到前端加密的逻辑。分析加密方式通常是为了防止数据被明文窥探加密密钥可能硬编码在JS中或从服务器动态获取。你需要分析出加密算法、模式、密钥和IV初始化向量。使用Burp插件如果找到了加密逻辑可以尝试编写Burp的Python扩展在Proxy层自动解密和加密方便测试。或者先在前端代码中定位到发起请求的位置通过修改代码如果条件允许来打印出加密前的明文数据。6.2 漏洞测试中的边界情况问题3修改参数测试越权时服务器返回了“参数错误”或“系统繁忙”。原因可能触发了服务端的参数格式校验或简单的风控。排查对比正常请求仔细对比你修改的请求和原始请求除了目标参数其他所有Header、Cookie、Body格式是否完全一致特别是Content-Type。检查参数类型user_id是数字还是字符串尝试用字符串格式“10002”和数字格式10002分别测试。检查签名或Token有些接口会对所有参数生成一个签名sign来防篡改。如果你修改了user_id但没有更新签名请求就会被拒绝。你需要在前端代码中寻找生成签名的算法。放慢速度快速连续发送大量异常请求可能触发IP临时限制。适当降低测试频率。问题4找到了疑似Token生成规律但伪造的Token无效。原因Token的验证可能比想象中复杂。排查检查有效期JWT的Payload里通常有exp过期时间字段。确保你伪造的Token没有过期。检查签名确认服务器是否真的不校验签名。也许它校验了但你的伪造方式不对。尝试用已知的、有效的Token结构只替换uid部分看是否有效。检查绑定信息Token是否还与登录时的IP、设备ID等进行了绑定服务器可能在校验Token的同时还从数据库里查询了该Token最后一次使用的IP与当前请求IP不一致则拒绝。查看服务器日志如果条件允许在授权测试中让开发同学查看服务器验证Token时的详细日志看失败的具体原因是什么。6.3 授权与法律风险规避最重要的问题所有测试必须在合法授权的范围内进行获取书面授权在测试任何非自己公司的小程序或应用前必须获得所有者明确的、书面的渗透测试授权。未经授权的测试是违法的。使用测试账户永远使用自己创建的或测试方提供的测试账户不要触碰任何真实用户的数据。控制测试影响避免使用可能对服务器造成高负载的自动化扫描工具如Intruder的暴力遍历避免进行破坏性操作如修改、删除数据。清晰报告发现漏洞后编写清晰、专业的漏洞报告详细描述复现步骤、潜在危害和修复建议通过正规渠道提交给相关方。