蚂蚁安全光年实验室HODOR研究论文被国际计算机安全顶级会议ACM CCS录用
近⽇,上海交通大学与蚂蚁安全光年实验室以及浙江大学联合论⽂《HODOR: Shrinking Attack Surface on Node.js via System Call Limitation》中稿 CCS 2023。
ACM CCS与IEEE S&P、USENIX Security、NDSS并列为计算机安全领域四大顶级会议,收录研究机构以及科技企业在计算机安全和隐私研究领域最前沿、最顶级的研究成果,被中国计算机学会(CCF)认定为网络与信息安全领域A类国际学术会议,ACM CCS至今已举办29届,拥有悠久的历史和极高的声誉,一直被视为系统和网络安全领域研究的风向标,过去五年录取率为18%左右。
本次中稿论文的主题是《HODOR: Shrinking Attack Surface on Node.js via System Call Limitation》( 《HODOR:通过限制可调用的系统调用集合来缩减 Node.js应用攻击面 》)。该工作提出了 HODOR,针对 Node.js服务端应用程序提供系统调用级别运行时防护的系统。(点击文末“阅读原文”,查看论文)
上海交通大学网络空间安全学院院长谷大武教授表示,现如今,Node.js应用场景越来越多,涉及金融、云平台、互联网等关键领域,但是Node.js应用程序面临着供应链漏洞的严重威胁。Hodor是上海交通大学LoCCS实验室和蚂蚁安全光年实验室以及浙江大学IS2实验室提出的针对Node.js应用程序提供的细粒度的系统调用级别的运行时防护系统。团队围绕Node.js框架运行的特征进行了探索,提出了基于Seccomp技术的创新性运行时保护方案Hodor,显著降低了在Node.js应用程序场景下,供应链漏洞所引入的任意代码执行漏洞产生的危害,显著提高了现有工作针对Node.js应用程序的运行时保护的效率,为研究人员提供了新的研究思路。
引言
Node.js是一个开源的、跨平台的JavaScript运行时环境,它提供给JS代码访问系统调用的能力,并允许在服务器端执行JavaScript代码。Node.js使用了事件驱动、非阻塞式I/O模型,具备轻量且高效的优势。目前,一些知名网站在其网页产品中使用了 Node.js,例如 Paypal、LinkedIn、Microsoft和Netflix。
然而,如图一所示,这也会导致JS代码存在破坏运行时系统正常运行的可能性,具体来说,攻击者可以通过利用Node.js应用中的任意代码执行漏洞,即,利用vm.Script或eval等执行任意JS代码,或利用child_process模块的exec方法执行命令,扩大其攻击的能力。进一步地,Node.js庞大的第三方库生态系统npm,也会扩大过时的库中潜在任意代码执行漏洞所引入的安全隐患。
图一
举一个例子,攻击者可以利用任意代码执行漏洞扩大其攻击面。如图二所示,周下载量超过三百万次的growl库(v1.8.0)中的growl函数,用户可以使用growl函数创建一个通知消息(第4行)。该消息被传递给child_process模块的exec方法(第15行),作为notify-send命令的参数执行。由于消息字符串没有经过验证,攻击者可以将恶意命令注入到消息字符串中,例如"You have a mail `echo Hacker` ",Hacker字符串将被打印出来。此时,攻击者具有与运行Node.js应用程序的进程相同的权限,他们可以进一步扩大攻击面并执行更加危险的操作。例如,可以读取敏感文件("mail `cat /etc/passwd` "),创建远程连接("mail `nc -l -e /bin/bash 8001` "),以及更改root权限("mail `su root` ")。现有运行时防护工作仅能在在JavaScript源代码级别上限制攻击者的能力。然而,对于以上此类拥有与运行应用程序进程相同权限的攻击者来说,这些保护措施则是无效的。
图二
针对此类攻击,我们设计了HODOR系统,HODOR通过限制应用系统调用的能力,缩小任意代码执行攻击的攻击面。如图三所示,Node.js框架由内置层模块、绑定层模块和依赖层模块组成,为了保留应用程序所必须的系统调用,需要通过程序分析的方法对不同功能、不同语言所编写的模块进行层层分析(挑战一),并最终确认应用程序在运行时所必须的系统调用。为了开展系统调用级别的保护,需要使用Seccomp技术,然而现有的工作仅将Seccomp应用在进程上,而Node.js引擎是多线程框架,且考虑到Seccomp的继承特性,需要设计不同的Seccomp策略并应用到不同的线程上(挑战二)。
图三
系统设计
2.1 框架概览
图四为Hodor的pipeline:
1.首先,为Node.js应用程序的源代码和Node.js引擎构建了调用图。对于由JS编写的应用层和内置层模块代码,我们通过动静态结合的方法为JS源码构建了调用图,并在构建调用图时考虑了builtin方法的语义和任意命令执行的语句。对于由C/C++编写的绑定层模块和依赖层模块,我们使用SVF工具对编译后的LLVM Code构建调用图,并针对switch-case语句和函数参数指针进行了局部上下文敏感的调用图分析。
2.接下来,基于这些调用图,建立内置模块API与其系统调用列表之间的映射关系。对于应用层,我们通过遍历调用图记录了运行时所调用的Node.js API;对于内置层模块,我们通过遍历调用图记录了Node.js API与Binding层API的映射关系;对于绑定层模块,我们撰写了LLVM Pass,记录了Binding层与C函数的映射关系;对于依赖层模块,我们则从C函数开始遍历调用图,记录下来C++函数在运行时使用的系统调用。
3.根据层层的API映射关系,我们可以得到应用程序在运行时所使用的系统调用。为了实现线程级的保护机制,我们根据主线程和线程池线程运行的代码模式,将系统调用分为主线程执行的系统调用和线程池执行的系统调用。这些系统调用即为Seccomp策略中的系统调用白名单。
4.运行Node.js应用程序的线程中。我们设计了一种细粒度的过滤机制,对于不同类型的应用程序中采用不同的加载机制,对于不同类型的线程采取不同的加载时机。
2.2 调用图构建
在对JavaScript代码进行调用图分析时,相较于现有工作,我们考虑了Node.js引擎和JavaScript语言所提供的内置方法的代码模式,并在构建调用图时添加内置方法执行时的调用边。例如,如图2所示,在执行命令之后,第2行的匿名函数将被调用;当Promise对象包装的函数执行完毕时,then方法的回调函数将被调用;以及当调用Array对象的map方法(例如,[1,2,3].map(x => x * 2))。通过静态分析和规则匹配,我们能够生成更完整的调用图。另外,JavaScript语言的高度动态特性仍然是导致调用图构建不精确的关键因素。例如,JavaScript可以使用eval方法执行字符串所表示的JavaScript代码。为了解决这一问题,我们引入了动态调用图分析。我们使用动态调用图工具 Nodeprof来完善静态分析所生成的调用图。除此之外,Node.js 应用程序可以使用 child_process 模块的 exec 和 spawn 方法来执行动态命令,对于动态命令中所必须的系统调用,我们使用Linux 的 strace 工具识别并记录入白名单。
在对C/C++代码进行调用图分析时,我们基于了SVF工具。在对Node.js底层模块进行分析时,我们发现非上下文敏感的调用图构建在处理Node.js底层代码中的switch-case语句(如图五所示)和函数指针参数(如图六所示)时会产生较多误报,从而极大的影响了白名单的精度。因此,我们设计了局部上下文敏感的算法。具体地,我们会首先定位满足代码模式的switch-case 和函数参数语句,对于这些语句我们采取局部上下文敏感的数据流分析,以确定传播到分支选择变量和函数指针对象的变量的赋值情况。反向数据流分析从分支选择变量或函数指针参数开始,直到变量或参数被赋予常量或实际函数为止。当我们到达反向数据流分析的终点时,我们使用基于LLVM的use-define chain分析方法来确定变量在程序内部的数据流分析中是否被赋予一个或多个常量或实际函数。当变量通过不同的调用上下文被赋予单个常量或实际函数时,我们执行上下文敏感分析。
图五
图六
Node.js底层模块都通过libc库与操作系统内核交互。考虑到流行的glibc库无法编译为LLVM IR,我们无法利用SVF对其进行分析。因此Hodor使用了musl libc。Musl libc是与Node.js兼容的轻量级libc库,可以被LLVM编译。我们用musl替换glibc以构建更精确的调用图。
2.3 映射关系构建
在这个阶段,我们为内置层模块、绑定层模块和依赖层模块构建API映射关系用以生成系统调用白名单,如图七所示。内置模块利用module.export或export对象将内置方法导出给Node.js应用程序使用,并利用internalBinding方法导入绑定层模块地方法,我们遍历每个内置模块的调用图(第2-12行),生成映射关系。对于绑定层模块,我们开发了LLVM Pass来定位和分析注册到内置层模块node_module结构和初始化C函数的关系(第13-19行)。最后,对于依赖层模块,我们从初始化C函数为起点,遍历调用图,收集C++函数在执行时所使用的libc库函数(第20-25行)。
图七
2.4 白名单生成
算法2展示了为Node.js应用程序的白名单的方法,如图八所示。应用程序使用require方法加载内置模块和依赖包,我们首先遍历应用程序的调用图,并识别require方法的调用(第3行),并通过将内置方法与在构建映射阶段所得到的映射列表进行关联(第4-9行),为应用程序生成系统调用列表,即为应用白名单。为了提供细粒度的保护,我们根据线程池代码的代码模式,将白名单分为主线程白名单(第12行)和线程池白名单(第10行)。
图八
2.5 构建HODOR
Node.js应用程序可以分为使用线程池线程和不使用线程池线程的应用程序。针对不同类型的应用程序,我们采用不同的加载机制。对于需要线程池的应用程序,我们首先基于线程池白名单为线程池线程安装过滤器,然后为主线程安装过滤器,以防止线程池线程继承主线程过滤器。由于在Node.js引擎中广泛使用读取和写入系统调用(文件操作或更低级别的线程间通信),系统调用限制机制无法缩小与这两个系统调用相关的攻击面。Hodor额外采用了Linux的chroot机制和将文件所有权转移到高特权用户的方法来进一步限制与读写相关的攻击面扩大。
实验结果
最后,我们对HODOR进行了评估,对于具有任意代码执行漏洞的168个应用程序,HODOR可以将攻击面平均减少到19.42%(如图九所示),而运行时开销可以忽略不计(如图十所示)。
图九
图十
总结
在本文中,我们提出了HODOR——一种为通过在限制系统调用层面上为Node.js应用提供运行时保护的系统,用以减少任意代码执行的攻击面。HODOR首先为Node.js应用程序生成细粒度的系统调用权限,根据确定的所需权限,为应用程序创建白名单,并使用Seccomp机制在线程粒度级别上实施运行时系统调用的限制。实验结果表明,针对168个存在任意代码执行漏洞的应用程序,Hodor可以使攻击面减少为原来的19.42%,运行时开销可以忽略不计。
参考文献
[1]. Koishybayev I, Kapravelos A. Mininode: Reducing the attack surface of node. js applications[C]//23rd International Symposium on Research in Attacks, Intrusions and Defenses (RAID 2020). 2020: 121-134.
[2]. Bulekov A, Jahanshahi R, Egele M. Saphire: Sandboxing {PHP} applications with tailored system call allowlists[C]//30th USENIX Security Symposium (USENIX Security 21). 2021: 2881-2898.
[3]. Vasilakis N, Staicu C A, Ntousakis G, et al. Preventing dynamic library compromise on node. js via rwx-based privilege reduction[C]//Proceedings of the 2021 ACM SIGSAC Conference on Computer and Communications Security. 2021: 1821-1838.
点击“阅读原文”,查看论文