Lisk沙箱漏洞分析及解决方案

背景javascript

前一阵子比特股的创始人Daniel Larimer质疑了lisk系统中的一系列问题,绝大多数都被lisk的创始人之一Max正面回应过了,具体能够看看这个http://ethereum.stackexchange...
可是有一个问题Max没有回应或者说尚未提出解决方案。
那就是lisk侧链的运行环境,Larimer说
“Lisk所面临的大多数问题,均可以经过一个高度定制的JavaScript 环境来解决。”
不然,lisk的系统面临两大考验:
首先,也是最重要的,lisk目前的沙箱机制不足以限制侧链代码的权限,也就是说没法运行不受信任的代码,那些代码可能会盗取服务器的关键信息,或者直接对服务器进行破坏
其次,是关于侧链开发者的,lisk的侧链代码是运行在一个拥有所有能力的javascript环境,这里面有些能够致使不肯定因素的函数,好比Math.random,lisk官方的开发文档特别指出但愿开发者避开这些函数,这对开发者会形成心智负担,若是是一个定制版的javascript环境呢,直接把那些致使不肯定因素的函数干掉,开发者根本不须要花费额外的精力去避开那些陷阱。前端

lisk的沙箱安全问题

咱们先说说为何要用沙箱。沙箱(sandbox)是云计算平台普遍采用的安全防范措施,是一种访问控制机制,其目的是为了限制应用代码的权限,防止应用代码对系统进行随意的访问和破坏,由于在云计算平台中,应用代码是由各类各样的第三方开发者实现的,他们的代码是不能被信任的,须要沙箱来作一层隔离,让这些应用代码只能作有限的事情。
lisk在区块链领域是很相似于云计算平台的,他们提供一些服务,容许第三方开发者基于这些服务构建他们本身的应用程序(即dapps)。
因此lisk也须要沙箱机制来保证dapps的做者没法做恶,lisk的作法是提供一个定制版的nodejs环境来达到这一目的。
具体实如今这段代码里,https://github.com/LiskHQ/lis...
我通过分析,发现这个sandbox名存实亡,只是使用管道手段实现了进程间通信,并且是使用一种绕弯的方式实现的。nodejs的进程间通信能够经过javascript代码直接实现,为何要用c++来实现呢?
我带着这个疑问,和一些指望,亲手作了一个实验。
我首先使用lisk-cli建立一个hello world侧链java

lisk-cli dapp -a

接着我在侧链程序的入口处 加了一段代码node

console.log(require("fs").readFileSync("../../config.json"))

而后运行之,因而我获得了这个服务器主节点的受托人的全部密码linux

"forging": {
  "secret": [
    "wKyoJM1vS4ucHmWvxDSdcpC23mJwqfg3G6MKZoXaFfcnWHTqo7",
    "2aTWYPpQidVunxTg3y8YESYps7za6f9d4wYn9Gy2GuGnE7JX7V",
    "65uZNjL36Bdg2tkJnueYkd2n6YPe76fpdeYtgu7fso1m385mwD",
      …………

果真,这个沙箱并无起隔离的做用,拥有管理员的权限,这等于给黑客敞开了大门。
有人说,lisk系统设置了二级密码,你获取了他的一级密码,仍是无法盗取他们的钱。
也有人说,能够经过Linux自身的权限机制,给侧链代码一个低级用户,使之没法访问其余用户的文件。
这些都是治标不治本的办法,根本解决途径是按照lisk预先设想的那样,要让沙箱名副其实,作到真正的环境隔离,要让侧链代码对外界一无所知。
解决方案webpack

那么,如何实现真正的沙箱呢?有不少种方案,好比跳过nodejs,直接使用v8引擎,或者使用进程级别的权限控制,好比windows系统的SetWindowsHookEx,固然,lisk目前并不打算支持windows版本的完整版钱包,那么在linux系统中可使用seccomp技术。
我这里有种更简单的方法,那就是利用nodejs自带的vm模块。
第一步 建立原生的javascript虚拟机c++

var vm = require('vm');
var context = vm.createContext();
vm.runInContext(sideChainCode, context);

这几行代码完成了对侧链代码的隔离,sideChainCode中只能进行纯粹的运算逻辑,只能使用少许的v8引擎内置的javascript标准库。甚至连setTimeout,console.log都没有。
咱们须要作些额外的工做。
好比,给运行环境添加setTimeout和clearTimeoutgit

context.setTimeout= function(fn, delay) {
    if (typeof(fn) == 'string') {
      setTimeout(new Function(fn), delay)
    } else {
      setTimeout(fn, delay)
    }
};
context.clearTimeout = clearTimeout;

添加日志打印功能,让侧链的日志,转发到主系统的中github

global.print = send.bind(global, 'stdout');
global.console = { log: send.bind(global, 'stdout') };
global.process = {
    stdout: { write: send.bind(global, 'stdout') }
};
global.postMessage = send.bind(global, 'message');

另外,还能够禁用那些致使不肯定因素的函数web

global.Math.random = undefined;

这些都作完之后,侧链代码的访问权限就被限制在一个狭小的范围内了。它们没法使用require,fs,http等nodejs内置的标准库。
这样安全的目的达到了,可是引起另外一个问题,那就是功能性的问题了,不能使用那些额外的库,只有js的标准库也太不方便了,不少复杂功能没法实现,特别是没有了require以后,连模块化都作不到了。
因此咱们须要第二步。
第二步 webpack

webpack原本是一个前端经常使用的打包方案,用于模块化的管理前端项目。不少人忽略了其实在后端webpack一样适用,而且能够把node_modules里的库,甚至nodejs的一部份内置库一块儿打包。
也就是说前端能用的js库,除了UI相关的以外,在侧链沙箱内,也均可以用,好比async,bytebuffer,crypto,js-nacl,bignum等等,这对于侧链来讲够用了。
那些不能用的库,好比文件系统、多进程、网络模块,正是咱们想要抛弃的。
vm + webpack的组合堪称完美。
这是突飞猛进的前端技术带给javascript这门语言的福利,也是以太坊的solidity等新语言可望不可即的。
不过咱们还须要一些收尾的工做
第三步 扫清障碍

目前侧链代码中有些地方使用了一些比较复杂的库,好比ed2curve,涉及到很是多的依赖,咱们认为是没有必要的,这部分功能能够在主链中提供,经过进程间通信以api的方式提供给侧链使用。
这样也能够减轻侧链代码的累赘,还可让侧链开发者更加轻松。这些代码对整个框架的影响很是小,能够忽略不计,可是它们依赖的库却占用了一半以上的代码量,其中还包括了沙箱环境不容许的一些操做。
通过分析,我发现只须要禁用modules/api/crypto.js中的两个函数便可

Crypto.prototype.encrypt
Crypto.prototype.decrypt

另外,js-nacl这个库里依赖了fs模块,可是相关函数并无被用到,暂时经过手工修改的方式,把fs相关代码去掉就能够正常打包并运行了。
最后,我把一个完整的侧链项目和主链框架中的关键代码打包放在这里了。http://o7dyh3w0x.bkt.clouddn....

关于侧链和区块链开发的问题,欢迎加群:485979564,一块儿讨论交流

相关文章
相关标签/搜索