# 常见风险及处理建议

微信小程序团队在常规安全巡检过程中,发现有部分小程序存在未授权或授权不严的接口安全风险。为高效识别和处理漏洞,避免开发者自身业务受到威胁和损失,本文盘点小程序开发过程中经常出现的安全风险,请开发者重视,在上线前后对自己的小程序开展自查自纠。

# 一、业务风险

# 1.1 接口鉴权

风险识别

接口鉴权是指后台接口(包括自建后台接口与云函数)在被调用时需要对本次接口调用进行权限校验,否则容易发生越权行为。

如商品删除接口,后台在收到请求时应当校验调用者的身份信息(如 openid、 ip 地址、开发者自定义的登录态信息等),只有指定用户才可以通过校验进行删除。

越权通常分为平行越权和垂直越权:

(1)平行越权

平行越权是指相同角色之间的越权,用户可以访问同一权限下的其他用户数据或操作界面。

A1、 A2 都是普通用户, A1 通过请求后台接口 userinfo.php?id=A1 来获取用户 A1 自己的信息,如果 userinfo.php 没有进行权限校验,用户 A1 把请求改为 userinfo.php?id=A2 便可以获取到 A2 用户的信息,造成 A2 用户信息的泄露。

(2)垂直越权

垂直越权是指不同角色之间的越权,指系统未能正确验证用户权限,用户能访问未授权的资源。

B1 是管理员, B2 是普通用户,管理员 B1 通过请求后台接口 getalluserinfo.php 可以获取所有注册用户的信息,如果 getalluserinfo.php 没有进行权限校验, B2 用户也可以请求 getalluserinfo.php 来获取所有注册用户的信息,出现越权行为。

(3)未授权

部分小程序因业务属于公开信息,所以未设置登录授权检查。导致该接口会被黑灰产利用,进行信息倒卖、信息监控等恶意行为。

用户可以不通过登录直接通过订单 ID、手机号、车牌等信息访问详细信息。

处理建议

无论业务形态上是否需要登录鉴权,都应设置鉴权校验,并记录详细的操作日志以备后续分析和恶意风险识别。

敏感数据、能力相关接口需要在后台进行鉴权。通常可校验 openid、 IP 地址、自定义登陆态等信息。

鉴权逻辑应放在后台进行,不应在小程序前端以隐藏页面、隐藏按钮等方式来代替。

鉴权代码示例(仅供参考)

// 自建后台鉴权
function actionDelete(){
    $item_id = $_POST["item_id"]; 
    $openid = $_POST["openid"];
    $ip = $_SERVER['REMOTE_ADDR'];
    $user_role = $_SESSION["user_role"];
    if ($openid === "xxx" &&
        $ip === "192.168.0.101" &&
        $user_role === "admin") {
            // 进行删除操作
            // ...
            return 0;
        } else {
            // 记录非法请求
            // ...
            return -1;
        }
}
// 云函数接口鉴权
exports.main = async (event, context) => {
    const { OPENID, APPID, UNIONID } = cloud.getWXContext();
    if (OPENID === "xxx") {
        // 进行删除操作
        // ...
    } else {
        // 记录非法请求
        // ...
    }
}

# 1.2 业务信息泄露

风险识别

敏感信息是指一旦泄露可能会对开发者的业务、合作伙伴和用户带来利益损害的数据,包括但不限于账号 AppSecret、特权账号信息、后台加密密钥、登录账户密码、用户身份证号、手机号、银行卡号等

处理建议

敏感信息不应以明文、注释、可逆的编码方式(如 base64)、不安全散列函数(如 MD5、 SHA1)等形式出现在小程序文件内。

部分敏感信息如用户的银行卡号、手机号等需要用于展示的,需要进行脱敏处理。常用脱敏规范如下:

敏感信息类型 展示样例
姓名 名字只有两个字,对第一个字打码,如:*三。 多于两个字,只保留第一个和最后一个,其余都打码,如:王*四、欧**五
身份证 只显示第一位和最后一位,如:3****************1
手机号 除去手机国际码后,手机号位数不少于10位时,只显示前三位和最后两位,如:156******77。手机号位数少于10位时,只显示前两位和后两位,如:12*****89。国家码可以完全显示。
银行卡 只显示最后4位,如:************1234

如果小程序存在敏感信息泄露的问题,微信开放平台将有可能下架该小程序,并暂停该小程序的相关服务。

# 1.3 授权用户信息变更

风险识别

开发者通过微信小程序 API,经过用户明确同意获取了用户资料,当遇到用户资料过期、用户主动撤销、用户注销账号等情况时,开发者需对先前获取的用户资料及时清理,依规及时履行相应个人信息保护义务,保护用户权益。

处理建议

开发者需要设置「消息推送服务器配置」,及时接收微信平台关于授权信息的通知并处理,详情请查看此文档

# 1.4 爬虫遍历

风险识别

如果信息检索接口使用数字自增 ID(或有特定规律),且接口不做人机和登录鉴权校验,很容易造成数据信息被拖库。

处理建议

ID 信息不应该使用简单的递增,如必须递增则后缀应添加随机码。

将数字型的 ID 做加密处理,服务端只接收加密处理的 ID 查询请求。

# 1.5 弱口令

风险识别

弱口令指管理后台的用户名密码设置得较为简单或者使用默认账号。攻击者可以通过登录这些账号修改后台数据或进行下一步的入侵操作。

处理建议

后台服务禁用默认账号名(如 root、admin、administer、master 等),修改后台弱口令。

敏感服务增加二次验证机制,如短信验证码、邮箱验证码等。

# 二、开发风险

# 2.1 代码管理与泄漏

风险识别

当使用 git、 svn 等版本管理工具时,会产生 .git 等目录。某些编辑器或软件也会在运行过程中生成临时文件。若这些目录或文件被带到生产环境,则可能发生源码泄漏。

使用小程序代码管理平台或 github 等第三方平台时需要注意项目权限,不要公开敏感、内部项目。

处理建议
  1. 备份文件和版本管理工具产生的文件不要同步到 Web 目录下。
  2. 禁止外部访问 .git 等目录与文件。
  3. 小程序代码管理平台等管理平台内配置适当的访问权限。

# 2.2 SQL 注入漏洞

风险识别

SQL 注入是指 Web 程序代码中对于用户提交的参数未做有效过滤就直接拼接到 SQL 语句中执行,导致参数中的特殊字符打破了 SQL 语句原有逻辑,黑客可以利用该漏洞执行任意 SQL 语句。

处理建议

使用数据库提供的参数化查询来进行数据库操作,不允许直接通过拼接字符串的方式来合成 SQL 语句。

如果存在部分情况需要通过拼接的方式来合成 SQL ,拼接的变量必须要经过处理:「对于整数,需要判断变量是否为整数类型;对于字符串,需要对单引号、双引号等做转义处理」

避免 Web 应用显示 SQL 的报错信息。

保证 Web 应用里每一数据层的编码统一。

# 2.3 命令注入漏洞

风险识别

命令注入漏洞是指 Web 应用未对用户可控参数进行有效过滤,攻击者可以构造恶意参数拼接到命令上来执行任意命令。

处理建议

对用户输入的数据(如 ;、|、&等)进行过滤或转义。

不要信任前端(WEB、小程序)发送请求的任何内容,所有参数都进行类型验证,避免直接使用前端传递的命令去执行。

# 2.4 文件上传漏洞

风险识别

文件上传漏洞是指 Web 应用允许用户上传指定文件,但未对文件类型、格式等做合法性校验,导致可以上传非预期格式的文件。

处理建议

正确解析上传文件的文件类型,通过白名单的方式限制可上传的文件类型。

# 2.5 文件下载

风险识别

文件下载漏洞是指 Web 应用允许用户通过指定路径和文件名的方式来下载对应的文件,但未正确限制可下载文件所在的目录范围,导致预期范围外的文件被下载泄露。

处理建议

正确限制可下载文件所在的目录范围

通过指定文件 id 的方式来查找下载对应的文件

# 2.6 目录遍历

风险识别

目录遍历是指由后台服务对用户输入验证不足或配置不严谨导致的服务器目录内容泄漏。外部可能通过目录遍历获取系统文件、后台代码等敏感文件。

处理建议

web 服务配置

  • 服务端禁止展示目录
  • 设置目录访问权限
  • 在每个目录下放置一个空的 index.html 页面

web 应用代码

  • 严格检查文件路径参数,限定文件的范围

# 2.7 条件竞争

风险识别

条件竞争比较常见的例子是攻击者通过并发 https 请求而达到多次获奖、多次收获、多次获赠等非正常逻辑所能触发的效果。漏洞代码示例

// 从DB里查询该用户剩余获奖次数,初始值为1
int remain_times = SelectRemainTimes();

if(remain_times > 0){
    EarnRewards();          // 用户获得奖励
    ClearRemainTimes();     // 在DB里把该用户的剩余获奖次数清零
}

开发者的设计本意是只允许用户获得一次奖励,但当出现并发请求时,有可能出现请求 A 和请求 B 都刚好执行完第2行代码的情况,此时两个请求的 remain_times 都为1,也就是可以通过第4行代码的判断,获得两次奖励。

处理建议

对关键(完整)逻辑加锁操作或把关键逻辑以队列任务的形式去进行处理。

# 三、总结

开发者在开发环节中必须始终基于以下原则:

  1. 互不信任原则,不要信任用户提交的数据,包括第三方系统提供的数据,必要的数据校验必须放在后台校验。
  2. 最小权限原则,代码、模块等只拥有可以完成任务的最小权限,不赋予不必要的权限。
  3. 禁止明文保存用户敏感数据。
  4. 小程序代码(不包括云函数代码)跟传统 Web 应用的前端代码类似,可被外部获取及进行反混淆,重要业务逻辑应放在后台代码或云函数中进行。
  5. 后台接口调用以及云函数调用,必须进行有效的身份鉴权。