CVE-2026-40771

Contest Gallery – Upload & Vote Photos, Media, Sell with PayPal & Stripe <= 28.1.6 - 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.7
Patched in
10d
Time to patch

Description

The Contest Gallery – Upload & Vote Photos, Media, Sell with PayPal & Stripe plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 28.1.6 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.

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.6
PublishedApril 21, 2026
Last updatedApril 30, 2026
Affected plugincontest-gallery

What Changed in the Fix

Changes introduced in v28.1.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

frontend.php` via a query. * Okay, let's assume the vulnerability is in an AJAX action named `cg_load_entries`. The parameter is `orderBy`. Payload: `action=cg_load_entries&orderBy=ID AND (SELECT 1 FROM (SELECT SLEEP(5))x)` * Wait, I found a more likely candidate. …

Show full research plan

frontend.php` via a query.

*   Okay, let's assume the vulnerability is in an AJAX action named `cg_load_entries`.
    The parameter is `orderBy`.
    Payload: `action=cg_load_entries&orderBy=ID AND (SELECT 1 FROM (SELECT SLEEP(5))x)`

*   Wait, I found a more likely candidate.
    The plugin has a `cg_get_images_data` AJAX action.
    In `28.1.6`, it might be using `$_POST['picture_id']` or similar.

*   Wait! I found it. The vulnerability is in the handling of the `galleryIDuser` or `realGid` in some specific context where it is NOT `absint`ed.
    Wait, look at `v10/v10-frontend/load-data-ajax.php` again.
    Line 118:
    ```php
    $fromCommentsWpUserIdsQueryResults = $wpdb->get_results( "SELECT DISTINCT WpUserId FROM $tablenameComments WHERE WpUserId > 0 AND GalleryID = $galeryID");
    ```
    Wait... what if `$galeryID` is NOT the only thing in that query?
    No, that query looks very specific.

*   Let's look for `wp_ajax_nopriv` in `ajax-functions-frontend.php`.
    One very common action is `
Research Findings
Static analysis — not yet PoC-verified

Summary

The Contest Gallery plugin for WordPress is vulnerable to unauthenticated SQL Injection via the 'contest-gal1ery-[ID]-voting' cookie in the rating scripts (e.g., rate-picture-five-star.php). The plugin directly concatenates the cookie's value into SQL queries without sanitization or preparation, allowing attackers to extract sensitive data or perform unauthorized database operations.

Vulnerable Code

// v10/v10-frontend/data/rating/rate-picture-five-star.php line 493
    $CookieId = '';
    if(isset($_COOKIE['contest-gal1ery-'.$galeryID.'-voting']) && $options['general']['CheckCookie'] == 1) {
        $CookieId = $_COOKIE['contest-gal1ery-'.$galeryID.'-voting'];
    }

---

// v10/v10-frontend/data/rating/rate-picture-five-star.php line 718
     }elseif ($CheckCookie == 1 && $CheckIp != 1){
        if(isset($_COOKIE['contest-gal1ery-'.$galeryID.'-voting'])) {
            $lastVotedIpRow = $wpdb->get_row( "SELECT id, Rating FROM $tablenameIP WHERE Rating >= '1' && CookieId = '$CookieId' && GalleryID = '$galeryID' && pid = '$pictureID' ORDER BY id DESC LIMIT 1" );
            $countUserVotesForImage = $wpdb->get_var( "SELECT COUNT(*) AS NumberOfRows FROM $tablenameIP WHERE Rating >= '1' && CookieId = '$CookieId' && GalleryID = '$galeryID' && pid = '$pictureID'" );
        }
     }

Security Fix

diff -ru /v10/v10-frontend/data/rating/rate-picture-five-star.php /v10/v10-frontend/data/rating/rate-picture-five-star.php
--- /v10/v10-frontend/data/rating/rate-picture-five-star.php
+++ /v10/v10-frontend/data/rating/rate-picture-five-star.php
@@ -455,10 +455,10 @@
         }
 
     }
-    $CookieId = '';
     if($CheckCookie==1) {
-        if(!isset($_COOKIE['contest-gal1ery-'.$galeryID.'-voting'])) {
+        $CookieId = cg_get_valid_frontend_cookie($galeryID,'voting');
+        if(empty($CookieId)) {
             $cookieValue = cg_set_cookie($galeryID,'voting');
             ?>
             <script data-cg-processing="true">
@@ -715,17 +711,49 @@
             $countUserVotesForImage = $wpdb->get_var( "SELECT COUNT(*) AS NumberOfRows FROM $tablenameIP WHERE Rating >= '1' && WpUserId = '$wpUserId' && GalleryID = '$galeryID' && pid = '$pictureID'" );
         }
      }elseif ($CheckCookie == 1 && $CheckIp != 1){
-        if(isset($_COOKIE['contest-gal1ery-'.$galeryID.'-voting'])) {
-            $lastVotedIpRow = $wpdb->get_row( "SELECT id, Rating FROM $tablenameIP WHERE Rating >= '1' && CookieId = '$CookieId' && GalleryID = '$galeryID' && pid = '$pictureID' ORDER BY id DESC LIMIT 1" );
-            $countUserVotesForImage = $wpdb->get_var( "SELECT COUNT(*) AS NumberOfRows FROM $tablenameIP WHERE Rating >= '1' && CookieId = '$CookieId' && GalleryID = '$galeryID' && pid = '$pictureID'" );
+        if(!empty($CookieId)) {
+            $lastVotedIpRow = $wpdb->get_row($wpdb->prepare(
+                "SELECT id, Rating FROM $tablenameIP WHERE Rating >= %d AND CookieId = %s AND GalleryID = %d AND pid = %d ORDER BY id DESC LIMIT 1",
+                1,
+                $CookieId,
+                $galeryID,
+                $pictureID
+            ));
+            $countUserVotesForImage = $wpdb->get_var($wpdb->prepare(
+                "SELECT COUNT(*) AS NumberOfRows FROM $tablenameIP WHERE Rating >= %d AND CookieId = %s AND GalleryID = %d AND pid = %d",
+                1,
+                $CookieId,
+                $galeryID,
+                $pictureID
+            ));
         }

Exploit Outline

To exploit this vulnerability, an attacker must identify a gallery where 'Cookie recognition' is enabled for voting. The attacker then makes a request to the voting endpoint (typically an AJAX request to rate-picture-five-star.php or rate-picture-one-star.php) while providing a malicious SQL payload inside a cookie named 'contest-gal1ery-[ID]-voting', where [ID] is the target gallery ID. Because the plugin uses the cookie value directly in a database query without using $wpdb->prepare() or sanitization, the payload is executed by the database. No authentication is required for this attack if the gallery allows unauthenticated voting.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.