CodeDom漏洞模式与SharePoint RCE

admin 2023年12月12日02:27:22评论16 views字数 6223阅读20分44秒阅读模式
什么是CodeDom机制

.NET Framework 提供一种叫做 代码文档对象模型 (CodeDom)  的机制。我们可以使用 CodeDom 元素组合成 CodeDom 图来表示一段源代码的逻辑。


CodeDom 有两个主要的功能:

  1. 根据 CodeDom 图生成源代码;

  2. 将源代码即时编译为程序集。


当然,也可以忽略中间过程,直接将 CodeDom 图编译为程序集。


关于CodeDom的例子

为了介绍CodeDom的一般用法,下面是提供一个关于CodeDom 例子。该例子展示了如何使用一段CodeDom程序描述一段源码的逻辑。


目标源码类似下面这样:

namespace MyNamespace {    using System;
public class MyClass { public static void MyMethod() { Console.WriteLine("Hello, World!"); } }}


对应的CodeDom例子:

using System;using System.CodeDom;using System.CodeDom.Compiler;using Microsoft.CSharp;using System.Reflection;
namespace CodeDomExample{ class Program { static void Main(string[] args) {    /************************************************************   // 第一部分:创建 CodeCompileUnit,构建 CodeDom图以表示一段代码逻辑    ************************************************************/ // 创建一个 CodeCompileUnit 对象,表示要编译的代码单元 CodeCompileUnit compileUnit = new CodeCompileUnit();
// 创建一个 CodeNamespace 对象,表示代码的命名空间 CodeNamespace codeNamespace = new CodeNamespace("MyNamespace");
// 添加需要引用的命名空间 codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
// 创建一个 CodeTypeDeclaration 对象,表示要编译的类型 CodeTypeDeclaration codeType = new CodeTypeDeclaration("MyClass"); codeType.IsClass = true;
// 在类型中添加一个方法 CodeMemberMethod codeMethod = new CodeMemberMethod(); codeMethod.Name = "MyMethod"; codeMethod.Attributes = MemberAttributes.Public | MemberAttributes.Static; codeMethod.ReturnType = new CodeTypeReference(typeof(void));
// 方法体中的代码 CodeSnippetStatement codeSnippet = new CodeSnippetStatement("Console.WriteLine("Hello, World!");");      codeMethod.Statements.Add(codeSnippet);
   // 将方法添加到类型中 codeType.Members.Add(codeMethod);
   // 将类型添加到命名空间中 codeNamespace.Types.Add(codeType);
   // 将命名空间添加到代码单元中 compileUnit.Namespaces.Add(codeNamespace);
   /*************************************************************   // 第二部分:将 CodeDom 图编译为程序集   **************************************************************/  // 创建一个 CSharpCodeProvider 对象,用于编译代码 CodeDomProvider provider = new CSharpCodeProvider();
  // 创建一个编译参数对象 CompilerParameters parameters = new CompilerParameters(); parameters.GenerateExecutable = true; parameters.OutputAssembly = "MyCode.exe";
// 编译代码 CompilerResults results = provider.CompileAssemblyFromDom(parameters, compileUnit);
// 检查编译是否成功 if (results.Errors.HasErrors) { foreach (CompilerError error in results.Errors) { Console.WriteLine("Error: {0}", error.ErrorText); } } else { Console.WriteLine("Code compiled successfully!"); }
   /***************************************************** // 第三部分:通过反射加载生成的程序集,并调用相应的方法   ******************************************************/ // 加载并执行编译生成的程序集 Assembly assembly = Assembly.LoadFrom("MyCode.exe"); Type type = assembly.GetType("MyNamespace.MyClass"); MethodInfo method = type.GetMethod("MyMethod"); method.Invoke(null, null); } }}


类似这样一个典型的 CodeDom 程序往往包括三部分:

  1. 依据某些信息,使用 CodeDom 元素 描述 一段源代码的逻辑;

  2. 将 CodeDom 图转换为源码,然后进行编译,或者 直接将 CodeDom图编译为程序集;

// 根据 CompileUnit (CodeDom图) 生成源码CodeDomProvider.GenerateCodeFromCompileUnit();// 从不同的源码表示形式编译程序集CodeDomProvider.CompileAssemblyFromDom();CodeDomProvider.CompileAssemblyFromFile();CodeDomProvider.CompileAssemblyFromSource();
  3. 通过反射加载前面生成的程序集,并调用其中的方法。


CodeDom的危险细节

CodeDom 中有大量的元素用于描述源代码中的结构,比如 算术运算、赋值语句、函数调用、循环语句、条件语句等。


下面举其中一个例子:

public class CodeTypeOfExpression : System.CodeDom.CodeExpression{    public CodeTypeOfExpression(string type);    ...}


CodeTypeOfExpression 用于描述一个 typeof() 语句,其参数是指定的类型名称标识符。示例如下:

CodeTypeOfExpression("System.String");
// 对应的源码如下:typeof(System.String)


可以看到,在 CodeDom 元素里,我们传入的是一个 字符串。但在生成的源码里,它变成了一个类型名称标识符。


假如传入的 "System.String" 字符串用户可控呢?是否存在注入?


CodeTypeOfExpression typeof1 =  new CodeTypeOfExpression(@"System.String); Object/**/test2 = System.Diagnostics.Process.Start(""cmd.exe"", "" /c calc"");//");
CodeVariableDeclarationStatement myVariable = new CodeVariableDeclarationStatement( typeof(Type), "test", typeof1);
myMethod.Statements.Add(myVariable);


生产的C#源码会像下面这样:

System.Type test = typeof(System.String); Object/**/test2 = System.Diagnostics.Process.Start("cmd.exe", " / c calc");//);

当然,即便跳过生成源码的中间过程,直接将 CodeDom 编译为程序集,也是会被注入的。


CodeDom 中的所有标识符元素都可以被注入,比如变量名、类型名、命名空间名


CVE-2020-0646
SharePoint WorkFlow RCE 漏洞

SharePoint 的 WorkFlow 提供一些受限的 Activity 以供用户编辑自定义工作流,比如事件到达某个节点后给某个人发一封邮件。其中发一封邮件就是一个Activity。


用户使用 XOML 格式的文件定义一段工作流,SharePoint 根据 XOML 中使用的 Activity 生成 CodeDom 图,然后生成相应源码,再编译成为相应的程序集。最后当工作流启动时,就会加载该程序集并调用其中的方法。


以上过程发生在:System.Workflow.ComponentModel.dll的 WorkflowCompiler.Compile() 函数中。


但是由于 CodeDom 的注入问题,而 Workflow 在编译时又没有检查,导致了通过工作流进行代码注入。

payload 如下:

<SequentialWorkflowActivity x:Class="MyWorkflow" x:Name="foobar" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">  <CallExternalMethodActivity x:Name="codeActivity1" MethodName='test1' InterfaceType='System.String);}Object/**/test2=System.Diagnostics.Process.Start("cmd.exe","/c calc");private/**/void/**/foobar(){//' /></SequentialWorkflowActivity>


InterfaceType 属性指定的接口类型,在构造 CodeDom 图时被用作 CodeTypeOfExpression 的参数,即被传递给 typeof() 语句。


通过对 InterfaceType 注入可以实现代码执行。


补 丁

在进行编译前,调用 CodeGenerator.ValidateIdentifiers(CodeObject) 方法来检查 CodeDom 图中的标识符是否符合语言要求。


CodeGenerator.ValidateIdentifiers() 是 CodeDom 提供的一个检查方法。


补丁是修补在 System.Workflow.ComponentModel.dll 中的,所以这个用于编译工作流的公共组件是已经被修复了。那还会有人/组件 这样使用 CodeDom 吗?


CVE-2023-24955
2023 P2O SharePoint RCE 漏洞

下面是 SharePoint 的补丁比较内容,红色为新加代码:

CodeDom漏洞模式与SharePoint RCE


经过前面的学习,可以看到一些关键字:CodeDom、IsValidIdentifier ,NamespaceName。


Namespace 也是标识符,所以在 CodeDom 中也是存在注入风险的,这里加上了 IsValidIdentifier 似乎表明这里可以被注入, proxyNamespaceName 可以被用户控制吗?要如何触发该位置?


寻找触发路径

补丁在  Microsoft.SharePoint.BusinessData.SystemSpecific.Wcf 命名空间下的 WcfProxyGenerator.GenerateProxyAssembly() 方法附近。该命名空间属于  Microsoft Business Connectivity Services (BCS)  组件。


通过BCS,用户可以在SharePoint中创建外部内容类型(External Content Type),这些内容类型定义了与外部数据源的连接和访问方式。使用这些外部内容类型,用户可以创建外部列表(External List)、外部数据列(External Data Column)和外部内容网站(External Content Site),从而实现对外部数据的展示和操作。


GenerateProxyAssembly、外部内容类型、连接外部数据源、Wcf Service,结合这些关键词,已经可以理清漏洞触发的原因:


  1. 定义一个 恶意的 Wcf Service;
  2. 在 SharePoint 里创建外部数据源,并指向我们的 恶意 Wcf Service;
  3. 在 SharePoint 里定义一个外部列表,并与我们的外部数据源绑定;
  4请求外部列表,SharePoint 就会请求我们的 Wcf Service,也意味着 SharePoint 会通过 GenerateProxyAssembly 来编译出一个 客户端代理程序集


CodeDom漏洞模式与SharePoint RCE


在创建 wcf 外部数据源的时候,很明显可以看到 “代理命名空间” 这个可控字段,实际证明这里就是注入点。


总 结

CodeDom 在生成源码和编译时,不会自己检查标识符里是否含有违法字符,而是提供了 Validate 方法由开发者调用以进行检查。不了解 CodeDom 安全问题的开发者很可能会忽略这个检查。


CodeDom 机制常被用于即时代码编译(wcf客户端),或者给用户提供一个有限的可编程功能(workflow 编程)。


在审计类似功能时可检查是否使用了 CodeDom,以及是否使用了 CodeGenerator.ValidateIdentifiers() 或 CodeDomProvider.IsValidIdentifier()  进行安全防护。






【版权说明】

本作品著作权归HuanGMz所有

未经作者同意,不得转载


CodeDom漏洞模式与SharePoint RCE
HuanGMz

天工实验室安全研究员

专注于二进制安全、.NET安全


CodeDom漏洞模式与SharePoint RCE

原文始发于微信公众号(破壳平台):CodeDom漏洞模式与SharePoint RCE

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月12日02:27:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CodeDom漏洞模式与SharePoint RCEhttp://cn-sec.com/archives/2275441.html

发表评论

匿名网友 填写信息