|DNS Server远程代码执行漏洞分析-SIGRed(CVE-2020-1350)
本文插图
简介
SIGRed(CVE-2020-1350)是Windows DNS服务器中一个可修复的严重漏洞 , 影响Windows server 2003至2019版本 , 并且可以由恶意DNS相应触发 。 由于服务在提升的特权(系统)中运行 , 如果成功利用 , 攻击者将被赋予域管理员权限 , 从而威胁整个公司基础设施 。
起因
我们的主要目标是找到一个可以让攻击者破坏Windows域环境的漏洞 , 最好是不需要经过身份验证的漏洞 。 大多数已发布的公开资料和漏洞利用都集中在微软的SMB(EternalBlue)和RDP(BlueKeep)协议实现上 , 因为这些目标都影响服务器 , 要获得域管理员权限 , 一种直接的方法是直接利用域控制器(Domain Controller) 。 因此 , 我们决定将我们的研究重点放在一个较少的攻击面上 , 它主要存在于Windows服务器和域控制器上 。 DNS客户端和DNS服务器一起为计算机和用户提供计域名解析服务 。
Windows DNS 概述
DNS主要在53端口上使用 UDP 协议来处理请求 。 DNS查询由来自客户端的单个UDP请求和来自服务器的单个UDP响应组成 。
除了将名称转换为IP地址外 , DNS还有其他用途 , 例如 , 邮件传输代理使用DNS找到发送电子邮件的最佳邮件服务器:MX记录提供了域和exchanger邮件之间的映射 , 这可以提供额外的容错和负载 , 在Wikipedia上可以找到可用的DNS记录类型及其相应用途的列表 。
但是 , 本文的目的不是要对DNS功能和历史进行长篇大论 , 因此我们建议您在此处阅读有关DNS的更多信息 。
您需要了解的内容:
- DNS通过UDP/TCP 53端口运行 。
- 一条DNS消息(响应/查询)在UDP中限制为512字节 , 在TCP中限制为65,535字节 。
- DNS本质上是分层的和分散的 , 这意味着 , 当DNS服务器不知道它接收到的查询的答复时 , 该查询将被转发到它上层的DNS服务器 , 在架构的顶部 , 全世界有13个根DNS服务器 。
- DNS客户端 – dnsapi.dll 负责DNS解析 。
- DNS服务器 – dns.exe负责在安装了DNS角色的Windows 服务器上响应DNS查询 。
准备环境
我们的攻击面主要有两种情况:
- 1.DNS服务器解析传入查询的方式中的漏洞 。
- 2.DNS服务器解析转发查询时相应的方法的漏洞 。
如上所述 , 转发查询是利用DNS来将不知道答复的查询转发到上层DNS服务器中 。 但是 , 大多数环境将其转发器配置为知名的DNS服务器 , 例如8.8.8.8(Google)或1.1.1.1(Cloudflare)或者至少是不受攻击者控制的服务器 。 这意味着即使我们在解析DNS响应时发现问题 , 也需要建立一个中间人来加以利用 。 显然 , 这无法达到要求 。
NS记录重用
NS代表“名称服务器” , 该记录表明哪个DNS服务器是该域的授权 , NS记录通常负责解析指定域的域名 , 一个域通常有多个NS记录 , 这些记录可以表明该域的主名称服务器和备份名称服务器 。
若要使目标Windows DNS服务器解析来自恶意DNS名称服务器的响应 , 请执行以下操作:
- 1.将我们域的(deadbeef.fun)NS记录配置为指向我们的恶意DNS服务器(ns1.41414141.club) 。
- 2.查询受害Windows DNS服务器的NS记录deadbeef.fun 。
- 3.受害DNS目前不知道该查询的答复 , 将查询转发到其上层的DNS服务器(8.8.8.8) 。
- 4.权威服务器(8.8.8.8)知道答复 , 并响应的NameServer deadbeef.fun为ns1.41414141.club 。
- 5.受害Windows DNS服务器处理并缓存此响应 。
- 6.下次我们查询域名deadbeef.fun , 目标Windows DNS服务器也会查询ns1.41414141.club并响应 , 因为它是该域的NameServer 。
本文插图
漏洞– CVE-2020-1350
函数:dns.exe!SigWireRead漏洞类型:整数溢出导致基于堆的缓冲区溢出
dns.exe 为每种受支持的响应类型实现解析功能 。
本文插图
Wire_CreateRecordFromWire:RRWireReadTable被传递给RR_DispatchFunctionForType函数处理
本文插图
RRWireReadTable支持响应的类型
支持的响应类型之一用于SIG查询 。 根据Wikipedia:”SIG查询是在SIG(0) (RFC 2931)和TKEY (RFC 2930)中使用的签名记录,RFC 3755指定RRSIG作为在DNSSEC内使用的SIG的替代品 。 ”
让我们来看一下由Cutter生成的反汇编:dns.exe!SigWireRead SIG响应类型的处理函数:
本文插图
RR_AllocateEx通过以下公式计算传递给第一个参数:
[Name_PacketNameToCountNameEx result] + [0x14] + [The Signature field’s length (rdi–rax)]签名字段的大小可能会有所不同 , 因为它是SIG响应的主要payload 。
本文插图
R根据RFC 2535的SIG资源记录的结构
正如你在下面的图片中看到 , RR_AllocateEx希望其参数在16位寄存器中传递 , 因为它只使用rdx的dx部分和rcx的cx部分 。
这意味着 , 如果我们可以使上述公式输出的结果大于65,535字节(16位整数的最大值) , 则会出现整数溢出 , 从而导致分配比预期的小得多 , 这可能会导致基于堆的缓冲区溢出 。
本文插图
比较方便的是 , 这个分配的内存地址作为memcpy的目标缓冲区传递 , 从而导致基于堆的缓冲区溢出 。
本文插图
从中分配的缓冲区RR_AllocateEx被传递到中memcpy
总而言之 , 通过发送一个包含较大(大于64KB) SIG记录的DNS响应 , 我们可能会导致一个基于堆的缓冲区溢出 , 溢出大约64KB 。
触发漏洞
现在我们能够让受害者DNS服务器查询我们的DNS服务器 , 我们已经有效地将其转换为客户端 。 我们可以让受害者DNS服务器询问我们的恶意DNS服务器特定类型的查询 , 并分别用匹配的恶意响应请求 。
我们认为触发此漏洞所需要做的只是使受害DNS服务器向我们查询SIG记录 , 然后用一个很长的签名(length >= 64KB)回复它一个SIG响应 。 我们失望地发现DNS超过UDP的大小限制为512字节(如果服务器支持EDNS0 , 则为4,096字节) 。 无论如何 , 这都不足以触发漏洞 。
但是 , 如果有合理的理由让服务器发送一个大于4,096字节的响应 , 会发生什么呢?例如 , 一个很长的TXT响应或可以解析为多个IP地址的主机名 。
DNS截断-但等一下 , 还有更多!
根据DNS RFC 5966:“在没有EDNS0(DNS 0扩展机制)的情况下 , 任何需要发送超过512字节限制的UDP响应的DNS服务器的正常行为是截断响应 , 使其符合该限制 , 然后在响应头中设置TC标志 。 当客户端收到这样的响应时 , 它将TC标志作为一个指示 , 指示它应该通过TCP重试 。 ”
因此 , 我们可以在响应中设置TC(truncation) 标志 , 这将导致目标Windows DNS服务器启动与恶意NameServer新的TCP连接 , 并且我们可以传递大于4,096字节的消息 。 但是要大多少?
根据DNS RFC 7766:“DNS客户端和服务器应同时2个八位字节长度字段和该长度字段描述的消息传递到TCP层” , (例如 , 在单个的“write”系统调用中) , 以使所有数据更有可能在单个TCP段中传输 。
由于邮件的前两个字节表示其长度 , 因此TCP上DNS中邮件的最大大小表示为16位 , 因此限制为64KB 。
本文插图
但是 , 即使长度为65,535的消息也不足以触发漏洞 , 因为消息长度包括标头和原始查询 。 在计算传递给RR_AllocateEx的大小时 , 没有考虑这个开销 。
DNS指针压缩–少即是多
让我们再来看一个合法的DNS响应(为方便起见 , 我们选择了A类型的响应) 。
本文插图
dig research.checkpoint.com A @8.8.8.8的DNS响应 , 如Wireshark所示
您可以看到Wireshark对research.checkpoint.com的答复名称字段中的0xc00c字节进行了计算 。 问题是 , 为什么?
为了尽可能多的将信息压缩到512字节中 , 可以(通常必须)压缩DNS名称……在这种情况下 , 答复的DNS名称编码为0xc0 0x0c 。 c0部分设置了两个最高有效位 , 表示下面的6+8位是指向消息前面某个地方的指针 。 在本例中 , 这指向数据包内的位置12(=0x0c)它紧跟在DNS header之后 。
与数据包开头的偏移量0x0c(12)是什么?是research.checkpoint.com啊!
在这种压缩形式中 , 指针指向编码字符串的开头 。 在DNS中 , 字符串被编码为(<size> <value>)链 。
本文插图
因此 , 我们可以使用“magic”字节0xc0从数据包中引用字符串 。 让我们再次检查计算传递给RR_AllocateEx的大小的公式:
[Name_PacketNameToCountNameEx result] + [0x14] + [The Signature field’s length (rdi–rax)]逆向Name_PacketNameToCountNameEx确认我们上面描述的行为 。 Name_PacketNameToCountNameEx的目的是计算名称字段的大小 , 并将指针压缩考虑在内 。 当仅用两个字节表示分配时 , 拥有一个允许我们大量增加分配大小的原语正是我们所需要的 。
因此 , 我们可以在SIG签名者的名称字段中使用指针压缩 。 但是 , 仅指定0xc00c为签名者的名称不会引起溢出 , 因为查询的域名已经存在查询中 , 并且从分配的值中减去开销大小 。 但是0xc00d呢?我们唯一需要满足的约束是编码字符串是有效的(以结尾0x0000) , 并且我们可以轻松做到这一点 , 因为我们有一个没有任何字符约束的字段-签名值(signature value) 。 对于域名41414141.fun , 0xc00d指向域名的第一个字符(“4”) 。 然后将此字符的序号值用作未压缩字符串的大小(“4”表示值0x34(52)) 。 该未压缩字符串的大小加上我们可以在Signature字段中容纳的最大数据量(最多65,535 , 具体取决于原始查询)的汇总将造成大于65,535字节的值 , 从而导致溢出!
让我们用WinDBG附加到dns.exe:
本文插图
我们crashed了!
虽然我们似乎崩溃了 , 因为我们试图写值到未映射的内存 , 堆的形状可以允许我们重写一些有意义的值 。
以前对dns.exe的利用可以在线获得 。 例如:A deeper look at ms11-058 。
从浏览器触发
我们知道这个bug可以由局域网环境中的恶意参与者触发 。 然而 , 我们想看看这个bug是否可以在没有局域网访问的情况下被远程触发 。
在HTTP内私有DNS
到目前为止 , 您应该知道DNS可以通过TCP传输 , 并且Windows DNS Server支持此连接类型 。 您还应该熟悉DNS在TCP上的结构 , 但以防万一 , 这里是一个快速回顾:
本文插图
考虑以下标准HTTP payload:
000050 4f 53 54 20 2f 70 77 6e 20 48 54 54 50 2f 31POST /pwn HTTP/1 00102e 31 0d 0a 41 63 63 65 70 74 3a 20 2a 2f 2a 0d.1..Accept: */*. 00200a 52 65 66 65 72 65 72 3a 20 68 74 74 70 3a 2f.Referer: http:/即使这是一个HTTP payload , 将它发送到53端口上的目标DNS服务器会导致Windows DNS服务器将此payload解释为一个DNS查询 。 它使用以下结构来实现这一点:
000050 4f 53 54 20 2f 70 77 6e 20 48 54 54 50 2f 31POST /pwn HTTP/1 00102e 31 0d 0a 41 63 63 65 70 74 3a 20 2a 2f 2a 0d.1..Accept: */*. 00200a 52 65 66 65 72 65 72 3a 20 68 74 74 70 3a 2f.Referer: http:/ Message Length: 20559 (0x504f) Transaction ID: 0x5354 Flags: 0x202f Questions: 28791 (0x7077) Answer RRs: 28192 (0x6e20) Authority RRs: 18516 (0x4854) Additional RRs: 21584 (0x5450) Queries: [...]幸运的是 , Windows DNS服务器同时支持RFC 7766的“连接重用(Connection Reuse)”和“Pipelining”,这意味着我们可以在一个TCP会话中发出多个查询 , 而无需等待应答 。
为什么这很重要?
当受害者访问我们控制的网站时 , 我们可以使用基本的JavaScript从浏览器向DNS服务器发出POST请求 。 但是如上所示 , POST请求的解释方式不是我们真正控制的 。
但是 , 我们可以利用“连接重用”和“Pipelining”特性 , 通过使用二进制数据向目标DNS服务器(https://target-dns:53/)发送HTTP POST请求 , 在POST数据中包含另一个“私藏(smuggled)”的DNS查询 , 分别进行查询 。
我们的HTTP有payload包括以下内容:
- HTTP请求头 , 我们不控制(User-Agent , Referer等) 。
- “Padding(填充)” , 以便第一个DNS查询在POST数据内具有适当的长度(0x504f)
- 我们“私藏”的DNS查询里面的POST数据 。
本文插图
在单个TCP会话中的多个查询 , 如Wireshark所示
实际上 , 大多数流行的浏览器(例如Google Chrome和Mozilla Firefox)都不允许HTTP请求访问53端口 , 因此只能在少数Web浏览器中利用此bug , 包括Internet Explorer和Microsoft Edge(基于非Chromium) 。
变体分析
出现这个漏洞的主要原因是RR_AllocateEx API期望size参数为16位 。 通常可以安全地假设单个DNS消息的大小不超过64KB , 因此这种行为不应该出现问题 。 但是 , 正如我们刚才看到的 , 在计算缓冲区大小时考虑Name_PacketNameToCountNameEx的结果时 , 这种假设是错误的 。 这是因为Name_PacketNameToCountNameEx函数计算未压缩名称的有效大小 , 而不是它在表中表示该名称所需的字节数 。
要查找此漏洞的其他变体 , 我们需要找到一个满足以下条件的函数:
- 调用RR_AllocateEx时 , 大小是可变的(不是常数值) 。
- 有一个对Name_PacketNameToCountNameEx的调用 , 它的结果用于计算传递给RR_AllocateEx的大小 。
- 传递给RR_AllocateEx的值是使用16位或16位以上的值计算的 。
RESOURCE_RECORD* NsecWireRead(PARSED_WIRE_RECORD *pParsedWireRecord, DNS_PACKET *pPacket, BYTE *pRecordData, WORD wRecordDataLength) { DNS_RESOURCE_RECORD *pResourceRecord; unsigned BYTE *pCurrentPos; unsigned int dwRemainingDataLength; unsigned int dwBytesRead; unsigned int dwAllocationSize; DNS_COUNT_NAME countName; pResourceRecord = NULL; pCurrentPos = Name_PacketNameToCountNameEx(&countName, pPacket, pRecordData, pRecordData + wRecordDataLength, 0); if (pCurrentPos) { if (pCurrentPos >= pRecordData// = (unsigned int)(pCurrentPos - pRecordData)) // = dwBytesRead// data, &countName); memcpy(&pResourceRecord->data + pResourceRecord->data->bOffset + 2, pCurrentPos, dwRemainingDataLength); } } } } return pResourceRecord; }如您所见 , 这个函数包含许多安全检查 。 其中一项是16位溢出检查 , 它可以防止我们在该函数中的漏洞变体 。 我们还想指出 , 这个函数比dns中的函数有更多的安全检查 。 这让我们怀疑这个漏洞是否已经被注意到和修复 , 但只是在那个特定的函数 。
如上所述 , Microsoft在两个不同的模块中实现了DNS客户端和DNS服务器 。 虽然我们的漏洞确实存在于DNS服务器中 , 但我们想看看它是否也存在于DNS客户端中 。
本文插图
看来 , 不像dns.exe!SigWireRead, dnsapi.dll!Sig_RecordRead 它确实验证了Sig_RecordRead+D0传递给dnsapi.dll!Dns_AllocateRecordEx的值小于0xFFFF字节 , 从而防止了溢出 。
这个漏洞在dnsapi.dll中不存在 。 并且两个模块之间的命名约定不同 , 这使我们相信Microsoft管理DNS服务器和DNS客户端的两个完全不同的代码库 , 并且不会同步它们之间的bug补丁 。
Exploitation Plan
根据Microsoft的要求 , 我们决定保留有关漏洞利用原语的信息 , 以便给用户足够的时间来修补他们的DNS服务器 。 相反 , 我们将讨论应用于Windows Server 2012R2的利用方案 。 然而 , 我们相信这个方案也应该适用于其他版本的Windows Server 。
该dns.exe二进制文件是用控制流保护(CFG)编译的 , 这意味着在内存中重写函数指针的传统方法不足以利用这个漏洞 。 如果这个二进制文件不是用CFG编译的 , 利用这个漏洞将是非常简单的 , 因为很早就我们遇到了以下崩溃:
本文插图
如您所见 , 我们在ntdll!LdrpValidateUserCallTarget上崩溃了 。 这个函数负责作为CFG的一部分验证目标函数指针 。 我们可以看到被验证的指针(rcx)是完全可控的 , 这意味着我们成功地覆盖了某个函数指针 。 我们看到崩溃的原因是函数指针被用作全局位图表的索引 , 每个地址有“allowed” / “disallowed”位 , 而我们的任意地址导致从表本身的未映射page读取 。
要利用这个漏洞在绕过CFG的同时实现远程代码执行 , 我们需要找到提供以下功能的原因:write- where(精确地覆盖栈上的返回地址)和infoleak(泄漏内存地址) 。
信息泄漏
为了实现Infoleak(信息泄露)原语 , 我们利用溢出破坏了DNS资源记录的元数据 , 而它仍在缓存中 。 然后 , 当再次从缓存中查询时 , 我们可以泄漏相邻的堆内存 。
WinDNS的堆管理器
WinDNS使用Mem_nualloc函数动态分配内存 。 此函数管理自己的内存池 , 以用作高效缓存 。 对于不同的分配大小 , 有4个内存池(0x50、0x68、0x88、0xA0) 。 如果请求的分配大小大于0xA0字节 , 则默认为HeapAlloc , 它使用本地Windows堆 。 堆管理器为内存池header分配额外的0x10字节 , 其中包含元数据 , 包括缓冲区的类型(已分配/空闲)、指向下一个可用内存块的指针、用于调试检查的cookie等 。 堆管理器以单链表的方式实现它的分配列表 , 这意味着块的分配顺序与它们被释放的顺序相反(LIFO) 。
Write-What-Where
为了实现“Write-What-Where”原语 , 我们通过破坏chunk的header(元数据)来攻击WinDNS堆管理器 , 实际上就是破坏freelist 。
在freelist被破坏后 , 下次我们尝试分配大小合适的任何内容时 , 内存分配器都会为我们分配我们选择的内存区域作为”Malloc-Where”原语 。
为了绕过CFG , 我们希望该内存区域位于堆栈上 , 由于信息泄漏 , 我们希望知道它的位置 。 一旦我们在堆栈上有了写能力 , 我们就可以将返回地址改写为我们想要执行的地址 , 有效地劫持执行流程 。
必须指出的是 , 默认情况下 , DNS服务在前3次崩溃时重新启动 , 这增加了成功利用的机会 。
总结
这个高度的漏洞已经被微软承认 , 并为其分配CVE-2020-1350 。
我们相信这个漏洞被利用的可能性是很高的 , 因为我们在内部找到了利用这个漏洞所需的所有原语 。 由于时间限制 , 我们没有继续利用这个漏洞 , 但是我们相信其他的攻击者能够利用它 。 成功利用此漏洞将产生严重影响 , 因为通常可以找到未修补的Windows域环境 , 尤其是域控制器 。 此外 , 甚至一些互联网服务提供商(ISP)可能将其公共DNS服务器设置为WinDNS 。
我们建议用户修补受影响的Windows DNS服务器 , 以防止利用此漏洞 。
作为临时的解决方法 , 在打补丁之前 , 建议将DNS消息(通过TCP)的最大长度设置为0xFF00 , 这样可以消除此漏洞 。 您可以通过执行以下命令来实现:
reg add ''HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesDNSParameters'' /v ''TcpReceivePacketSize'' /t REG_DWORD /d 0xFF00 /f net stop DNS && net start DNS披露时间表
- 2020年5月19日 – 向Microsoft提交的初步报告 。
- 2020年6月18日 – Microsoft发布了此漏洞的CVE-2020-1350 。
- 2020年7月9日 – Microsoft承认该问题是一个可修复的严重漏洞 , CVSS评分为10.0 。
- 2020年7月14日 – Microsoft发布了补丁 。
- https://en.wikipedia.org/wiki/Domain_Name_System
- https://blog.skullsecurity.org/2011/a-deeper-look-at-ms11-058
- https://know.bishopfox.com/blog/2017/10/a-bug-has-no-name-multiple-heap-buffer-overflows-in-the-windows-dns-client
- https://powerdns.org/hello-dns/basic.md.html
- https://www.cloudflare.com/learning/dns/what-is-dns/
- https://tools.ietf.org/html/rfc7766
- https://tools.ietf.org/html/rfc5966
- https://tools.ietf.org/html/rfc2535
【|DNS Server远程代码执行漏洞分析-SIGRed(CVE-2020-1350)】本文翻译自 research.checkpoint.com ,原文链接。 如若转载请注明出处 。
推荐阅读
- |如何解决网络连接配置和dns异常?
- 华为|华为企业智慧屏IdeaHub远程诊疗解决方案:多场景专业支持 实现普惠远程诊疗
- 最新版 Ubuntu Linux 手把手下载安装教程 for Server
- 吊打Teamviewer的免费远程控制软件:ToDesk来了
- [魔兽世界 怀旧服 wow 安其拉 TAQ 治疗 祈福]|魔兽世界怀旧服别玩这职业,整个60年代,没一件称手的远程武器
- 胎心|【院内胎监放心、院外胎监安心】远程胎心监护,可以带回家的呵护
- 马医专家已对下海救人两匹马远程会诊:医治困难,有生命危险
- 下水救人|马医专家已对下海救人两匹马远程会诊:医治困难,有生命危险
- 环球网|具备隐身能力!俄罗斯“猎人”远程察打一体无人机有望明年服役
- 可隐身!俄罗斯“猎人”远程无人机有望明年服役
