第一章:小程序安全概述

1.1 小程序安全威胁分析

说实话,小程序刚出来那会儿,很多人都觉得它比原生App安全。为什么?因为代码跑在微信的沙箱里嘛。但我得告诉你,这种想法其实挺危险的。

我在2018年接手过一个电商小程序的安全审计。当时团队觉得「反正有微信兜底」,结果呢?用户数据被中间人攻击窃取了。嗯,从那以后,我再也不敢对小程序安全掉以轻心。

小程序的威胁主要来自三个层面:

  • 客户端威胁:代码被反编译、本地存储被读取、界面被劫持
  • 通信层威胁:HTTPS证书校验不严、数据包被篡改、中间人攻击
  • 服务端威胁:API接口被刷、业务逻辑漏洞、权限绕过

你想想看,小程序虽然运行在微信里,但它的代码本质上还是JavaScript。只要用户能拿到包,就能用工具反编译。我见过有人直接把整个小程序的源码扒下来,然后改个支付金额重新打包——这种事真不是危言耸听。

核心观点:小程序的安全边界,不能完全依赖微信平台。开发者必须自己扛起安全责任。

1.2 常见攻击类型

这些年我踩过的坑,基本可以归纳成以下几类攻击。每一条都是血泪教训。

1.2.1 代码反编译与逆向

小程序的.wxapkg文件,说白了就是个zip包。用工具解压后,所有逻辑代码一览无余。我曾经帮一个金融类小程序做安全加固,发现他们的支付密钥直接硬编码在代码里——这等于把保险柜钥匙贴在门上。

// 错误示范:硬编码密钥
const API_SECRET = 'sk_live_xxxxxxxxxxxxx';

// 正确做法:通过后端接口动态获取
wx.request({
  url: 'https://api.example.com/get-token',
  success: (res) => {
    const token = res.data.token;
    // 使用token进行后续操作
  }
});

警告:永远不要在客户端代码中存储任何敏感密钥。微信小程序的代码包是可以被反编译的,这不是假设,而是事实。

1.2.2 中间人攻击(MITM)

我记得有一次,一个客户说他们的用户数据莫名其妙被泄露了。排查后发现,问题出在HTTPS证书校验上。他们用了自签名证书,但没有做pinning。攻击者只要在同一个WiFi下,就能轻松劫持所有通信。

为什么会这样?因为默认的HTTPS请求只验证证书链,不验证证书是否是你信任的那个。攻击者可以伪造一个证书,只要系统里有对应的CA根证书,请求就能通过。

1.2.3 本地存储泄露

wx.setStorageSync 这个API,用起来确实方便。但很多人不知道,这些数据是明文存储在手机上的。我曾经在项目中看到有人把用户的身份证号、银行卡号直接存进去——这简直是在给攻击者送数据。

存储方式 安全性 建议用途
wx.setStorageSync 低(明文存储) 仅存储非敏感数据(如主题色、语言偏好)
加密后存储 存储需要离线使用的敏感数据
服务端存储 所有用户隐私数据

1.2.4 接口滥用与刷单

这个坑我踩得最深。有一次上线了一个秒杀活动,结果被脚本刷了三千单。为什么?因为接口没有做签名校验和频率限制。攻击者直接拿着小程序的请求参数,用Python写了个脚本疯狂调用。

避坑指南:我曾经在接口设计中加入「时间戳+随机数+签名」的机制。每次请求都生成一个唯一的nonce,服务端校验签名和nonce是否已使用。这样就算攻击者拿到了请求包,也无法重放。

1.3 安全防护体系架构

讲完了威胁,咱们聊聊怎么防。我个人习惯把安全体系分成三层,就像盖房子一样——地基、墙体、屋顶,缺一不可。

1.3.1 客户端安全层

  • 代码混淆:使用微信官方提供的代码混淆工具,增加反编译难度
  • 反调试检测:检测是否开启了开发者工具或Fiddler代理
  • 本地数据加密:使用AES-256对存储数据进行加密
  • 界面防劫持:对关键页面进行防截屏、防录屏处理

这里有个细节:代码混淆不是万能的。它只能让攻击者多花点时间,不能完全阻止逆向。所以,核心逻辑一定要放在服务端。

1.3.2 通信安全层

  • 强制HTTPS:所有请求必须走HTTPS,且做证书pinning
  • 请求签名:每个请求都带上签名,服务端校验
  • 防重放攻击:引入时间戳和nonce机制
  • 敏感数据加密:对请求体和响应体进行二次加密
// 请求签名示例
function generateSignature(params, secret) {
  const sortedKeys = Object.keys(params).sort();
  let signStr = '';
  for (let key of sortedKeys) {
    signStr += `${key}=${params[key]}&`;
  }
  signStr += `key=${secret}`;
  return md5(signStr);
}

// 每次请求带上时间戳和nonce
const requestData = {
  timestamp: Date.now(),
  nonce: Math.random().toString(36).substr(2, 16),
  data: encryptedPayload,
  sign: generateSignature({timestamp, nonce, data}, API_SECRET)
};

1.3.3 服务端安全层

  • 身份认证:使用微信登录态+自定义token双重验证
  • 权限校验:每个接口都校验用户是否有操作权限
  • 频率限制:对API进行限流,防止刷接口
  • 数据校验:所有输入数据都要做白名单校验
  • 日志审计:记录所有敏感操作的日志,便于事后追溯

我的经验:安全防护不是一次性工作。我建议每个版本迭代时,都做一次安全评审。哪怕只是改了一个按钮,也要想想会不会引入新的安全风险。说白了,安全是一种习惯,不是一种功能。

最后说一句:小程序安全没有银弹。你不可能靠一个工具、一个框架就解决所有问题。真正有效的,是建立「安全第一」的开发意识。嗯,这一章的内容就到这里。下一章我们会深入讲解代码混淆与反调试的具体实现。