Stubbifier: 去除动态服务端 JavaScript 应用的膨胀代码
2023-4-21 21:12:47 Author: 安全学术圈(查看原文) 阅读量:12 收藏

原文标题:Stubbifier: debloating dynamic server-side JavaScript applications
原文作者:Alexi Turcotte, Ellen Arteca, Ashish Mishra, Saba Alimadadi, Frank Tip
原文链接:https://link.springer.com/article/10.1007/s10664-022-10195-6
发表会议:ESE'22
笔记作者:[email protected]
笔记小编:[email protected]

1 研究介绍

JavaScript 毋庸置疑地成为了当下前后端常用的开发语言之一,而其之所以能够成为后端开发语言,在一定程度上要归功于 NodeJS 的模块生态系统。NPM 作为 NodeJS 的包管理器能够轻松的引入外部模块来辅助开发,原本仅需一小部分,然而 NPM 会安装模块的所有功能,这导致代码过度增加。这篇文章是同之前介绍的 Minicode, UFF Remover, JSLIM 属于同类型的工作,而本文的主要贡献如下:

  • 使用静态、动态分析、代码拆分技术,在保持原始功能的前提下减少 NodeJS 应用大小

  • 实现工具 Stubbifier,其支持新版 JavaScript ECMA International

  • 工具在 15 个开源 NodeJS 应用程序(75 个客户端,每个项目使用 5 个主题)上进行评估,平均大小减少了 56%

2 主要架构

图 1 方法概述

由于 JavaScript 的动态加载特性,为了真正合理的精简项目,动态的测试是必不可少的。因此本文的架构的输入之一就是项目的 test 用例。通过 test 用例去完成动态测试,构建代码调用图,从而使用存根来替换(Transformation)调用图不用的函数和文件,完成项目精简。

调用图构建

调用图主要包括动态静态两种。这里主要使用了 NYC 命令行工具来计算动态调用图的代码覆盖率。静态调用图主要使用 CodeQL 来进行静态分析,通过跟踪模型数据流,完成静态图构建。这里针对 JS 动态属性的问题,使用存根进行动态加载,因此不会导致可达路径标记为不可达。

存根

存根,可以解释为备份。这里存根的引入主要是解决路径不可达问题,由于会存在无法访问的函数和文件,这里 Stubbifier 使用存根来进行替换(当代码短于存根时,将不会产生替代)。

这里主要会对文件、函数(包括匿名函数、类和对象的方法、箭头函数等)进行存根,而对于一些特殊情况将无法进行存根操作,例如无法出现在 eval 函数中的 yield 关键字等。

其他

作者这里考虑到新代码的引入可能会带来一些安全问题,提出了守卫机制,即对扩展代码的检查,看起是否会包含高权限函数(eval, process.exec, child_process 等)。

考虑到包开发者可能会使用打包器,而打包器可能会执行自己的代码转换逻辑,因此包作者最好在打包后再使用 Stubbifier。

图 2 与打包器集成使用

3 实验效果

实验选择了 NPM 中最受欢迎的 15 个项目,但需要满足项目能够自主安装、无错构建,并且包含一个无错的测试套件。下面是选用的项目信息。

针对本文提出的方法,作者设计并回答了以下问题(内容较为丰富,这里简要概述一下结果和表格):

表 1 实验效果
表 1 续 实验效果

- RQ1:Stubbifier 减少了多少应用程序大小,哪种类型的调用图(静态或动态)对减少应用程序大小更有效?

该项研究结果如表 1 所示。可以看到的是,Stubbifier 能够帮助项目缩减大小。静态缩减从 2% 到 87% 不等(平均 56%),动态缩减从 3% 到 89% 不等(平均 31%),并且在 11/15 的项目中,静态有着更好的表现。

- RQ2:由于存根扩展,动态加载了多少代码?会增加多少开销?

根据表 2 可以观察到运行增长时常基本在 2s 内,拓展代码也是在可以接受的范围内。而开销变化幅度不大,整体在可接受范围内,但确实会存在项目减速的问题存在。

表 2 项目开销结果

- RQ3:Stubbifier 需要多少时间来改造应用程序?

表 3 所示,这里展示了每个项目产生调用图和处理调用图的时间。可以看到的是处理时间大多保持在 3s 左右,比较花时间的在于调用图的构建。而在调用图的构建中,静态图的构建更花时间,并且时间是动态图构建时长的数十倍。因此对于一个项目来说,如果使用 Stubbifier 的话,往往需要增加 5-15 分钟左右的缩减时间,作者认为这个对于一个项目收尾工作来说是可以接受的(这里我认为也是可以接受的时间范围,但这个时间肯定越短越好)。

表 3 调用图生成及处理时长

- RQ4:受保护的执行模式会产生多少运行时开销,它能否检测到安全漏洞?

启用守卫机制,能够明显看到开销增大,这是必然的,因为这相当于对文件进行了多步处理,同时由于代码的变更,使得缩减效果变差,具体结果如表  所示。而对安全漏洞的检测,在测试项目上确实发现了 3 个并且得到验证的漏洞,但可能存在漏报情况,而且拦截的主要是敏感函数,这部分也是安全漏洞的主要成因。

表 4 有无守卫机制对比结果

- RQ5:Stubbifier 将使用 Rollup bundle 的应用程序的大小减少了多少?

这里 Stubbifier 针对 Rollup bundle 后的包进行了处理,发现代码能够进一步缩减,例如 memsf 项目,Rollup bundle 后大小为 128 KB,而经过 Stubbifier 后能够在 128 KB 基础上进一步缩减为 10 KB。可见 Stubbifier 是有用的。作者针对 bundle 进行了进一步的分析解释,bundle 的主要作用在于打包成一个可分发文件而不是缩减大小。(但具体为什么 tree-shaking 机制没能很好的发挥作用这里没看懂)

表 5 针对 bundle 项目的效果

4 个人思考

  • 这篇文章与 Minicode 的区别在于针对对象不同,minicode 将开发版本作为处理对象,而本文是在去除开发版本中依赖项后,针对分发运行版本的处理。并且该文章支持 ES6 的新特性

  • 现有的 Rollup 打包器具有一定的代码去膨胀功能,而本项目的使用虽然能够减少更多的不必要代码,但是确实会增加后期的资源开销,要根据具体项目来判断是否进行使用

  • 此外就是动态分析,必然要依赖于测试用例,而测试用例的有无、质量等都会影响到动态分析的使用,因此动态分析尽管有效解决 JS 动态加载机制的问题,但是对于没有测试用例的项目将无法运用,具有较大的局限性

安全学术圈招募队友-ing 
有兴趣加入学术圈的请联系 secdr#qq.com

文章来源: http://mp.weixin.qq.com/s?__biz=MzU5MTM5MTQ2MA==&mid=2247488815&idx=1&sn=594685e7f222723d41b0044db36bb3ec&chksm=fe2eeaa4c95963b27045d1f4c9d5328daad8b4cd3e9487c033830fe3745419924d0521946483#rd
如有侵权请联系:admin#unsafe.sh