CVE-2022-34747 ZyXel NAS设备格式化字符串漏洞分析与复现
2022-9-13 15:35:27 Author: mp.weixin.qq.com(查看原文) 阅读量:3 收藏

一、漏洞信息

9月6日,网络设备制造商ZyXel发布安全通告,称ZyXel NAS产品中的某个特定二进制程序中存在一个格式化字符串漏洞,可导致攻击者通过精心构造的UDP数据包实现越权远程代码执行。该漏洞会影响其产品NAS326、NAS540和NAS542。漏洞被追踪为CVE-2022-34747,CVSS评分为9.8。

ZyXel已经以固件更新的形式发布了受影响设备的安全更新。

受影响设备和版本:

设备型号影响版本安全版本
NAS326<= V5.21(AAZF.11)C0V5.21(AAZF.12)C0
NAS540<= V5.21(AATB.8)C0V5.21(AATB.9)C0
NAS542<= V5.21(ABAG.8)C0V5.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数据包实现,利用格式化字符串漏洞控制程序执行恶意命令,命令执行完后,程序大概率因为栈不平衡而崩溃,所以是一次性利用。

由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,且听安全团队及文章作者不为此承担任何责任。

点关注,不迷路!


文章来源: https://mp.weixin.qq.com/s?__biz=Mzg3MTU0MjkwNw==&mid=2247491481&idx=1&sn=8865b32e49a41b456eb8010dd27a04bb&chksm=cefda68df98a2f9b219be69fdd8b2b611f0064402144bc07121dcc7f993964ae0b180edda8ce&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh