
CVE-2017-9822的详细分析与概念验证利用代码,该漏洞为DotNetNuke CMS中的XXE/不安全反序列化漏洞,可通过Cookie篡改实现远程代码执行。
DotNetNuke(通常缩写为 DNN)是一个基于微软 ASP.NET 技术的 CMS(内容管理系统)平台 和 Web应用程序框架。
受影响产品: DotNetNuke(DNN Platform)——一个流行的 .NET CMS/门户。
发布日期: 2017年7月。
严重程度: 严重(CVSS ~9.8)。
漏洞类型: XML外部实体(XXE)/ 不安全反序列化 → 远程代码执行(RCE)。
影响范围: 9.1.1 之前的版本,可通过 cookie 远程执行代码。
这里我使用的是 Windows 10 来搭建和调试程序。我安装的版本是 9.1.0,你可以参考 此链接 的安装方法。完成后效果如下:


根据我阅读的报告,此漏洞位于 DotNetNetNuke 处理 cookie 的位置。
DNN 对 cookie DNNPersonalization 使用了不安全的反序列化方法。

.dll 或 .exe)的源代码。可以从 这里 安装。我们需要下载两个版本用于调试。
DotNetNuke.dll,选择 Edit Assembly Attributes (C#)。
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default |
DebuggableAttribute.DebuggingModes.DisableOptimizations |
DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints |
DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]

然后保存。
Attach to Process。
w3wp.exe。
选择 w3wp.exe 的原因:
w3wp.exe = IIS 工作进程。
它是 IIS 中 应用程序池 的执行进程。
当 HTTP 请求到达网站时,IIS 会创建或重用 w3wp.exe 进程来处理该请求(运行 ASP.NET 代码、处理模块、中间件、数据库连接等)。
每个应用程序池可以有一个或多个 w3wp.exe 进程,具体取决于配置(web garden、回收等)。
接下来,选择 Debug -> Window -> Modules。

完成后将显示模块列表,右键点击任意模块,选择 Open All Modules。

最后将显示所有与 DotNetNuke 相关的程序集。

DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int)。
此函数用于 加载用户的个性化(profile)数据 在 DNN 门户中。
如果用户已登录 → 从 数据库 + 缓存 获取 profile。
如果是匿名用户(未登录)→ 从 cookie DNNPersonalization 获取 profile。
这里我们应重点关注 DNNPersonalization。
如果 userId 无效(匿名用户)。
检查请求中是否有 DNNPersonalization cookie。
如果有 → 从该 cookie 中获取 XML 值。
我们将向网站发送一个 404 请求,并使用任意 DNNPersonalization,然后使用 dnSpy 在 DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int) 处设置断点,即可调试。


PortalSettings 类。
值得注意的地方是,这里使用 if 条件来检查当前请求是否已通过身份验证(IsAuthenticated)。
而我们发送的请求是 404 -> 未通过身份验证。
继续在调用栈部分,我们关注 Handle404OrException。

context.User 是否为 null,如果是,则将 context.User 设置为当前线程用户。

可以看到在 Handle404OrException 中,变量 IsAuthenticated 现在是 true,且用户正是 IIS 服务器的用户,因此该请求将作为已验证用户执行。
问题原因在于这段代码:
else if (transfer)
{
if (context.User == null)
{
context.User = Thread.CurrentPrincipal;
}
response.TrySkipIisCustomErrors = true;
IHttpHandler handler = new CDefault();
context.Handler = handler;
server.Transfer("~/" + text, true);
}
如果 context.User 不存在 → 赋值为 Thread.CurrentPrincipal(即当前线程的标识)。
这使得后续处理时请求拥有 用户/角色 信息。
=> 当我们向带有变量 DNNPersonalization 的 cookie 中传入任意内容时,它将以普通用户身份执行。
仍然在 DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int) 中。
我们看到变量 text 从 cookie 值接收,然后作为输入传递给 Globals.DeserializeHashTableXml()。

Globals.DeserializeHashTableXml()。
函数 DeserializeHashTableXml 的职责是:
接收一个 XML 字符串(Source)。
解析该 XML 字符串,将其 转换为 Hashtable 对象。
在解析过程中,它调用 XmlUtils.DeSerializeHashtable,参数为 "profile",用于指定 XML 的根节点。
进入 XmlUtils.DeSerializeHashtable 内部,我们看到它的处理方式。

函数 DeSerializeHashtable 接收 XML 字符串并将其转换为 Hashtable。对于每个 <item> 节点,函数会:
获取 key 作为键。
获取 type 并调用 Type.GetType(type) 来确定数据类型。
使用 XmlSerializer.Deserialize 将 XML 内容转换为实际对象。
添加到 Hashtable。
👉 问题:因为 type 和 XML 内容完全由用户控制(来自 cookie DNNPersonalization)。
基于 XmlUtils#DeSerializeHashtable 这个漏洞点,创建一个类似的序列化和反序列化对象的程序:
using System.Xml;
using System.Diagnostics;
using System.Xml.Serialization;
namespace example
{
public class Test
{
private string _name;
public string name
{
get { return _name; }
set { this._name = value; execCMD(); }
}
private void execCMD()
{
Process process = new Process();
process.StartInfo.FileName = this._name;
process.Start();
process.Dispose(); // close
}
}
public class Program
{
private static string fileFolder = "D:\\lab\\csharp\\DNN\\example\\serialization\\";
public static void Serialize(Object obj) // method xml serialize arbitrary object
{
// create xml root element
XmlDocument xmlDocument = new XmlDocument();
XmlElement xmlElementRoot = xmlDocument.CreateElement("profile");
xmlDocument.AppendChild(xmlElementRoot);
// create child node item with attribute type containing the object type name
XmlElement xmlElementItem = xmlDocument.CreateElement("item");
xmlElementItem.SetAttribute("type", obj.GetType().AssemblyQualifiedName);
// serialize obj into xmlDocumentObj