Contest Gallery – Upload & Vote Photos, Media, Sell with PayPal & Stripe <= 28.1.6 - Unauthenticated SQL Injection
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:NTechnical Details
<=28.1.6What Changed in the Fix
Changes introduced in v28.1.7
Source Code
WordPress.org SVNfrontend.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 `
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
@@ -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.