小程序安全沙箱技术,将恶意代码装进“笼子”里,微信小程序沙箱

4747 63 2022-12-11

本文讲述了小程序安全沙箱技术,将恶意代码装进“笼子”里,微信小程序沙箱。

我们的工作和生活已经离不开各式各样的软件。

趋势所向。今年初白宫都不得不召集会议讨论Log4J,拜登都得知道Java,是不是莫名魔幻?但对于码农来说是不是略感自豪 - 相当于所在银行行长、公司CEO深入IT基层,亲自了解一个用Java写的、通常埋在十八层代码之下的日志工具,“并作出重要指示”的既视感...

那么,软件盛行的年代,企业中高管最怕的是什么?

“某天,某银行被盗取大量数据、遭受巨大经济损失,并遭到消费者集体诉讼和监管天量罚单。原因是技术系统用了某个开源代码包,该开源代码包原来是一个遭黑客污染过植入了后门的有毒组件。不小心误用这个代码包的,是IT部门某基层小弟所为。新闻爆出,CIO一脸蒙圈,卒。”

上述例子的罪魁祸首,叫软件供应链攻击。这个问题的潜在风险,大到什么程度呢?根据Gartner,至2025年全球有接近一半的企业会会遭遇到,感兴趣的同学可以自行脑补...

“Hello,World”后面可能有几万行代码

开源软件运动如火如荼的进行了二十四五年(如果从1998年2月3日在硅谷的一次会议中首次提出“open source”一说开始算 - 当时互联网先驱Netscape刚刚宣布开放他们的浏览器源码),极大程度的改变了软件业的面貌。当前全球企业超过90%直接或者间接甚至在无意识中使用了开源技术。

时间快进到2022年,很多企业的业务软件里可能只有低至10%的代码是自己的工程师写的,其他的都来源于不知名的开源世界,开发者自己都不知道,供应链被污染了,影响到自己,也殃及其他“租户”。“物业”呢,则不排除内部人员有道德风险,做倒卖“租户”资产(例如数据)的事情。

你想用JavaScript+Node.js开发一个只能对网络请求返回“Hello,World”回复的微服务,你决定采用一个最轻量简约的微服务框架ExpressJS - 一动手的瞬间,你的开发工具npm就给你从上游拉取100+软件包 - 54,000行代码拿去,不谢。如果你想再玩点高级功能,例如添加一个MVC框架(例如Locomotive),你的这个“微”服务实际代码量马上升至220,000行 - 不好意思,起步价,哪怕你只写一行代码。

软件供应链的四大风险

对于企业来说,当前软件供应链起码面临四类风险。

软件质量风险。企业软件表面上由IT或者外包商开发,可是实质上背后是成千上万的第三方开源代码,企业的QA工程质量管理方法和流程,对于第三方完全失控无效。

长期支持风险。企业软件所间接依赖的一些第三方开源零部件,并没有商业体在背后提供质量承诺和长期支持。开源项目因创始人退出或者社区活跃度低而不再维护、半途而废的,不在小数。产生维护支持需求时,企业自己不得不安排人手去处理该部分代码,先不说有没有这个意愿,企业自己的IT工程师是否有这个能力也难说。

知识产权风险。开源软件的知识产权机制,反映在著佐权(Copyleft)和许可证(Permissive)。后者约束了你的软件的分发传播需要满足的条件,前者则往往更进一步要求你用开源组件开发的软件本身的源代码必须沿用同样的开源条款,导致你的软件知识产权不得不公开。国内软件企业在使用开源、贡献开源的过程中规则意识普遍薄弱,存在错误混用不兼容的许可证,违反许可证规定二次发布等问题,带来更为复杂的知识产权问题和法律合规风险。

信息安全风险。在开发人员写第一行代码前,一个系统可能就注定继承了一堆“安全债务” - 部分取决于这个系统的设计者、开发者选择采用什么第三方组件,部分取决于这些第三方组件的开发者又选择依赖于什么别的组件。反正安全风险是传递的,只要有一个零部件有安全漏洞、甚至是在漫长复杂的互联网分发链路上被篡改过注入了恶意代码,你的系统就继承了所有这些风险。

在信息化程度比较高的金融业,软件作为金融信息基础设施的重要组成部分,安全问题将直接影响金融信息系统的安全稳定运行。

小程序安全沙箱类技术的盛行

首先说说小程序生态。在BAT等巨头的带动下,市场上已经有11大小程序平台,700W+的小程序应用,覆盖200+个细分垂直领域,可见,小程序生态在国内已经具备相当影响力的规模。正因为如此迅猛的发展,互联网系列全球标准的制定者W3C,也正在通过其Mini-Apps工作组制定小程序技术的国际标准。

然后说说App插件生态。作为Web 2.0的标志性技术产物,历经互联网蓬勃发展的市场需求的迭代,衍生出许多标准化的、能够降低App(甚至扩展至移动设备)开发的插件式SDK:极光推送、声网音视频、第三方登录、第三方支付.....

再说说小程序安全沙箱技术。如果将小程序和移动设备插件比喻成“点”,那么小程序安全沙箱技术(例如:FinClip)就是能够让一个个点组装成App的“线”。FinClip小程序容器技术的价值点之一在于「连接」:只要把FinClip SDK嵌入到自己的App中,马上获得小程序运行能力,而只有获得小程序运行能力,才能在App中充分引入成熟的小程序应用。

此外,在软件供应链安全防护上,小程序容器技术天然的安全隔离能力,通过构建一个封闭的软件环境,隔离了它所在的“宿主”的资源包括内存、文件系统、网络等等的访问权限。运行在这个封闭环境中的进程,其代码不受信任,进程不能因为其自身的稳定性导致沙箱的崩溃从而影响宿主系统,进程也无法突破沙箱的安全管控以读写宿主系统的资源。

俗话说,天时-地利-人和,现代社会快节奏市场强压下,如果还是老式的思想,企业重复造车轮,那么铁定是跟不上用户快速变化的需求。只有充分利用各领域构建起来的成熟生态,以互联互通、合作共赢的方式方能发挥企业1+1>2的效应。

小程序容器技术便是一个非常好的「技术催化剂」,将小程序应用生态、移动设备插件生态、移动设备有机的“粘合”在一起,且Plus一个安全沙箱的机制,对软件供应链安全的端侧进行安全隔离和防护。感兴趣的可以登陆FinClip官网了解一下。

背景和需求

随着微盟业务的拓展,我们在产品形态上允许商户使用不同的业务模块动态生成一种解决方案。这时我们就需要去解决不同的业务模块开发团队所面临的协作问题。
一般情况下,我们在多个团队协作开发小程序过程中,如果某个团队的模块对全局的JS变量进行了修改,比如:wx、Page、App等,都会影响到其它模块,甚至出现一些莫名其妙的bug。所以为避免某个模块对全局的修改影响到其它模块,我们希望在使用其它团队的模块时,对该模块进行一个沙箱化(Sandbox)处理,让其对全局的修改只会在该模块下有效。


解决思路

通常的一些解决方案比如在H5中使用 with+Function、借助iframe,在nodejs中使用 vm/vm2 等方式去实现代码环境隔离,但是在小程序中这些方式都不可用。
故我们通过在编译时,在代码中插入沙箱代码实现环境隔离,以下是实现的2个思路:

方式一: 在代码最外层套一层外壳, 将代码包含在其中.

比如原始代码为:

Page({
  onLoad() {
    wx.request({ /* 请求参数 */ })
  }
});

转换后代码:

// 注入局部环境变量((wx, Page, /* 其它全局变量 */) => { /* 注入的代码 */Page({
  onLoad() {
    wx.request({ /* 请求参数 */ })
  }
});
})(...global.getModuleEnv('moduleA')) /* 注入的代码 */

说明: getModuleEnv 是提前挂在 global 上的一个方法, 用于获取每个模块的的环境变量, 返回值是一个数组, 数组元素顺序与注入局部环境变量顺序一致, 伪代码如下:

const envs = {};
global.getModuleEnv = (moduleName) => {  if (!envs[moduleName]) {
    envs[moduleName] = [
      createScopedWx(wx),
      createScopedPage(Page),      // 其它环境变量
    ];
  }  return envs[moduleName];
}

优点: 实现简单, 只需对模块内的每个js文件都包装一下这个外壳就好.
缺点:

  1. 如果模块内某块代码对全局变量进行赋值操作, 如下:

// init.jsPage = proxified(Page, (_Page, that, args) => {  /* 对 pageOpts 进行修改, 注入业务逻辑等  */
  return _Page.apply(that, args);
});// user-page.jsPage({ // 这里的 Page 应该是被修改过的
  /* 代码逻辑 */})

方式一这种用法将无效, init.js无法影响到同模块内其他JS文件.

  1. 如果某个js代码中写了如下代码:

const wx = {}; // 虽然没有人会这么写, 但是正常情况下是不会报错的

经过一层代码包裹之后, 运行时将会报Duplicate declaration "wx"的错误

方式二: 使用 Babel 对代码中的全局变量进行替换, 实现思路如下:

说明:

1. 判断是否需要被局部化

使用 path.node.name 判断是否需要被替换

2. 判断是否被引用的标识符

使用 path.isReferencedIdentifier()

3. 判断是否是赋值操作

使用 t.isAssignmentExpression(path.parent)

举例:

// 原代码Page = function() {  // 代码}// 替换结果为_$_.Page = function() {  // 代码}

4. 判断是否是赋默认值操作

使用 t.isAssignmentPattern(path.parent)

举例:

// 原代码function fn(origWx = wx) {  // 代码}// 替换结果为function fn(origWx = _$_.wx) {  // 代码}

5. 判断是否是数组结构赋值

使用 t.isArrayPattern(path.parent) && !t.isVariableDeclarator(path.parent.parent)

举例:

// 原代码[Page, Component] = [
  newPage,
  newComponent,
];// 替换结果为[_$_.Page, _$_.Component] = [
  newPage,
  newComponent,
];

6. 没有定义过该名称

使用 path.scope.getBinding(name) == null

举例:

function fn(wx) {  // 这里的 wx 不应该被替换
  wx.showToast({})
}

Program节点的exit阶段中判断是否进行过上面的代码替换操作,如果是则在顶部插入一句

const _$_ = global.getModuleEnv('moduleA');

最终转换效果如下:
原代码为:

Page({
  onLoad() {
    wx.request({ /* 请求参数 */ })
  }
});

转换后代码为:

const _$_ = global.getModuleEnv('moduleA');
_$_.Page({
  onLoad() {
    _$_.wx.request({ /* 请求参数 */ })
  }
});

说明:getModuleEnv 是提前挂在 global 上的一个方法, 用于获取每个模块的的环境变量, 返回值是一个对象, 对象中包含所有需要进行隔离的变量, 伪代码如下:

const envs = {};
global.getModuleEnv = (moduleName) => {  if (!envs[moduleName]) {
    envs[moduleName] = {      wx: createScopedWx(moduleName, wx),      Page: createScopedPage(moduleName, Page),      // 其它环境变量
    };
  }  return envs[moduleName];
}

优点:模块内的任何修改都仅作用于该模块内,并且没有方式一的缺点,实现了我们想要的效果,并且可以自定义注入全局API,不用挂到globalwx
缺点:实现较为复杂,需要接入Babel。

其它

对于每个模块的本地存储也需要进行隔离,可以在方式二的基础上,在createScopedWx中对Storage相关API进行包装,给key加上模块名称,这样每个模块的本地存储也不会被相关影响了。
同时一些全局的公共key需要加白名单功能(比如:存用户信息的key),避免模块内读取不到公共的本地存储数据

后记

使用Babel这种方式实现了我们想要的效果,并且在此过程中对Babel以及AST有了一个全新的认识。最终我们在项目中使用的是方式二这种模式。

上文就是小编为大家整理的小程序安全沙箱技术,将恶意代码装进“笼子”里,微信小程序沙箱。

国内(北京、上海、广州、深圳、成都、重庆、杭州、西安、武汉、苏州、郑州、南京、天津、长沙、东莞、宁波、佛山、合肥、青岛)Finclip软件分析、比较及推荐。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。