一、漏洞信息
9月6日,网络设备制造商ZyXel发布安全通告,称ZyXel NAS产品中的某个特定二进制程序中存在一个格式化字符串漏洞,可导致攻击者通过精心构造的UDP数据包实现越权远程代码执行。该漏洞会影响其产品NAS326、NAS540和NAS542。漏洞被追踪为CVE-2022-34747,CVSS评分为9.8。
ZyXel已经以固件更新的形式发布了受影响设备的安全更新。
受影响设备和版本:
设备型号
影响版本
安全版本
NAS326
<= V5.21(AAZF.11)C0
V5.21(AAZF.12)C0
NAS540
<= V5.21(AATB.8)C0
V5.21(AATB.9)C0
NAS542
<= V5.21(ABAG.8)C0
V5.21(ABAG.9)C0
二、漏洞分析
1. 定位漏洞文件
首先从ZyXel官网下载NAS420设备的固件:
NAS540_V5.21(AATB.8)C0.zip是受漏洞影响的版本,下文简称8版,NAS540_V5.21(AATB.9)C0.zip是不受漏洞影响的安全版本,下文简称9版。
将9版压缩包解压后,查看Firmware Release Note信息(521AATB9C0.pdf文件),其中有固件更新记录。
更新记录显示,9版修复了格式化字符串漏洞,并且移除了NAS starter utility功能。
将8版和9版固件分别用binwalk解开,用目录比较工具比较文件变化。以工具Beyond Compare 4为例:
通过比较,发现9版的更新基本上就是移除了NAS starter utility功能模块nsuagent程序。
2. 定位漏洞点
用IDA pro加载nsuagent程序,因为格式化字符串漏洞是由c语言的printf函数族使用不当触发的,所以从nsuagent程序的导入表中的printf函数族出发,找漏洞点。
通过逐项查找,发现仅有fprintf函数使用不正确,存在格式化字符串漏洞的可能性。
进一步分析发现,nsa_fprintf函数对fprintf函数进行了封装,固定了其中的输出文件,其目的是将nsuagent程序的执行日志,记录在/tmp目录下的nsu_progress文件中。nsa_fprintf也是一个可变参数函数,其中s变量作为格式化串,理应不受用户输入控制,但程序中s变量却由参数varg_r1和varg_r2拼接而成,一旦nsa_fprintf的可变参数可控,则可触发格式化字符串漏洞。
至此,漏洞点确定。
三、漏洞复现
**1. 调试环境搭建
**
因为手头没有ZyXel NAS设备,所以只能用QEMU模拟器仿真。
首先尝试用firmware-analysis-toolkit进行全系统仿真,没成功!
尝试用qemu-user进行仿真,因为nsuagent程序是arm指令集32位小端程序,所以使用qemu-arm。
仿真过程中需要用到很多so库,在8版固件解开的目录中一一找到,并建立好软链接。还需要两个pem文件,把它们找到,放在相应目录下。
直接执行命令为:qemu-arm -L ./ nsuagent
调试执行命令为:qemu-arm -g 12345 -L ./ nsuagent
-L用于指定加载库的根目录,有点类似chroot命令。
调试的话可以用gdb,也可以用IDA pro。
2. 漏洞利用
通过前面有关漏洞点的介绍可知,只要username和password中含有%p %x %n之类的字符串,就可以触发nsa_printf函数中的格式化字符串漏洞。利用过程主要是构造Udp数据包,在username和password字段构造格式化字符串。
先以一个简单例子讲解格式化字符串的利用原理
%0x74c%9$n
打印0x74个字节,将打印的字节数目作为值写入距离格式化字符串偏移为9的栈上指针指向的区域。
利用格式化字符串进行任意地址写,要求栈上存在指向这些地址的指针,笔者这里构造username字段时,先填入所有需要写入的地址,再在后面加上计算好偏移的格式化字符串。
因为程序开启了NX保护,所以不能通过写入shellcode然后跳转shellcode来进行利用。为了绕过NX,笔者这里采用构造ROP链的方式来执行代码。使用ROPgadget找到下面两条指令,后续通过这两条指令,将参数传入R0寄存器然后调用system函数
`0x0001292c : pop {r4, pc}``0x00012b10 : mov r0, r4 ; pop {r4, pc}`
具体需要实现的栈布局如下:
`---------------------------------------``0x0001292c : pop {r4, pc} <-------retaddr``---------------------------------------``.......``---------------------------------------``cmdstr <------ SP``---------------------------------------``0x00012b10 : mov r0, r4 ; pop {r4, pc}``---------------------------------------``junk``---------------------------------------``system``---------------------------------------``junk``---------------------------------------``retaddr``---------------------------------------`
要实现上面的栈布局,需要将函数返回地址改成0x0001292c,然后将此时的栈顶向下的4块区域写入相应的地址。
又因为在进入漏洞函数之前,username和password字段都是从缓冲区中的数据包中提取的,而提取的过程对这两个字段的长度进行了限制,所以无法通过一次利用格式化字符串漏洞完成上面的栈布局。这里笔者采取了迂回的方式,先通过一次利用,将函数返回地址改成提取完username之后的指令地址(在漏洞函数之前),同时用完整的字符串地址,覆盖存放截断后的username字符串的地址,这样就会再一次进入漏洞函数,此时的字符串没有经过截断。最终成功执行whoami命令。
**存在的问题
**
以上利用过程都是建立在栈地址可知的情况下,因为漏洞程序只接收udp请求,回的包都是广播包,所以没办法得到回显,实际不好利用。除此之外,上面构造的ROP链执行了三次pop {r4,pc},这样会造成回到真正的返回地址时,栈空间不平衡,从而导致程序崩溃。这里笔者没有找到合适的指令,让ROP链运行完后栈顶也能得到恢复。综上所述,本文研究的利用方式,只能在已知栈地址的情况下,发送一个精心构造UDP数据包实现,利用格式化字符串漏洞控制程序执行恶意命令,命令执行完后,程序大概率因为栈不平衡而崩溃,所以是一次性利用。
由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,且听安全团队及文章作者不为此承担任何责任。
点关注,不迷路!