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/lib/msf/core/auxiliary/ubiquiti.rb
Views: 11784
# -*- coding: binary -*-12require 'bson'3require 'zip'45module Msf6###7#8# This module provides methods for working with Ubiquiti equipment9#10###11module Auxiliary::Ubiquiti12include Msf::Auxiliary::Report1314def decrypt_unf(contents)15aes = OpenSSL::Cipher.new('aes-128-cbc')16aes.decrypt17aes.key = 'bcyangkmluohmars' # https://github.com/zhangyoufu/unifi-backup-decrypt/blob/master/E>18aes.padding = 019aes.iv = 'ubntenterpriseap'20aes.update(contents)21end2223def repair_zip(fname)24zip_exe = Msf::Util::Helper.which('zip')25if zip_exe.nil?26print_error('Zip utility not found.')27return nil28end29print_status('Attempting to repair zip file (this is normal and takes some time)')30temp_file = Rex::Quickfile.new('fixed_zip')31system("yes | #{zip_exe} -FF #{fname} --out #{temp_file.path}.zip > /dev/null")32return File.read("#{temp_file.path}.zip", mode: 'rb')33end3435def extract_and_process_db(db_path)36f = nil37Zip::File.open(db_path) do |zip_file|38# Handle entries one by one39zip_file.each do |entry|40# Extract to file41next unless entry.name == 'db.gz'4243print_status('extracting db.gz')44gz = Zlib::GzipReader.new(entry.get_input_stream)45f = gz.read46gz.close47break48end49end50f51end5253def bson_to_json(byte_buffer)54# This function takes a byte buffer (db file from Unifi read in), which is a bson string55# it then converts it to JSON, where it uses the 'select collection' documents56# as keys. For instance a bson that contained the follow (displayed in json57# for ease):58# {"__cmd"=>"select", "collection"=>"heatmap"}59# {'example'=>'example'}60# {'example2'=>'example2'}61# would become:62# {'heatmap'=>[{'example'=>'example'}, {'example2'=>'example2'}]}63# this is mainly done to ease the grouping of items for easy navigation later.6465buf = BSON::ByteBuffer.new(byte_buffer)66output = {}67key = ''6869while buf70begin71# read the document from the buffer72bson = BSON::Document.from_bson(buf)73if bson.has_key?('__cmd')74key = bson['collection']75output[key] = []76next77end78output[key] << bson79rescue RangeError80break81end82end83output84end8586def unifi_config_eater(thost, tport, config)87# This is for the Ubiquiti Unifi files. These are typically in the backup download zip file88# then in the db.gz file as db. It is a MongoDB BSON file, which can be difficult to read.89# https://stackoverflow.com/questions/51242412/undefined-method-read-bson-document-for-bsonmodule90# The BSON file is a bunch of BSON Documents chained together. There doesn't seem to be a good91# way to read these files directly, so looping through loading the content seems to work with92# minimal repercussions.9394# The file format is broken into sections by __cmd select documents as such:95# {"__cmd"=>"select", "collection"=>"heatmap"}96# we can pull the relevant section name via the collection value.9798if framework.db.active99creds_template = {100address: thost,101port: tport,102protocol: 'tcp',103workspace_id: myworkspace_id,104origin_type: :service,105private_type: :password,106service_name: '',107module_fullname: fullname,108status: Metasploit::Model::Login::Status::UNTRIED109}110end111112report_host({113host: thost,114info: 'Ubiquiti Unifi Controller'115})116117store_loot('unifi.json', 'application/json', thost, config.to_s.strip, 'unifi.json', 'Ubiquiti Unifi Configuration')118119# Example BSON lines120# {"__cmd"=>"select", "collection"=>"admin"}121# {"_id"=>BSON::ObjectId('5c7f23af3825ce2067a1d9ce'), "name"=>"adminuser", "email"=>"[email protected]", "x_shadow"=>"$6$R4qnAaaF$AAAlL2t.fXu0aaa9z3uvcIm3ujbtJLhIO.lN1xZqHZPQoUAXs2BUTmI5UbuBo2/8t3epzbVLib17Ls7GCVx7V.", "time_created"=>1551825823, "last_site_name"=>"default", "ubic_name"=>"[email protected]", "ubic_uuid"=>"c23da064-3f4d-282f-1dc9-7e25f9c6812c", "ui_settings"=>{"dashboardConfig"=>{"lastActiveDashboardId"=>"2c7f2d213813ce2487d1ac38", "dashboards"=>{"3c7f678a3815ce2021d1d9c7"=>{"order"=>1}, "5b4f2d269115ce2087d1abb9"=>{}}}}}122def process_admin(lines, credential_data)123lines.each do |line|124admin_name = line['name']125admin_email = line['email']126admin_password_hash = line['x_shadow']127print_good("Admin user #{admin_name} with email #{admin_email} found with password hash #{admin_password_hash}")128next unless framework.db.active129130cred = credential_data.dup131cred[:username] = admin_name132cred[:private_data] = admin_password_hash133cred[:private_type] = :nonreplayable_hash134create_credential_and_login(cred)135end136end137138# Example BSON lines139# {"__cmd"=>"select", "collection"=>"firewallrule"}140# {"_id"=>BSON::ObjectId('5c7f23af3825ce2067a1d9ce'), "ruleset" => "WAN_OUT", "rule_index" => "2000", "name" => "Block Example", "enabled" => true, "action" => "reject", "protocol_match_excepted" => false, "logging" => false, "state_new" => false, "state_established" => false, "state_invalid" => false, "state_related" => false, "ipsec" => "", "src_firewallgroup_ids" => ["1a1c15a11111ce14b1f1111a"], "src_mac_address" => "", "dst_firewallgroup_ids" => [], "dst_address" => "", "src_address" => "", "protocol" => "all", "icmp_typename" => "", "src_networkconf_id" => "", "src_networkconf_type" => "NETv4", "dst_networkconf_id" => "", "dst_networkconf_type" => "NETv4", "site_id" => "1c1f208b3815ce1111a1a1a1"}141def process_firewallrule(lines, _)142lines.each do |line|143rule = (line['action']).to_s144unless line['dst_address'].empty?145rule << " dst addresses: #{line['dst_address']}"146end147unless line['dst_firewallgroup_ids'].empty?148rule << " dst group: #{line['dst_firewallgroup_ids'].join(', ')}"149end150unless line['src_address'].empty?151rule << " src addresses: #{line['src_address']}"152end153unless line['src_firewallgroup_ids'].empty?154rule << " src group: #{line['src_firewallgroup_ids'].join(', ')}"155end156rule << " protocol: #{line['protocol']}"157158print_status("#{line['enabled'] ? 'Enabled' : 'Disabled'} Firewall Rule '#{line['name']}': #{rule}")159end160end161162# Example BSON lines163# {"__cmd"=>"select", "collection"=>"radiusprofile"}164# {"_id"=>BSON::ObjectId('2c7a318c38c5ce2f86d179cb'), "attr_no_delete"=>true, "attr_hidden_id"=>"Default", "name"=>"Default", "site_id"=>"3c7f226b2315be2087a1d5b2", "use_usg_auth_server"=>true, "auth_servers"=>[{"ip"=>"192.168.0.1", "port"=>1812, "x_secret"=>""}], "acct_servers"=>[]}165def process_radiusprofile(lines, credential_data)166lines.each do |line|167line['auth_servers'].each do |server|168report_service(169host: server['ip'],170port: server['port'],171name: 'radius',172proto: 'udp'173)174next unless server['x_secret'] # no need to output if the secret is blank, therefore its not configured175176print_good("Radius server: #{server['ip']}:#{server['port']} with secret '#{server['x_secret']}'")177next unless framework.db.active178179cred = credential_data.dup180cred[:username] = ''181cred[:private_data] = server['x_secret']182cred[:address] = server['ip']183cred[:port] = server['port']184create_credential_and_login(cred)185end186end187end188189# settings has multiple items we care about:190# x_mesh_essid/x_mesh_psk -> should contain the mesh network wifi name and password191# ntp -> ntp servers192# x_ssh_username/x_ssh_password/x_ssh_keys/x_ssh_sha512passwd193194# Example lines195# {"__cmd"=>"select", "collection"=>"setting"}196# {"_id"=>BSON::ObjectId('3c3e21ac3715ce20a721d9ba'), "site_id"=>"3c2f215b3825ca2087c1dfb6", "key"=>"ntp", "ntp_server_1"=>"0.ubnt.pool.ntp.org", "ntp_server_2"=>"1.ubnt.pool.ntp.org", "ntp_server_3"=>"2.ubnt.pool.ntp.org", "ntp_server_4"=>"3.ubnt.pool.ntp.org"}197# {"_id"=>BSON::ObjectId('3c3e21ac3715ce20a721d9bb'), "site_id"=>"3c2f215b3825ca2087c1dfb6", "key"=>"mgmt", "advanced_feature_enabled"=>false, "x_ssh_enabled"=>true, "x_ssh_bind_wildcard"=>false, "x_ssh_auth_password_enabled"=>true, "unifi_idp_enabled"=>true, "x_mgmt_key"=>"ba6cbe170f8276cd86b24ac79ab29afc", "x_ssh_username"=>"admin", "x_ssh_password"=>"16xoB6F2UyAcU6fP", "x_ssh_keys"=>[], "x_ssh_sha512passwd"=>"$6$R4qnAaaF$AAAlL2t.fXu0aaa9z3uvcIm3ujbtJLhIO.lN1xZqHZPQoUAXs2BUTmI5UbuBo2/8t3epzbVLib17Ls7GCVx7V."}198# {"_id"=>BSON::ObjectId('3c3e21ac3715ce20a721d9bc'), "site_id"=>"3c2f215b3825ca2087c1dfb6", "key"=>"connectivity", "enabled"=>true, "uplink_type"=>"gateway", "x_mesh_essid"=>"vwire-851237d214c8c6ba", "x_mesh_psk"=>"523a9b872b4624c7894f96c3ae22cdfa"}199# {"_id"=>BSON::ObjectId('3c3e21ac3715ce20a721d9bd'), "site_id"=>"3c2f215b3825ca2087c1dfb6", "key"=>"snmp", "community": "public", "enabled": true, "enabledV3": true, "username": "usernamesnmpv3", "x_password": "passwordsnmpv3"}200def process_setting(lines, credential_data)201lines.each do |line|202case line['key']203when 'snmp'204if framework.db.active205cred = credential_data.dup206cred[:protocol] = 'udp'207cred[:port] = 161208cred[:service_name] = 'snmp'209else210cred = {} # throw away211end212unless line['community'].blank?213print_good("SNMP v2 #{line['enabled'] ? 'enabled' : 'disabled'} with password #{line['community']}")214cred[:private_data] = line['community']215create_credential_and_login(cred) if framework.db.active216end217unless line['x_password'].blank? || line['username'].blank?218print_good("SNMP v3 #{line['enabledV3'] ? 'enabled' : 'disabled'} with username #{line['username']} password #{line['x_password']}")219cred[:username] = line['username']220cred[:private_data] = line['x_password']221create_credential_and_login(cred) if framework.db.active222end223when 'connectivity'224print_good("Mesh Wifi Network #{line['x_mesh_essid']} password #{line['x_mesh_psk']}")225next unless framework.db.active226227cred = credential_data.dup228cred[:username] = line['x_mesh_essid']229cred[:private_data] = line['x_mesh_psk']230create_credential_and_login(cred)231when 'ntp'232['ntp_server_1', 'ntp_server_2', 'ntp_server_3', 'ntp_server_4'].each do |ntp|233next if line[ntp].empty? || line[ntp].ends_with?('ubnt.pool.ntp.org')234235report_service(236host: line[ntp],237port: '123',238name: 'ntp',239proto: 'udp'240)241print_good("NTP Server: #{line[ntp]}")242end243when 'mgmt'244admin_name = line['x_ssh_username']245admin_password_hash = line['x_ssh_sha512passwd']246admin_password = line['x_ssh_password']247print_good("SSH user #{admin_name} found with password #{admin_password} and hash #{admin_password_hash}")248line['x_ssh_keys'].each do |key|249print_good("SSH user #{admin_name} found with SSH key: #{key}")250end251next unless framework.db.active252253cred = credential_data.dup254cred[:username] = admin_name255cred[:private_data] = admin_password_hash256cred[:private_type] = :nonreplayable_hash257login = create_credential_and_login(cred)258if login.present? && admin_password.present?259create_cracked_credential(username: admin_name, password: admin_password, core_id: login.core.id)260end261end262end263end264265# Example lines266# {"__cmd"=>"select", "collection"=>"wlanconf"}267# {"_id"=>BSON::ObjectId('3c3e21ac3715ce20a721d9ba'), "enabled" => true, "security" => "wpapsk", "wep_idx" => 1, "wpa_mode" => "wpa2", "wpa_enc" => "ccmp", "usergroup_id" => "5a7f111a3815ce1111a1d1c3", "dtim_mode" => "default", "dtim_ng" => 1, "dtim_na" => 1, "minrate_ng_enabled" => false, "minrate_ng_advertising_rates" => false, "minrate_ng_data_rate_kbps" => 1000, "minrate_ng_cck_rates_enabled" => true, "minrate_na_enabled" => false, "minrate_na_advertising_rates" => false, "minrate_na_data_rate_kbps" => 6000, "mac_filter_enabled" => false, "mac_filter_policy" => "allow", "mac_filter_list" => [], "bc_filter_enabled" => false, "bc_filter_list" => [], "group_rekey" => 3600, "name" => "ssid_name", "x_passphrase" => "supersecret", "wlangroup_id" => "5c7f208c3815ce2087d1d9c4", "schedule" => [], "minrate_ng_mgmt_rate_kbps" => 1000, "minrate_na_mgmt_rate_kbps" => 6000, "minrate_ng_beacon_rate_kbps" => 1000, "minrate_na_beacon_rate_kbps" => 6000, "site_id" => "5c7f208b3815ce2087d1d9b6", "x_iapp_key" => "d11a1c86df1111be86aaa69e8aa1c57f", "no2ghz_oui" => true}268def process_wlanconf(lines, credential_data)269lines.each do |line|270ssid = line['name']271mode = line['security']272password = line['x_passphrase']273print_good("#{line['enabled'] ? 'Enabled' : 'Disabled'} wifi #{ssid} on #{mode}(#{line['wpa_mode']},#{line['wpa_enc']}) has password #{password}")274next unless framework.db.active275276cred = credential_data.dup277cred[:username] = ssid278cred[:private_data] = password279create_credential_and_login(cred)280end281end282283# Example lines284# {"__cmd"=>"select", "collection"=>"firewallgroup"}285# {"_id"=>BSON::ObjectId('3c3e21ac3715ce20a721d9ba'), "name" => "Cameras", "group_type" => "address-group", "group_members" => ["1.1.1.1"], "site_id" => "5c7f111b3815ce208aaa111a"}286def process_firewallgroup(lines, _)287lines.each do |line|288print_status("Firewall Group: #{line['name']}, group type: #{line['group_type']}, members: #{line['group_members'].join(', ')}")289end290end291292# Example lines293# {"__cmd"=>"select", "collection"=>"device"}294# {"_id"=>BSON::ObjectId('3c3e21ac3715ce20a721d9ba'), "ip" => "5.5.5.5", "mac" => "cc:cc:cc:cc:cc:cc", "model" => "UGW3", "type" => "ugw", "version" => "4.4.44.5213844", "adopted" => true, "site_id" => "5aaaaaabaaaaae1117d1d1b6", "x_authkey" => "eaaaaaaa63e59ab89c111e11d6e11aa1", "cfgversion" => "aaa4b11b1df1a111", "config_network" => {"type" => "dhcp", "ip" => "1.1.1.1"}, "license_state" => "registered", "two_phase_adopt" => false, "unsupported" => false, "unsupported_reason" => 0, "x_fingerprint" => "aa:aa:11:aa:11:11:11:11:11:11:11:11:11:11:11:11", "x_ssh_hostkey" => "MIIBIjANBgkAhkiG9w0AAQEFAAOCAQ8AMIIBCgKCAQEAAU4S/7r548xvtGuHlgAAAKzkrL+t97ZWAZru8wQFbltEB4111HiIAkzt041td8V+P7c1bQtn3YQdViAuH2h2sgt8feAvMWo56OskAoDvHwAEv5AWqmPKy/xmKbdfgA5wTzvSztPGFA4QuOuA1YxQICf1MgpoOtplAAA31JxAYF/t7n8qgvJlm1JRv2AAAZHHtSiz1IaxzOO9LAAAqCfHvHugPcZYk2yAAAP7JrnnR1fAVj9F4aaYaA0eSjvDTAglykXHCbh1EWAAAecqHZ/SWn9cjmuAAArZxxG6m6Eu/aj9we82/PmtKzQGN0RWUsgrxajQowtNpVsNTnaOglUsfQIDAAAA", "x_ssh_hostkey_fingerprint" => "11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11", "inform_url" => "http://1.1.2.2:8080/inform", "inform_ip" => "1.1.1.1", "serial" => "AAAAAAAAAAAA", "required_version" => "4.0.0", "ethernet_table" => [{ "mac" => "b4:fb:e4:cc:cc:cc", "num_port" => 1, "name" => "eth0"}, {"mac" => "b4:fb:e4:bb:bb:bb", "num_port" => 1, "name" => "eth1"}, {"mac" => "b4:fb:e4:aa:aa:aa", "num_port" => 1, "name" => "eth2"}], "fw_caps" => 184323, "hw_caps" => 0, "usg_caps" => 786431, "board_rev" => 16, "x_aes_gcm" => true, "ethernet_overrides" => [{"ifname" => "eth1", "networkgroup" => "LAN"}, {"ifname" => "eth0", "networkgroup" => "WAN"}], "led_override" => "default", "led_override_color" => "#0000ff", "led_override_color_brightness" => 100, "outdoor_mode_override" => "default", "name" => "USG", "map_id" => "1a111c2e1111ce2087d1e199", "x" => -22.11111198630405, "y" => -41.1111113859866, "heightInMeters" => 2.4}295def process_device(lines, _)296lines.each do |line|297report_host({298host: line['ip'],299name: line['name'],300mac: line['mac'],301os_name: 'Ubiquiti Unifi'302})303print_good("Unifi Device #{line['name']} of model #{line['model']} on #{line['ip']}")304end305end306307# Example lines308# {"__cmd"=>"select", "collection"=>"user"}309# {"_id"=>BSON::ObjectId('3c3e21ac3715ce20a721d9ba'), "mac" => "00:0c:29:11:aa:11", "site_id" => "5c7f111b1111aa2087d11111", "oui" => "Vmware", "is_guest" => false, "first_seen" => 1551111161, "last_seen" => 1561621747, "is_wired" => true, "hostname" => "android", "usergroup_id" => "", "name" => "example device", "noted" => true, "use_fixedip" => true, "network_id" => "1c7f111a1115aa2087aaa9aa", "fixed_ip" => "7.7.7.7"}310def process_user(lines, _)311lines.each do |line|312host_hash = {313name: line['hostname'],314mac: line['mac']315}316desc = "#{line['hostname']} (#{line['mac']})"317if line['fixed_ip']318host_hash[:host] = line['fixed_ip']319desc << " on IP #{line['fixed_ip']}"320end321if line['name']322host_hash[:info] = line['name']323desc << " with name #{line['name']}"324end325report_host(host_hash)326print_good("Network Device #{desc} found")327end328end329330# here is where we actually process the file331config.each do |key, value|332next unless respond_to?("process_#{key}")333334credential_data = creds_template.dup335send("process_#{key}", value, credential_data)336end337end338end339end340341342