H5 的隐患

要知道,Web 技术是非常开放灵活的,开发者可以利用 JavaScript 脚本随意地操作 DOM,这是会带来以下的问题:
随意地跳转网页,改变界面上的任意内容
开发者可以利用 JavaScript 脚本随意地跳转网页,或是改变界面上的任意内容。当然,恶意攻击者也能利用这种便利。
获取页面数据
小程序也提供可一种可以展示敏感数据的组件,<open-data>能展示包括用户昵称、头像、性别、地理位置等信息(无需用户授权)。
如果开发者可以操作 DOM,意味着他们可以随意拿到用户的敏感信息。
常见的前端漏洞
开发者们普遍重视的安全漏洞,在前端常见的有 XSS 和 CSRF,XSS 是通过注入 JavaScript 脚本的方式来达到特定目的,而 CSRF 则是利用了 cookie。
XSS 在双线程的设计中就被过滤了,而 CSRF 会在后面讲到。

难以实现的管控

为了解决管控与安全问题,小程序需要禁用掉:

  • 危险的 HTML 标签或者相关属性,如外跳 url 的 a 标签
  • 危险的 API,如操作界面的 API、动态运行脚本的 API

如果要一个一个禁止,JavaScript 的灵活性以及浏览器接口的丰富性,会导致很容易遗漏一些危险的接口。并且浏览器内核在不断更新,或许下一版本会新增一个可能会在这套体系下产生漏洞的接口,无法完全避免。

安全的逻辑层

要怎么彻底解决这些问题呢?给大家点提示:
【20200212】小程序安全管控 - 图1
没错,就是沙箱环境。通过提供一个纯 JavaScript 的解释执行环境,这个环境没有浏览器相关接口,当然也不用担心操作 DOM、跳转等问题了。在 iOS 下是用内置的 JavaScriptCore 框架,在安卓下是 JsCore 环境(旧版是腾讯 x5 内核提供,新版是 v8 提供)。
一起来回顾下小程序的双线程长什么样子:
【20200212】小程序安全管控 - 图2
客户端系统有 JavaScript 的解释引擎,则可以创建一个单独的线程去执行 JavaScript,这个环境下只执行有关小程序业务逻辑的代码。界面渲染相关的任务呢,就丢到 webview 线程里面,通过逻辑层代码去控制渲染哪些界面。
把开发者的 JS 逻辑代码放到单独的线程去运行,因为不在 Webview 线程里,所以这个环境没有 Webview 任何接口,自然的开发者就没法直接操作 DOM,也就没法动态去更改界面或者抓取页面数据。
同时小程序不支持动态载入脚本,XSS 漏洞自然也无缝可钻。

审核机制的管控

审核机制,故事要从公众号讲起了。

WebView的飞速发展

当年随着公众号的出现和繁荣,WebView 的使用频率也越来越高。不少的企业或是小商家、外包公司开始做 H5 页面,各式各样的 H5 活动页、小商城、小测试、小游戏满天飞。
【20200212】小程序安全管控 - 图3
当微信中的 WebView 逐渐成为移动 Web 的一个重要入口时,微信就有相关的 JS API 了。
2015年初,微信发布了一整套网页开发工具包,开放了拍摄、录音、语音识别、二维码、地图、支付、分享、卡券等几十个API,称之为 JS-SDK。
到这个时候,web开发者可以使用到微信的原生能力,去完成一些之前做不到或者很难做到的事情。

难管控的 JSSDK

由于使用 WebView 和 JSSDK 的人越来越多,微信上越来越多干坏事的人,有人做假红包,有人诱导分享,有伪造一些官方活动,他们会利用 JSSDK 的分享能力变相的去裂变分享到各个群或者朋友圈。
【20200212】小程序安全管控 - 图4
由于 JSSDK 是根据域名来赋予 api 权限的,运营人员封了一个域名后,他们立马用别的域名又继续做坏,注册一个新的域名的成本是很低的。

小程序的审核机制

为了保证小程序的质量,以及符合相关的规范,小程序的发布是需要经过审核的。经过审核的小程序才能对外发布,同时在出现问题时,小程序会被下架停用。
另外,每个微信小程序需要事先设置一个通讯域名,小程序只可以跟指定的域名与进行网络通信,包括普通 HTTPS 请求、上传文件、下载文件和 WebSocket 通信,参考框架-网络。这些通讯域名,也都必须要求通过备案。
同时,小程序必须使用 HTTPS 发起网络请求。请求时系统会对服务器域名使用的 HTTPS 证书进行校验,如果校验失败,则请求不能成功发起。
这些种种的限制和管理模式,都进一步保障了用户的数据和隐私安全。

安全的登录机制

想必在座的各位前端开发者,都清楚 CSRF 安全漏洞。

危险的 cookie

跨站请求攻击(CSRF),简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。
这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。通常的罪魁祸首则是浏览器的 cookie 登录态。
除了检查 Referer 字段来防范,更有效的一种方式是使用 token。小程序也是这么做的。

小程序登录

小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系。参考官方时序图:
【20200212】小程序安全管控 - 图5
在小程序中调用wx.login(),能拿到一个code作为用户登录凭证(有效期五分钟)。在开发者服务器后台,开发者可使用code换取openidsession_key等信息(code只能使用一次)。

可靠的 code

假设现在有个接口,请求 https://test.com/getUserInfo?id=1 拉取到微信用户 id 为 1 在我们业务侧的个人信息,那么黑客就可以通过遍历所有的 id,把整个业务侧的个人信息数据全部拉走,会给业务带来很大的安全风险。
由于code 5 分钟后会过期,如果黑客要冒充一个用户的话,那他就必须在 5 分钟内穷举所有的身份证 id,然后去开发者服务器换取真实的用户身份。而code在成功换取一次信息之后也会立即失效,即便凭证code生成时间还没过期。显然,黑客要付出非常大的成本才能获取到一个用户信息,同时,开发者服务器也可以通过一些技术手段检测到5分钟内频繁从某个 ip 发送过来的登录请求,从而拒绝掉这些请求。

需要保护的 AppSecret

开发者的后台就拿到了前边wx.login()所生成的微信登录凭证code,此时就可以拿这个code到微信服务器换取微信用户身份。微信服务器为了确保拿code过来换取身份信息的人就是刚刚对应的小程序开发者,到微信服务器的请求要同时带上AppIdAppSecret
AppIdAppSecret是微信鉴别开发者身份的重要信息,AppId是公开信息,泄露AppId不会带来安全风险,但是AppSecret是开发者的隐私数据不应该泄露,开发者需要好好保护。