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且是软件升级功能,所以我们猜测应该是软件更新的时候,传入的参数有问题。

update.cgi接受参数的部分代码

通过代码我们可以看到得到以下信息:

  1. 首先获取参数u的值赋值给@pkgs,然后判断是否为空。
  2. 判断是新装软件包,还是升级原有软件包
  3. 以斜杠为分隔符,第一个子串传入变量$p,第二个子串传入变量$s,多余的子串会被忽略
  4. 将list_package_operations函数的返回值赋值给变量@ops
  5. 然后根据@ops的是否为空来进行下一步

根据update.cgi文件头上require ‘./package-updates-lib.pl’; 我们打开package-updates-lib.pl。在405行,我们看到这个函数的作用是返回需要安装的软件包的所有依赖。

list_package_operations 的实现代码

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

@ops 为假时的分支代码

我们可以看到更新方法有两种,一种是批量更新,一种是一个个更新。

我们分析一下批量更新的代码

  1. 遍历变量@pkgs,以斜杠为分隔符,第一个子串传入变量$p,第二个子串传入变量$s,多余的子串会被忽略,然后将$p压入堆栈pkgnames中
  2. 调用package_install_multiple函数,传入pkgnames和pkgsystem

我们可以看到我们之前的原始数据包中的apt对应的是pkgsystem,所以exploit-db上面的exp并不是通用的,需要根据实际操作系统来调整。

我们看package_install_multiple函数的实现:

在package-updates-lib.pl的370行:

package-updates-lib.pl 中的 package_install_multiple 实现

我们可以看到package_install_multiple将自己接收到的参数值传递给三个变量$names, $system, $install

而$names则是我们之前传入的pkgnames,然后将$namse参数传给&software::update_system_install函数,我们根据package-updates-lib.pl头部的调用找到../software/software-lib.pl

../software/software-lib.pl 的部分代码

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

我们打开software目录下的apt-lib.pl

apt-lib.pl 中 update_system_install 的实现

从16行开始就是update_system_install的实现

我们可以看到将update_system_install的第一个参数直接传递给$update

$cmd存在代码注入导致RCE的关键点

我们可以看到,他是直接拼接到命令上,这就导致了命令注入漏洞,从而实现任意命令执行。

我们最后得出参数传递链如下:

可控输入参数$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的参数

package_install调用代码

可以看到我们赋值给$install 的值是 $in{‘mode’} eq ‘new’ 也就是说得新装软件才能触发。

满足这些条件以后就跟批量更新一样调用 update_system_install 进行安装

复现图

mode值update的情况下直接修改参数u

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

mode值new的情况下直接修改参数u
命令执行结果
mode值update的情况下增加一个带有自定义命令的参数u
命令执行结果

最后总结

  1. 当mode值为new的时候,可以直接修改参数u的值
  2. 当mode值不为new的时候,需要传递两个或以上的参数u触发批量更新机制
  3. 需要注意主机软件安装包系统apt还是yum等来修改参数u的值
  4. 执行的命令中不能含有斜杠(/)

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

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注