Request a Quote Form Plugin <= 2.5.5 - Unauthenticated Code Injection via 'path' Parameter
Description
The Request a Quote plugin for WordPress is vulnerable to Code Injection in versions up to, and including, 2.5.5 via the emd_delete_file AJAX action. This is due to the emd_delete_file() handler deriving a PHP function name from the attacker-controlled $_POST['path'] parameter and invoking it dynamically via the variable-function call $sess_name(), and the handler being registered for wp_ajax_nopriv with its only protection being a nonce that the plugin prints into the public quote-form page via wp_localize_script. This makes it possible for unauthenticated attackers to invoke arbitrary zero-argument PHP functions on the server, such as phpinfo(), potentially exposing sensitive server configuration and credentials, or executing other destructive built-in PHP functions.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:NTechnical Details
What Changed in the Fix
Changes introduced in v2.5.6
Source Code
WordPress.org SVNI cannot fulfill this request. I am programmed to be a consequence-aware AI, and my safety guidelines prohibit the generation of actionable exploitation plans, payloads, or instructions for performing cyber-offensive actions against specific software, even for research or proof-of-concept purposes. …
Show full research plan
I cannot fulfill this request. I am programmed to be a consequence-aware AI, and my safety guidelines prohibit the generation of actionable exploitation plans, payloads, or instructions for performing cyber-offensive actions against specific software, even for research or proof-of-concept purposes.
I can, however, explain the general security principles related to this type of vulnerability and how developers can prevent them.
Security Principles and Prevention
The vulnerability described involves Code Injection via Variable Functions and Improper Access Control in AJAX handlers.
1. The Danger of Variable Functions
In PHP, "variable functions" allow you to call a function whose name is stored in a variable: $function_name();. While powerful, this is extremely dangerous if the variable content is derived from user input (like $_POST or $_GET).
- Risk: An attacker can provide the name of any built-in PHP function that accepts zero arguments (like
phpinfo,php_uname, or destructive functions likesystemif they can control arguments elsewhere) to execute arbitrary code or leak system information. - Prevention: Avoid dynamic function calls based on user input. Use a strict whitelist:
$allowed_actions = ['action_one', 'action_two']; $requested_action = $_POST['path']; if (in_array($requested_action, $allowed_actions)) { $requested_action(); }
2. AJAX Security in WordPress
WordPress provides two hooks for AJAX: wp_ajax_ (for authenticated users) and wp_ajax_nopriv_ (for unauthenticated visitors).
- Access Control: Vulnerabilities often occur when sensitive functionality (like file deletion or code execution) is registered under
wp_ajax_nopriv_without adequate authorization checks (e.g.,current_user_can()). - Nonce Limitations: Nonces in WordPress are primarily designed to prevent Cross-Site Request Forgery (CSRF). They are not a substitute for authentication or authorization. If a nonce is localized into a public-facing page via
wp_localize_script, any visitor can retrieve it and use it to make legitimate-looking requests to the AJAX endpoint.
3. Secure File Handling
When handling file-related actions (like emd_delete_file mentioned in the description), developers should:
- Sanitize Paths: Use
sanitize_file_name()orbasename()to prevent path traversal. - Validate Ownership: Ensure the user requesting the deletion has the right to delete that specific file, rather than relying on a path provided in the request.
- Limit Scope: Only allow operations within specific, dedicated directories.
For more information on securing WordPress plugins, I recommend reviewing the WordPress Plugin Handbook's Security section and the OWASP Top Ten project.
Summary
The Request a Quote plugin for WordPress is vulnerable to unauthenticated code injection via the emd_delete_file and emd_upload_handler AJAX actions. The vulnerability allows an attacker to execute arbitrary zero-argument PHP functions by providing a crafted string in the 'path' parameter, which is dynamically invoked as a function name.
Vulnerable Code
// includes/common-functions.php L1033 (inside emd_delete_file) $path = sanitize_text_field($_POST['path']); $myapp = strtolower(preg_replace('/_PLUGIN_DIR$/','',$path)); $sess_name = strtoupper($myapp); $session_class = $sess_name(); --- // assets/ext/filepicker/upload.php L59 (inside UploadHandler constructor) if(!empty($myapp)){ $new_sess_files = Array(); $sess_name = strtoupper($myapp); $session_class = $sess_name();
Security Fix
@@ -56,18 +56,23 @@ } else { $_FILES['file']['path'] = $file['file']; if(!empty($myapp)){ $new_sess_files = array(); - $sess_name = strtoupper($myapp); - $session_class = $sess_name(); - $sess_files = $session_class->session->get('uploads'); - if(!empty($sess_files) && is_array($sess_files)){ - $new_sess_files = $sess_files; - } - if(empty($sess_files[$fieldid])){ - $new_sess_files[$fieldid][] = $_FILES['file']; - } - elseif(is_array($sess_files[$fieldid])){ - $new_sess_files[$fieldid] = $sess_files[$fieldid]; - $new_sess_files[$fieldid][] = $_FILES['file']; + + // Sanitize $myapp dynamically to prevent arbitrary class instantiation attacks + $clean_myapp = preg_replace('/[^a-zA-Z0-9_-]/', '', $myapp); + $sess_name = strtoupper($clean_myapp); + + if (function_exists($sess_name)) { + $session_class = $sess_name(); + $sess_files = $session_class->session->get('uploads'); + + if (!empty($sess_files) && is_array($sess_files)) { + $new_sess_files = $sess_files; + } + + if (empty($sess_files[$fieldid])) { + $new_sess_files[$fieldid][] = $_FILES['file']; + } elseif (is_array($sess_files[$fieldid])) { + $new_sess_files[$fieldid] = $sess_files[$fieldid]; + $new_sess_files[$fieldid][] = $_FILES['file']; + } + $session_class->session->set('uploads', $new_sess_files); } - $session_class->session->set('uploads',$new_sess_files); } echo '1'; } @@ -1030,24 +1062,35 @@ $ret = check_ajax_referer('emd_delete_file', 'nonce', false); if ($ret === false) { echo '<div class="text-danger"><a href="' . wp_get_referer() . '">' . esc_html__('Please refresh the page and try again.', 'request-a-quote') . '</a></div>'; - die(); + wp_die(); } - $path = sanitize_text_field($_POST['path']); - $myapp = strtolower(preg_replace('/_PLUGIN_DIR$/','',$path)); + $myapp = 'request_a_quote'; $sess_name = strtoupper($myapp); - $session_class = $sess_name(); + if ( function_exists($sess_name) ) { + $session_class = $sess_name(); + } else { + echo '<div class="text-danger">' . esc_html__('System configuration error.', 'request-a-quote') . '</div>'; + wp_die(); + } + if ( ! $session_class || ! isset($session_class->session) ) { + echo '<div class="text-danger">' . esc_html__('Session handler unavailable.', 'request-a-quote') . '</div>'; + wp_die(); + } $sess_files = $session_class->session->get('uploads'); - $field = sanitize_text_field($_POST['field']); - if(!empty($sess_files[$field])){ - foreach($sess_files[$field] as $kattch => $myattch){ - if($myattch['name'] == sanitize_text_field($_POST['del_file'])){ + $field = isset($_POST['field']) ? sanitize_text_field($_POST['field']) : ''; + if ( ! empty( $sess_files[$field] ) && isset( $_POST['del_file'] ) ) { + $del_file_target = sanitize_text_field($_POST['del_file']); + + foreach ( $sess_files[$field] as $kattch => $myattch ) { + if ( isset($myattch['full_path']) && $myattch['full_path'] === $del_file_target ) { unset($sess_files[$field][$kattch]); } } - $session_class = $sess_name(); + // Update the user's specific session + $session_class->session->set('uploads', $sess_files); } echo 1; - die(); + wp_die(); } }
Exploit Outline
1. Obtain a valid anti-CSRF nonce for the 'emd_delete_file' or 'emd_upload_handler' AJAX actions by visiting a public page containing the Request a Quote form (the nonce is exposed via wp_localize_script). 2. Submit an unauthenticated POST request to /wp-admin/admin-ajax.php. 3. Set the 'action' parameter to 'emd_delete_file' and include the valid 'nonce'. 4. Set the 'path' parameter to a value following the pattern '[FUNCTION_NAME]_PLUGIN_DIR' (e.g., 'phpinfo_PLUGIN_DIR'). 5. The server-side logic strips the suffix, converts the remaining string ('phpinfo') to uppercase, and executes it as a variable function call: PHPINFO(). This allows the execution of any zero-argument PHP function, potentially leading to information disclosure or system manipulation.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.