论文笔记——SerialDetector: Principled and Practical Exploration of Object Injection Vulnerabilities for the Web

时隔三年,继续更新!!!!

论文题目:SerialDetector: Principled and Practical Exploration of Object Injection Vulnerabilities for the Web

论文出处:NDSS 2021

论文作者:Mikhail Shcherbakov(KTH Royal Institute of Technology),Musard Balliu(KTH Royal Institute of Technology)

论文下载地址

会议视频地址

代码地址

0x01 Abstract & Introduction

​ 对象注入漏洞(OIV)允许攻击者控制的数据滥用Web应用代码库中的合法代码片段来执行一个代码链(gadget),攻击者利用这个代码链执行恶意行为,如RCE。当使用不受信任的数据实例化攻击者控制类型的对象,而且这个对象的属性攻击者可以选择时,就会发生OIV,从而触发恶意行为。

​ 论文第一个提出了针对包含框架和库的.NET语言应用的OIV漏洞的系统检测和利用方法。作者认为OIV漏洞的根本原因是不受信任的信息流从Web应用公共入口流到可以创建任意类型的对象的敏感函数去调用触发gadget执行的方法。

  1. 作者基于污点的数据流分析方法实现了自动化挖掘.NET程序集中的OIV模式;
  2. 使用这些模式自动匹配公开可用的gadget并验证OIV攻击的可用性;
  3. 用该方法对复杂的生产级应用进行实验,如微软的Azure DevOps Server;
  4. 描述针对Azure DevOps Server的威胁模型。

挑战:

  1. 对OIV核心语言特征的全面理解尚未出现;
  2. 类似.Net这种生产级别的框架,具有大量代码库复杂的实体、复杂的语言特性以及缺乏源代码;
  3. 缺乏一个自动化和开源的工具调查潜在攻击的可行性。

贡献:

  1. 识别了对象注入漏洞的根本原因,提出了原则而且实际的与框架无关的方法;
  2. 提出了第一个系统的检测和利用包含框架和库的.NET应用反序列化漏洞的方法;
  3. 开源了SerialDetector,实现了可扩展的基于污点的数据流分析挖掘OIV模式和利用公开可用的gadgets利用真实软件中的OIV;
  4. 进行一个彻底的评估,展示SerialDetector可以在安全分析中低负担地发现漏洞模式;
  5. 对Azure DevOps Server进行了深入的安全分析,解释了不同的威胁模型,借鉴这些威胁模型,展示了SeiralDetector识别和利用高危漏洞导致服务器远程代码执行。

0x02 Technical Background

2.1 应用层OIVs

​ 应用层OIVs包括如下要素:(1)一个允许攻击者注入不受信任数据的公共入口点;(2)一个可以创建攻击者可控类型对象的敏感函数;(3)一个由最终执行危险操作的方法调用链组成的gadget。示例如下面两张图,

image-20230404220003256

image-20230404220020037

攻击者构造name = OSCommand & args = del /q *,最后会导致远程代码执行,删除当前目录的所有文件。

​ 对于这类漏洞的检测,需要全面地分析考虑实现IOcomand接口的类中Excute方法的所有实现。

2.2 基础架构层OIVs

​ 在基础架构层的OIV的一个主要例子是不安全的反序列化,序列化是面向对象语言用来存储和传输对象的机制,可以将对象转换成字节流的形式存储或传输,反序列化与之相反,是从字节流中重新创建对象原始状态的过程。不安全的反序列化中的OIVs如下图实例:

​ 下图表示使用YamlDotNet反序列化yaml参数的代码片段,Deserialeze函数是一个公开入口点(public entry point),可能从HTTP请求参数、cookie、文件上传收到数据,之后递归调用DeserializeObject创建指定类型的对象并设置它的字段属性值。

image-20230405193659345

​ 下图为ObjectDataProvider类的代码片段,对象的属性setter ObjectInstance调用Refresh,会调用MethodName指定的方法,所以一旦攻击者控制了ObjectDataProvider.MethodNameObjectDataProvider.ObjectInstance,就可以执行任意方法。

image-20230405193723811

​ 攻击者用如下的payload即可实现执行计算器。

image-20230405220733214

​ 上述漏洞同样包括应用级OIV的3个要素:(1)公开入口点,(2)调用可以创建任意类型对象的敏感函数,(3)后续,恶意gadget的执行(属性的setter)。为了检测这类漏洞,应考虑属性setter方法的所有实现,如.NET框架库中的SetValue

0x03 Overview of the approach

3.1 分析对象注入漏洞的根本原因

  • 公共的入口点(public entry points):

  • 敏感Sink(sensitive sinks):RuntimeTypeHandle.Allocate(),方法以输入的type作为参数,并返回type类型定义的对象;

  • 攻击触发器(attack triggers):RuntimeMethodHandle.InvokeMethod(),能够决定导致恶意行为Gadget链第一个方法的方法;

​ 作者使用这三个因素作为OIV模式(OIV patterns):一个公共入口点会触发敏感接收器的执行,以创建一个控制攻击触发器执行的对象,下图为OIV模式的一个例子:

image-20230317160626713

研究问题:

(i):识别OIV的适当标准是什么?

(ii):我们能否提供一个工具可以在大规模包括框架和库的应用中检查OIV模式?

(iii):我们如何验生成的OIV模式的有效性?

(iv):是否有现实世界的应用来证明这种方法的可行性?

3.2 SerialDetector

​ SerialDetector的整体架构如下图,分为检测(全自动)和利用(半自动)两个阶段,对应的输入和输出在图中很清晰。

image-20230405222722597

(1)静态分析

目标针对Commom Intermediate Language的原因:

(i):旨在分析 .NET Framework 的代码以识别敏感方法,在源代码级别是不可用的;???

(ii):这个方法允许我们实现框架无关的分析方法,不需要任何框架的已知漏洞知识。

(iii):旨在针对真实生产级应用进行深入安全评测,比如微软的Azure DevOps,它们的源码都是不可用的。

(iv):对比高级语言,分析CIL需要更少的必须支持的语言构造。

​ 关注CIL不会失去任何代码分析中重要的数据,另一方面,CIL 继承了分析基于堆栈的面向对象中间语言所面临的众所周知的挑战,例如对求值栈的仿真和控制流的重构。这种分析的核心是基于方法摘要、指针别名和高效的即时控制流图重构的模块化过程间抽象解释。该分析实现了类型敏感、一个轻量级的上下文敏感以及类型层次结构图分析,用于重构调用图。

(2)结果路线图

​ 因为攻击触发器是 gadget 中的第一个方法,所以我们生成的模式中的攻击触发器只需与 gadget 的第一个方法匹配即可。

0x04 Taint-Based Static Analysis

​ 这节提出了基于污点的静态分析方法支撑SerialDetector的检测部分,分析针对CIL,一个面向对象的基于栈的二进制指令集,该方法采用模块化的过程间字段敏感(field-sensitive)的数据流分析。

4.1 CIL语言和符号

关注如下的指令集,描述分析方法的核心思想。

image-20230406154506898

“ldvar” 可能指的是 IL (Intermediate Language) 中的 “load local variable”,即加载本地变量的指令。在 CIL 中,本地变量是在方法中定义的变量,它们只在方法中有效。 “ldvar” 指令用于将本地变量的值加载到执行堆栈上,以供后续指令使用。该指令具有不同的操作码,具体取决于本地变量的类型和位置。

“ldfld” 是 CIL (Common Intermediate Language) 中的一条指令,用于将对象的字段值加载到执行堆栈上。它需要两个参数:第一个参数是对象引用,第二个参数是字段的元数据标记。这个指令是用于访问一个对象的实例字段的。

例如,如果有一个名为 “myObject” 的对象实例,它有一个名为 “myField” 的 int 类型字段,那么可以使用以下 C# 代码访问该字段:

1
2
arduinoCopy code
int x = myObject.myField;

在 CIL 中,该代码将被转换为以下指令序列:

1
2
arduinoCopy codeldarg.0 // 加载 this 对象的引用
ldfld int32 Namespace.MyClass::myField // 加载字段的值

其中 “ldarg.0” 指令用于加载当前方法的 this 指针,它是一个指向对象实例的引用。 “ldfld” 指令使用元数据标记来确定要加载的字段。

stvar 是 CIL 语言中的指令之一,用于将值存储到指定变量中。其操作数指定了要存储的变量的索引,该变量必须在先前定义的变量列表中。以下是一个示例代码片段,展示了如何使用 stvar 指令将值存储到变量中:

1
2
3
swiftCopy code.locals init (int32 V_0)
ldc.i4.5
stvar V_0

在这个示例中,.locals 指令声明了一个名为 V_0 的整型变量。ldc.i4.5 将整数值 5 推送到操作数栈上,然后 stvar V_0 将该值存储到 V_0 变量中。

……

​ 作者假设了变量集合Var(包含函数形参、本地变量等)、对象字段集合Fld、值集合Val(对象位置location和其他值bool true\bool false)、类类型集合Types。用 f[x → v] 表示将值 v 替换函数 f 中的参数 x,f(x) 表示函数 f 中 x 的值使用 f(x)↓ 来表示偏函数 f 在 x 处定义,否则为 f(x)↑。使用 (b ? e1 : e2) 表示一个条件表达式,如果条件 b 为真,则返回 e1,否则返回 e2。

​ 内存模型包含一个环境E: Var→Val,映射变量到值、一个堆h:Loc x Fld → Val,映射对象位置和字段到值,一个操作栈s和一个调用栈cs。E和h都是偏函数, 表示未定义的值。程序P Prog 由指令列表组成,由程序计数器pc索引表示,i PC。作者默认一个类的定义包含一组字段、一组方法,以及一组启动执行的不同的方法。每个方法包含一个带有形参的函数标识符和执行列表。sig Sig ,sig为由函数名和形参组成的函数签名。

​ 执行模型由cfg CFG组成,cfg形如cfg=(pc,cs,E,h,s)包含程序计数器pcPC,环境E ENV,堆h Heap,调用栈 cs = (pc,E,s)* ,cs (PC × Env × Val * ) *,操作栈s Val*。

​ 希腊字母e表示空栈, t::v表示栈的顶部是v,尾部是t。CIL程序中的语义由cfg上的转换关系定义,→∈ Conf ×Conf

下图为CIL的操作语义:

image-20230406170327323

4.2 过程内数据流分析

​ 作者的抽象解释高估了原始类型的操作,关注对象位置从敏感sink到攻击触发器的传播。作者对CIL指令的抽象解释利用了对象位置值和其他原始值的符号域。<pc,E,h,s,$\phi$,$\psi$>,前四个对应具体的组件,$\phi$过估计符号栈,$\psi$过估计控制流。

〇挑战和解决方案

挑战:

  • 堆的抽象表示
  • 非结构化的控制流和栈的符号表示(缺乏结构和跨不同分支保存符号堆栈的一致性)
  • 控制流的完备性(sound approximation)

解决方法:

​ 作者使用基于存储的堆抽象和通过正向符号分析对条件和循环的合并点的有效动态计算来解决这些挑战。作者的分析方法是流不敏感的,因此抽象堆图和关于别名的信息存在于方法中的任何程序点上。 通过记录每个分支指令的堆栈状态,并组合合并点上的堆栈,同时更新堆栈和环境中的指针,从而确保了符号堆栈的一致性。

①抽象堆

​ 节点表示抽象位置,边表示指向关系,包含字段和变量对应的标签。堆的抽象表示如下图,规则S-LDVAR、S-LDFLD和S-NEWOBJ(未显示)类似于图12中相应的规则,但对符号值进行操作,并忽略调用堆栈cs。规则S-STVAR和S-STFLD依赖于一个update函数来实现流不敏感字段敏感的抽象语义。update函数以两个lication作为输入,会合并根节点在那些locations的子图,这个方法是流不敏感的,因为它使用新的符号值更新符号配置,而不是重新变量字段的旧值。

image-20230406211143738

以下面的代码为例,第四行代码对应右边3行CIL,

image-20230406212337079

分析完前三行代码可以得到下图左侧的堆图,当分析第四条指令即对应的4a-4c,合并la(E(arg)=la)和lb(栈顶是lb),得到下图右侧图。

image-20230406212622434

②抽象控制流

​ 按照程序计数器pc施加的程序顺序“顺序”分析指令(1234,不考虑跳转,按顺序分析),并确保符号栈和堆的动态一致性。用两个数据结构扩展了符号配置,一个函数$\phi$: PC→℘(Stack),映射程序计数器索引到栈集合,来记录控制流分支合并点的符号栈;一个程序计数器索引集$\psi$ ℘(PC)来记录与循环相关联的向后跳转(backward jumps),用来确保循环不被重复分析,(这两个符号后面多次使用)当一个非条件跳转被分析,pc $\psi$,设置当前栈为⊥,继续分析下一条指令,一个未定义的堆栈将简单用规则S-STSKIP跳过当前指令的分析。因为这种分析是*流和路径不敏感*的,所以一个代码块分析一次是足够的。下图解释控制流指令分析算法,使用函数mergeStacks : (Stack) × Heap × Env × Φ Stack × Heap × Env × Φ合并所有栈并且更新符号配置。mergeStacks逐点合并符号位置,并更新指向其他组件中合并位置的指针。

image-20230406215720048

考虑如下的代码,分析第5行br 7时,为了$\phi$(7),存储包含arg和var2的函数栈,当分析到第7行stfld obj时,应用S-STUPD规则合并$\phi$(7)和当前的符号栈(包含arg和var1),然后S-STFLD确保字段arg.obj包含合并后的location。

image-20230406220225999

​ 还需考虑由向后跳转指令产生的潜在循环。如下图的代码,分析第一条指令时,基于S-BRFWD规则将当前符号栈存储在$\phi$(15)中,并将栈更新为未定义,这是因为不知道是否会从另一个配置中到达索引(2)处的下一条指令。因此直到下一个合并点(如对应的$\phi$(pc)是被定义过的)都利用规则S-STSKIP简单地跳过。然后使用规则S-STUPD合并$\phi$(15)和未定义的符号栈,之后加载flag变量到栈,并通过规则S-BRTRUEBWD在索引16处检查指令brture 2,这里对应上上图中i<pc的情况,因为pc $\psi$ ,将控制转换到索引为2的代码行去分析循环体,当再次分析到brture 2时,因为已经分析过,即16 $\psi$ ,所以继续分析下一条指令(索引为17的指令)。

image-20230406230306346

③假名和污点跟踪

​ 为了分析从敏感sink到攻击触发器的信息流,在抽象堆图的节点中丰富加了taint标记。如下图,无论什么时候分析完敏感sink后,将返回值放入栈标记为taint,然后为b.bar这个假名添加边,最终分析第三行得到b.bar是tainted的,因此,控制其类型的攻击者决定了执行VirtualCall()的具体实现。因此,我们认为这种方法是一种潜在的攻击触发器。

image-20230407102651398

4.3模块化数据流分析

①调用图分析

​ 从CIL程序集中识别与调用和调用命令指令关联的方法签名,并存储在hash表中,签名作为键,调用的函数作为值。这个hash表就是调用图的一个表示。通过backward analysis,可以重构调用图。从sink到point可以计算O(n),n表示调用栈的深度。计算类型层次图来确定虚拟方法调用的所有实现。作者假设一个基本方法的虚拟调用可以将控制转移到该方法的任何实现中,并将这些信息存储在调用图中。分析器在将调用图从一个敏感的接收器到入口点的反向重建期间,以及在callvirt指令的抽象解释期间使用这些信息。

前向传播与反向传播

②使用函数摘要的过程间分析

​ 在初步确定的每个入口点(可能是函数的入口点)进行一个模块化数据流分析,在任何分析到达新方法,独立于调用者的上下文来分析该方法,因此,它生成了一个称为摘要的堆图的紧凑表示。然后将摘要存储到缓存结构K中,并为对相同方法的后续调用重用它。

​ 使用下面的符号描述方法调用的抽象表示,state σ State ,σ是一个元组(E, h, s, $\phi$, $\psi$) ,无论什么时候分析一个新方法时,都存储它。

​ 符号调用堆栈cs (State × PC ) *是一个(σ,pc)对的堆栈,包含调用者的状态和状态σ中的程序计数器索引。

​ 偏函数K:Sig → Sum 缓存每个方法签名的方法摘要,方法摘要 sum ∈ Sum 是由环境E和堆h组成的元组(E,h)定义的。

​ 下图对应基于基于摘要的过程间分析方法的四种案例:S-CALLK:调用缓存K中已有对应摘要的函数,apply摘要(包含形参和返回值等)和当前的state,得到新状态;S-CALLEXT:调用到外部/本地方法,但没有实现可用,;S-CALL:调用到在缓存K中没有摘要的(非递归)方法,通过将控制转移到索引pc0的代码,并将调用者的上下文(调用者状态和程序计数器索引)存储在符号堆栈的cs中,从而触发对新方法的过程内分析,被调用方法的分析是上下文独立的,σ是未定义的,匹配S-END计算该方法终止时的过程内分析摘要,后续将摘要apply于调用者的上下文,并将摘要存储到K中。继续下一个指令的分析;S-END:在一个方法的分析终止时,更新缓存K.

image-20230407105459332

③例子:方法调用

​ 用下图左侧代码说明非递归调用的抽象解释,从Epoint方法开始分析,并调用SSink这个外部方法,规则S-CALLEXT分配一个新的位置,并将其推到堆栈中,以模拟返回值。因为方法签名被定义为敏感sink,所以我们将新变量标记为tainted。随后,分配值将受污染的值存储到b.foo中的位置。接下来,调用方法CreateAlias,它在将当前的σ和pc存储到调用堆栈后,通过规则S-CALL触发对其主体的过程内分析。该分析应用规则S-STFLD来在arg.bar和arg.foo之间创建一个别名。最后,规则S-END从当前符号状态构建摘要并把它存入缓存。摘要生成算法从E中的根变量Var开始遍历堆图h,并存储已访问的节点和对摘要的引用。这是唯一可能影响调用者上下文的信息。随后,该算法将摘要应用于调用者的状态,以创建一个考虑方法调用效果的新状态,并继续执行方法EPoint的下一条指令。图8b描述了摘要应用程序的效果,它们将带有bar标签的边添加到堆图中,从而导致这两个字段指向被污染的节点。

image-20230407194307308

​ 最后,通过规则S-CALL分析了Foo方法。Foo包含一个以参数arg作为参数的外部方法调用(由规则S-CALLEXT分析)。由于ExternalMethod可以用作攻击触发器,因此作者将有关ExternalMethod的信息存储在arg位置的节点中。规则S-END构建并存储摘要,并在到达方法的结束时将其应用于EPoint上下文。因此,我们合并两个位置(传递给Foo的b.bar和摘要中的arg),并检测对带有污染标记的攻击触发器的调用。最后,作者将从EPoint到SSink和ExternalMethod的链存储为OIV模式。

0x05 Implementation

5.1 剖析SerialDetector

​ 使用C#语言开发,运行在.NET平台,使用dnlib库解析程序集。

①检测阶段

​ 序列检测器的显著特点是,它实现了与框架无关的范式,并且不使用任何基于方法或类名的启发式方法来检测OIV模式。输入由一组的.NET程序集、针对敏感的接收器和攻击触发器的规则组成。敏感sink最初被描述为返回System.Object类型对象的本机方法。因此,我们假设攻击者可以操作敏感sink的参数或运行时状态,以获得任意类型的对象。SerialDetector只分析.NET程序集中的CIL代码,并且不支持本地方法中的二进制代码。因此,我们采用一种保守的方法,即每个本机方法都返回一个任何派生类型的对象作为返回类型。然后,我们将敏感sink的返回对象标记为tainted。攻击触发器被描述为将受污染对象作为参数的本地(外部)方法,或者将第一个参数标记为受污染对象的虚拟方法。

​ 检测阶段的管道由四个步骤组成:

  1. 序列检测器为整个.NET程序集数据集建立一个方法调用图的索引;
  2. 它使用定义敏感接收器的标准过滤所有本机方法签名。这一步产生敏感sink的签名,我们使用它来向后构建调用图的切片,从敏感sink到入口点方法;
  3. 如第四节所述,序列检测器执行基于摘要的过程间的数据流分析;
  4. 它输出一系列模式,其中包含调用每个敏感sink的触发器以及从入口点到敏感sink的轨迹。收集这些模式到一个知识库中,并将它们作为利用阶段的输入。

②利用生成和验证

​ 通过先前得到的知识库,手动识别框架和库中漏洞模式的用法。为此,作者使用YSoSerial.Net项目创建利用目标应用程序漏洞的模板。我们通过使用类似于DSL的API直接在C#代码中声明每个公共脆弱方法的签名来实现这一点。下图对应2.2节示例漏洞的利用模板。

image-20230407205402084

​ 使用DSL生成模板有便于修改和测试不同载荷的优点。

  1. 匹配:手动将生成的模式与已知gadget的真实敏感sink和攻击触发器匹配;
  2. 填充知识库:用匹配的结果填充知识库,描述了一个用于创建和转换为各种格式以生成有效负载的小工具的代码,还描述了来自模板中匹配模式的脆弱入口点的签名,以及额外的限制,例如,脆弱库的版本。
  3. payload和模板生成:基于描述的知识库规则生成payload和模板;
  4. 调用图分析:使用模板作为调用图分析的输入,以检测目标应用程序中潜在的脆弱模板。SerialDetector从应用程序入口点到模板中描述的脆弱调用生成调用图;
  5. 模板验证:它可以验证给定的有效负载是否可以利用模板中的一个入口点。它还使用模板描述作为编译带有漏洞代码.NET程序集的源来验证调用图分析步骤。它对这个示例运行分析。测试所需的所有信息都是从知识库中提取的;
  6. 利用生成:human-in-the-loop

5.2 挑战和限制

①虚拟函数调用

②递归

③静态字段

④数组

⑤不支持的指令

0x06 Evaluation

​ SerialDetector首先索引了.NET框架的所有代码并且检测敏感sink的列表。.NET框架由269个程序集、466218个方法和50399个类型。

​ 12.4s完成了这步,得到123个敏感sink,因为不是所有sink都能根据输入动态创建新的对象,手动分析过滤了这些敏感sink。

6.1 检测阶段

​ 实验目标程序:.NET框架中的OIV和来自YSoSerial.Net项目中使用不安全反序列化器的第三方库。

​ 使用不安全反序列化器的反序列化方法作为数据流分析的入口点进行分析,将攻击触发器与YSoSerial中的gadget进行匹配作为有效性的指标。

​ 下表中patterns表示每个序列化器的不同OIV模式数,Priority Patterns表示包含公开gadget方法的OIV模式数。

image-20230407220153930

6.2 利用阶段

​ 实验目标程序:NVD中搜索.NET and CWE-502,得到55个记录,对这些手动分析发现11个真实检测到漏洞,而且仅仅有5个存在漏洞的应用可以搭建。使用SerialDetector分析下表的应用,包括7个不同的应用,10个OIVS。SerialDetector检测到除Telerik UI产品之外的所有应用程序中不安全反序列化程序和相关入口点的脆弱调用,该UI产品使用反射API调用JavaScript序列化程序的不安全配置。当前版本的序列检测器不支持重构调用图的反射(Reflection),并且忽略这样的调用。Entry Points w/o Threat Model列表示到达不安全序列化器调用的入口点的数量,然而有些入口点永远不会被执行。Entry Points w/ Threat Model列报告了SerialDetector的结果。

image-20230407222213898

PS:上表中的后两列对应的三个漏洞为作者报告的0day漏洞。

​ CVE-2019-0604中6283的原因是SharePoint服务器中的代码与其主要程序集Microsoft.SharePoint.dll之间的紧密耦合,以及方法对虚拟调用的过估计。

性能:分析速度相当快,平均分析时间47.4秒,作者的流不敏感方法减少了堆图的大小,这使得SerialDetector能够更快地应用摘要和合并location,从而提高了整体分析时间。另一个提高可伸缩性的因素是轻量级上下文敏感分析的使用。

误报:我们还发现了从未被调用的攻击触发器。造成这些假阳性的根本原因是数据流分析的流-不敏感性。对流不敏感的方法允许我们以牺牲分析的精度为代价来控制堆的大小。另一方面,我们的结果显示,安全分析师应该手动审查的模式的数量并不庞大。

0x07 In-depth analysis of Azure Devops Server

介绍了三个对于Azure Devops Server的威胁模型。

下图路径:1,2a/2b/2c

image-20230407224830609

下图路径:1a,2a / 1b,2b,3b

image-20230407224932925

下图路径:1a,2a / 1b,2b,3b,4b

image-20230407225211627