Webmin任意命令执行漏洞(CVE-2019-12840)的分析
前言
一开始没有注意到exploit-db上已经有exp。后来自己复现成功后,在写文档的时候发现了有人已经编写好msf的exp了。就去看了下EXP,网上EXP还是有问题的,后面会说为什么。
一开始看到这个漏洞为高危,感觉应该是很好利用的,后面分析了代码后发现虽然是RCE,要求权限也不高,但是要命令里不能出现斜杠(/),这极大的提高了反弹shell的难度。目前基本只能靠脚本语言反弹shell,比如python和ruby。这个漏洞最后可以说有两个exp。
分析过程
信息获取
根据CVE描述 “In Webmin through 1.910, any user authorized to the “Package Updates” module can execute arbitrary commands with root privileges via the data parameter to update.cgi.”我们可以知道漏洞点在 Package Updates 的 update.cgi 上
代码分析
我们在ubuntu上安装webmin 1.910,我们直接查看/usr/share/webmin/package-updates/update.cgi (ubuntu软件包一般默认安装在/usr/share 目录下)
Webmin的代码还是很规范的,我们可以直接根据注释定为到存在问题的功能点,因为是文件名为update.cgi且是软件升级功能,所以我们猜测应该是软件更新的时候,传入的参数有问题。

通过代码我们可以看到得到以下信息:
- 首先获取参数u的值赋值给@pkgs,然后判断是否为空。
- 判断是新装软件包,还是升级原有软件包
- 以斜杠为分隔符,第一个子串传入变量$p,第二个子串传入变量$s,多余的子串会被忽略
- 将list_package_operations函数的返回值赋值给变量@ops
- 然后根据@ops的是否为空来进行下一步
根据update.cgi文件头上require ‘./package-updates-lib.pl’; 我们打开package-updates-lib.pl。在405行,我们看到这个函数的作用是返回需要安装的软件包的所有依赖。

我猜测存在依赖需要安装的软件包需要二次确认,所以我们直接看@ops为假的情况。

我们可以看到更新方法有两种,一种是批量更新,一种是一个个更新。
我们分析一下批量更新的代码:
- 遍历变量@pkgs,以斜杠为分隔符,第一个子串传入变量$p,第二个子串传入变量$s,多余的子串会被忽略,然后将$p压入堆栈pkgnames中
- 调用package_install_multiple函数,传入pkgnames和pkgsystem
我们可以看到我们之前的原始数据包中的apt对应的是pkgsystem,所以exploit-db上面的exp并不是通用的,需要根据实际操作系统来调整。
我们看package_install_multiple函数的实现:
在package-updates-lib.pl的370行:

我们可以看到package_install_multiple将自己接收到的参数值传递给三个变量$names, $system, $install
而$names则是我们之前传入的pkgnames,然后将$namse参数传给&software::update_system_install函数,我们根据package-updates-lib.pl头部的调用找到../software/software-lib.pl

根据图片我们可以看到,这个文件上没有update_system_install,但是他开头掉用了”$config{‘package_system’}-lib.pl”;我们随便看一个,比如apt-lib.pl
我们打开software目录下的apt-lib.pl

从16行开始就是update_system_install的实现
我们可以看到将update_system_install的第一个参数直接传递给$update

我们可以看到,他是直接拼接到命令上,这就导致了命令注入漏洞,从而实现任意命令执行。
我们最后得出参数传递链如下:
可控输入参数$u -> update.cgi的$pkgs -> update.cgi的@pkgnames -> package-updates-lib.pl的package_install_multiple函数的第一个参数 -> package_install_multiple函数的$name -> ../software/apt-lib.pl 的update_system_install函数的第一个参数 -> update_system_install的$update -> $cmd -> 执行命令
为什么我前面说要看批量更新的代码呢,因为我看了单个更新的代码,如果走单个更新的代码的分支的话需要修改mode参数的值为new

我们可以看到$pkg如果最后为空就会执行 return,最后无法执行我们自定义命令。 那么$pkg需要满足以下三个条件中的任意一个:
1.在list_possible_updates(0)的返回结果里,即在可以升级的软件包里,在Ubuntu中就是apt list –upgradable的返回结果中
2在 list_available(0) 的返回结果里 ,即在可以安装的软件包里,在Ubuntu中就是apt list 的返回结果中
3.不满足条件1和条件2的情况下,$install 为真
我们看看我们调用package_install的参数

可以看到我们赋值给$install 的值是 $in{‘mode’} eq ‘new’ 也就是说得新装软件才能触发。
满足这些条件以后就跟批量更新一样调用 update_system_install 进行安装
复现图

我们可以看到 mode值update的情况下直接修改参数u 是不可以的




最后总结
- 当mode值为new的时候,可以直接修改参数u的值
- 当mode值不为new的时候,需要传递两个或以上的参数u触发批量更新机制
- 需要注意主机软件安装包系统apt还是yum等来修改参数u的值
- 执行的命令中不能含有斜杠(/)
EXP1
POST: https://url/package-updates/update.cgi
Data: mode=updates&search=&u=apt/apt&u=;${command};/apt&ok_top=Update+Selected+Packages
EXP2
POST: https://url/package-updates/update.cgi
Data: mode=new&search=&u=;${command};/apt&ok_top=Update+Selected+Packages
命令注入通常需要借助管道符闭合或者忽略原命令的执行结果,我用的;
假如不支持执行多条命令可以换成 |
参考链接
https://nvd.nist.gov/vuln/detail/CVE-2019-12840
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12840
https://www.cnvd.org.cn/flaw/show/CNVD-2019-19305
https://vuldb.com/?id.136530
https://www.exploit-db.com/exploits/46984
https://www.zerokeeper.com/experience/a-variety-of-environmental-rebound-shell-method.html
https://blog.csdn.net/l1028386804/article/details/85919481